目录
- 前言
- 一、认识对象
- 1.什么是对象
- 2.对象的方法
- 3.对象的遍历
- 4.对象的深浅克隆
- 二、认识函数上下文
- 1.函数的上下文规则 ★
- 2.call和apply ★
- 三、构造函数
- 1.用new操作符调用函数
- 2.类与实例
- 3.构造函数和'类"
- 四、原型和原型链
- 1.prototype和原型链查找 ★
- 2.在prototype上添加方法
- 3.原型链的终点
- 4.继承 ★
- 五、上升到面向对象
- 1.红绿灯小案例
- 2.炫彩小球小案例
- 六、JS的内置对象
- 1.包装类
- 2.Math对象 ★
- 3.Date对象 ★
- 七、继承与内置构造函数[拓展]
前言
在计算机编程中,对象是一种非常重要的概念。它可以帮助我们更好地组织和管理数据,使代码更加模块化和可复用。对象可以存储多个键值对,每个键值对表示一个属性,属性可以是基本类型的值或者其他对象。通过对象,我们可以定义和调用方法,对属性进行操作或者实现特定的功能。
面向对象编程的思想强调通过对象和类来组织和管理代码,使其更加易于理解和维护。实际上,将问题和解决方案分解为对象是一种非常自然和直观的方式,也是实现代码复用和模块化的关键。
在接下来的学习中,我们将更加深入地认识和掌握这些概念,并通过小案例来实践和巩固所学的知识。让我们一起展开学习之旅吧!
一、认识对象
1.什么是对象
-
概念: 对象(object)是“键值对”的集合,表示属性和值的映射关系
-
语法: k和v之间用冒号分隔,每组k:v之间用逗号分隔,最后一个k:v对后可以不书写逗号
- 如果对象的属性键名不符合JS标识符命名规范,则这个键名必须用引号包裹
- 如:
'favorite-book':'舒克和贝塔'
属性名中有短横,不符合]S标识符命名规范,属性名必须用引号包裹
-
对象属性的访问
-
可以用“点语法”访问对象中指定键的值
xiaoming.name;//小明 xiaoming.age;//12 xiaoming.hobbys;//['足球',游泳','编程]
-
如果属性名不符合]S标识符命名规范,则必须用方括号的写
法来访问xiaoming['favorite-book'];//舒克和贝塔
-
如果属性名以变量形式存储,则必须使用方括号形式
var obj = { a:1, b:2, C:3 }; //属性名用变量存储 var key ='b'; console.log(obj.key); //undefined console.log(obj[key]);//2
-
-
属性的更改
- 直接使用赋值运算符重新对某属性赋值即可更改属性
var obj ={ a:10 } obj.a = 30; //将obj里a的值改成了30 ob].a++; // a = 31
- 直接使用赋值运算符重新对某属性赋值即可更改属性
-
属性的创建
- 如果对象本身没有某个属性值,则用点语法赋值时,这个属性会被创建出来
var obj ={ a:10 }; obj.b = 40; //在obj对象中创建了b:40这个键值对
- 如果对象本身没有某个属性值,则用点语法赋值时,这个属性会被创建出来
-
属性的删除
-
如果要删除某个对象的属性,需要使用delete操作符
var obj = { a:1, b:2 }; delete obj.a;
-
2.对象的方法
-
如果某个属性值是函数,则它也被称为对象的"方法"
varxiaoming = { sayHello:function(){ //sayHello方法 console.log('你好,我是小明,今年12岁,我是个男生'); } }
-
用点方法调用函数
xiaoming.sayHello();
-
方法也是函数,只不过方法是对象的"函数属性",它需要用对象打点调用
3.对象的遍历
- 用 for … in … 循环,可以遍历对象的每个键
4.对象的深浅克隆
- 对象是引用类型值时
- 不能使用
var obj1 = obj2
这样的语法克隆一个对象 - 使用== 或===进行对象的的比较时,比较的是它们是否为内存中的同一个对象,而不是比较值是否相同
- 不能使用
- 浅克隆
- 使用for…in….循环即可实现对象的浅克隆
- 深克隆 ★
-
和数组的类型相似,对象的深克隆需要使用递归
var obj1 = { a:1, b:2, c:[33,44,{ m:55, n:66, p:[77,88] }] }; // 定义一个深克隆函数 function deepClone(o){ // 判断o是对象还是数组,必须先判断是不是数组,因为数组的类型也是对象 if(Array.isArray(o)){ //数组 var result = []; for(var i=0;i < o.length;i++){ result.push(deepClone(o[i])); } }else if(typeof o == 'object'){ // 对象 var result = {}; for (var k in o){ result[k] = deepClone(o[k]) } }else{ //基本类型值 var result = o; } return result; } var obj2 = deepClone(obj1); console.log(obj2); // 测试一下 console.log(obj1 === obj2); //false obj1.c.push(99); console.log(obj2); //obj2是不变的,因为没有'藕断丝连'的现象
-
二、认识函数上下文
-
函数的上下文(this关键字)由调用方式决定; 同一个函数,用不同的形式调用它,则函数的上下文不同
var xiaoming = { nickname:小明, age:12, sayHello = function (){ console..log('我是'+this.nickname+',我'+this.age+'岁了); } };
-
情形1:对象打点调用函数,函数中的this指代这个打点的对象
xiaoming.sayHello();
-
情形2:圆括号直接调用函数,函数中的this指代window对象
var sayHello = xiaoming.sayHello; sayHello();
-
-
function是“运行时上下文"策略
-
函数如果不调用,则不能确定函数的上下文
1.函数的上下文规则 ★
-
规则①
- 对象打点调用它的方法函数,则函数的上下文是这个打点的对象
对象.方法();
-
规则②
- 圆括号直接调用函数,则函数的上下文是window对象
函数();
-
规则③
- 数组(类数组对象)枚举出函数进行调用,上下文是这个数组(类数组对象)
- 什么是类数组对象:所有键名为自然数序列(从0开始),且有length属性的对象
- arguments对象是最常见的类数组对象,它是函数的实参列表
数组[下标]();
- 数组(类数组对象)枚举出函数进行调用,上下文是这个数组(类数组对象)
-
规则④
-
IIFE中的函数,上下文是window对象
(function(){ })();
-
-
规则⑤
- 定时器、延时器调用函数,上下文是window对象
setInterval(函数,时间);
setTimeout(函数,时间);
-
规则⑥
- 事件处理函数的上下文是绑定事件的DOM元素
DOM元素.onclick=function(){ }
- 事件处理函数的上下文是绑定事件的DOM元素
2.call和apply ★
- call和apply能指定函数的上下文
函数.ca11(上下文);
要用逗号罗列参数函数.apply(上下文);
要把参数写到数组中
三、构造函数
1.用new操作符调用函数
-
进行“四步走”
- 1)函数体内会自动创建出一个空白对象
- 2)函数的上下文(this)会指向这个对象
- 3)函数体内的语句会执行
- 4)函数会自动返回上下文对象,即使函数没有return语句
2.类与实例
3.构造函数和’类"
-
Java、C++等是"面向对象”(object-oriented)语言
-
JavaScript是"基于对象”(object-based)语言
-
JavaScript中的构造函数可以类比于OO语言中的"类”,写法的确类似,但和真正OO语言还是有本质不同
四、原型和原型链
1.prototype和原型链查找 ★
- 任何函数都有prototype属性,prototype是"原型"的意思
- prototype属性值是个对象,它默认拥有constructor属性指回函数
- 普通函数来说的prototype属性没有任何用处,而构造函数的prototype属性非常有用
- 构造函数的prototype属性是它的实例的原型
console.log(xiaoming.__proto__ === People.prototype); //true
- 构造函数的prototype属性是它的实例的原型
- 原型链查找
- JavaScript规定:实例可以打点访问它的原型的属性和方法
- hasOwnProperty()方法
- 可以检查对象是否真正“自己拥有"某属性或者方法
- in运算符
- 只能检查某个属性或方法是否可以被对象访问,不能检查是否是自己的属性或方法
2.在prototype上添加方法
- 把方法直接添加到实例身上的缺点:每个实例和每个实例的方法函数都是内存中不同的函数,造成了内存的浪费
- 解决办法: 把方法添加到prototype上
3.原型链的终点
- 万物原型链的终点——Object.prototype
-
代码示例
function People(){ } var xiaoming = new People(); console.log(xiaoming.__proto__.__proto__ === Object.prototype); //true console.log(Object.prototype.__proto__); //null console.log(Object.prototype.hasOwnProperty('hasOwnProperty')); //true
-
数组的原型链
var arr = [22,33,4,555];
console.log(arr.__proto__ === Array.prototype); //true
console.log(arr.__proto__ .__proto__=== Object.prototype); //true
console.log(Array.prototype.hasOwnProperty('push')); //true
4.继承 ★
-
继承描述了两个类之间的"is a kind of"关系,比如学生“是一种”人,所以人类和学生类之间就构成继承关系
-
People是"父类”(或"超类”、"基类”);Student是“子类”(或“派生类”)
-
子类丰富了父类,让类描述得更具体、更细化
-
举例
- 通过原型链实现继承
- 让子类构造函数的prototype指向父类的一个实例:
Student.prototype = new People();
- 让子类构造函数的prototype指向父类的一个实例:
//父类:人类
function People(name,age,sex){
this.name = name;
this.age = age;
this.sex = sex;
}
People.prototype.sayHello = function (){
console.log('你好,我是'+this.name +'我今年'+this.age+'岁了');
}
People.prototype.sleep = function (){
console.log(this.nam+'开始睡觉,zzzz');
}
// 子类:学生
function Student(name,age,sex,school,studentNumber){
this.name = name;
this.age = age;
this.sex = sex;
this.school = school;
this.studentNumber = studentNumber;
}
//关键语句,实现继承
Student.prototype = new People();
Student.prototype.study = function (){
console.log(this.name + '正在学习');
}
Student.prototype.exam = function (){
console.log(this.name+'正在考试,加油!');
}
//实例化
var hanmeimei = new Student('韩梅梅',12,'女','CSDN大学',123456);
hanmeimei.study();
hanmeimei.sayHello();
五、上升到面向对象
- 面向对象的本质: 定义不同的类,让类的实例工作
- 面向对象的优点: 程序编写更清晰、代码结构更严密、使代码更健壮更利于维护
- 面向对象经常会用到的场合: 需要封装和复用性的场合(组件思维)
1.红绿灯小案例
-
使用面向对象编程,就能以"组件化"的思维解决大量红绿灯相互冲突的问题
-
面向对象编程,最重要的就是编写类
-
TrafficLight类
- 属性: 自己的当前颜色color、自己的DOM元素dom
- 方法: 初始化init()、切换颜色changeColor()、绑定事件bindEvent()
-
代码示例:
#box img{ width: 80px ; }
<div id="box" ></div>
//定义红绿灯类,构造函数 function TrafficLight(){ //颜色属性,一开始都是红色 //红色1,黄色2,绿色3 this.color = 1; //调用自己的初始化方法 this.init(); //绑定监听 this.bindEvent(); } //初始化方法 TrafficLight.prototype.init = function (){ // alert('我是init方法'); //创建自己的DOM this.dom = document.createElement('img'); this.dom.src = this.color+'.jpg'; box.appendChild(this.dom); } // 绑定监听 TrafficLight.prototype.bindEvent = function (){ //备份上下文,这里的this指的是JS实例 var self = this; //当自己的dom被点击时 this.dom.onclick = function (){ // 当被点击时,调用自己的changeColor方法 self.changeColor(); }; } // 改变颜色 TrafficLight.prototype.changeColor = function (){ // 改变自己的color属性,从而有一种"自治"的感觉,自己管理自己不干扰别的红绿灯 this.color++; if(this.color == 4){ this.color = 1; } // 光color属性变化没用,还要更改自己的dom中src属性,才能更换图片 this.dom.src = this.color+'.jpg'; }; // 得到盒子 var box = document.getElementById('box'); // 实例化100个 var count = 100; // 当count-- 为0的时候,判断为false,跳出循环 while(count--){ new TrafficLight(); }
2.炫彩小球小案例
-
Boll类的属性
-
Boll类的方法
- init()初始化方法
- update()更新方法
-
实现多个小球的动画
- 把每个小球实例放到同一个数组中
- [{小球实例},{小球实例},{小球实例},{小球实例}]
- 只需要使用一个定时器,在每一帧遍历每个小球,调用它们的update方法
- 把每个小球实例放到同一个数组中
-
代码示例:
body{ background-color: black; } .ball{ position: absolute; border-radius: 50%; }
//小球类 function Ball(x,y){ //属性x,y表示的是圆心的坐标 this.x = x; this.y = y; //透明的 this.opacity = 1; do{ // 这个小球的x增量和y的增量 this.dX = parseInt(Math.random()*20)-10; this.dY = parseInt(Math.random()*20)-10; }while(this.dX === 0 || this.dY === 0) // 小球的背景颜色 this.color = colorArr[parseInt(Math.random()*colorArr.length)]; // 小球半径 this.r = 20; // 初始化 this.init(); // 把自己推入数组,注意:这里的this不是类本身,而是实例 ballArr.push(this); } Ball.prototype.init = function (){ //创建自己的dom this.dom = document.createElement('div'); this.dom.className = 'ball'; this.dom.style.width = this.r *2 +'px'; this.dom.style.height = this.r *2 +'px'; this.dom.style.left = this.x - this.r+'px'; this.dom.style.top = this.y - this.r+'px'; this.dom.style.backgroundColor = this.color; //上树 document.body.appendChild(this.dom); } // 更新 Ball.prototype.update = function (){ // 位置改变 this.x += this.dX; this.y -= this.dY; // 半径改变 this.r += 0.2; // 透明度改变 this.opacity -= 0.05; this.dom.style.width = this.r *2 +'px'; this.dom.style.height = this.r *2 +'px'; this.dom.style.left = this.x - this.r+'px'; this.dom.style.top = this.y - this.r+'px'; this.dom.style.opacity = this.opacity; // 当透明度小于0,就需要从数组中删除自己,DOM元素也要删除自己 if(this.opacity<0){ //从数组中删除自己 for (var i = 0; i<ballArr.length;i++){ if(ballArr[i] == this){ ballArr.splice(i,1); } //还要删除自己的dom document.body.removeChild(this.dom); } } }; // 把所有的小球实例都放到一个数组中 var ballArr = []; // 初始颜色数组 var colorArr = ['#66CCCC','#CCFFCC','#FF99CC','#FF6666','#CC3399','#ff6600'] // 定时器,负责更新所有的小球实例 setInterval(function (){ //遍历数组 ,调用update方法 for(var i= 0;i<ballArr.length;i++){ ballArr[i].update(); } },20); // 鼠标指针的监听 document.onmousemove = function (e){ //得到鼠标指针的位置 var x = e.clientX; var y = e.clientY; new Ball(x,y); }
六、JS的内置对象
1.包装类
- Number()、String()和Boolean()的实例都是object类型,它们的Primitivevalue属性存储它们的本身值
- new出来的基本类型值可以正常参与运算
- 包装类的目的就是为了让基本类型值可以从它们的构造函数的prototype上获得方法
2.Math对象 ★
-
幂和开方:Math.pow()、Math.sqrt()
-
向上取整和向下取整:Math.ceil()、Math.floor()
-
Math.round()方法: 四舍五入
- 四舍五入到小数点后两位
- 四舍五入到小数点后两位
-
Math.max(): 得到参数列表最大值
- 用Math.max()求数组最大值
-
Math.max()要求参数必须是“罗列出来”,而不能是数组
-
用apply方法,它可以指定函数的上下文,并且以数组的形式传入“零散值”当做函数的参数
var arr = [3,6,9,2]; var max = Math.max.apply(null,arr); console.log(max); // 9
-
- 用Math.max()求数组最大值
-
Math.min(): 得到参数列表最小值
-
Math.random(): 得到0~1之间的小数
- 为了得到[a,b]区间内的整数,可以使用这个公式:
- parseInt(Math.random()*(b-a +1))+a
- 为了得到[a,b]区间内的整数,可以使用这个公式:
3.Date对象 ★
- 使用new Date()即可得到当前时间的日期对象,它是object类型值
- 使用new Date(2023,6,26)即可得到指定日期的日期对象,
- 注意第二个参数表示月份,从0开始算,6表示7月
- 也可以是new Date(‘2023-07-26’)这样的写法
- 使用new Date(2023,6,26)即可得到指定日期的日期对象,
- 日期对象常见方法
- 时间戳
-
时间戳表示1970年1月1日零点整距离某时刻的毫秒数
-
通过getTime()方法或者Date.parse()函数可以将日期对象变为时间戳
-
通过new Date(时间戳)的写法,可以将时间戳变为日期对象
-
七、继承与内置构造函数[拓展]
-
内置构造函数
- JavaScript有很多内置构造函数,比如Array就是数组类型的构造函数,Function就是函数类型的构造函数,Object就是对象类型的构造函数
- 内置构造函数非常有用,所有该类型的方法都是定义在它的内置构造函数的prototype上的,我们可以给这个对象添加新的方法,从而拓展某类型的功能
- Number\String\Boolearn是三个基本类型值的包装类,用new调用它们可以生成"对象"版本的基本类型值
-
内置构造函数的关系
- 任何函数都可以看作是Function"new出来的",包括Object
- 任何函数都可以看作是Function"new出来的",包括Object
-
借助构造函数(也被称为"伪造对象"或"经典继承")
- 优点: 解决了原型中包含引用类型值所带来的问题和子类构造函数不优雅的问题
- 原理: 在子类构造函数的内部调用超类的构造函数,但是要注意使用call()绑定上下文
function People(name,sex,age){ this.name = name; this.sex = sex; this.age = age; this.arr = [22,33,44]; } function Student(name,sex,age,school,sid){ People.call(this,name,sex,age); // 借助构造函数 this.school = school; this.sid = sid; } var xiaoming = new Student('小明','男',12,'CSDN学校',123455); console.log(xiaoming); </script>
-
组合继承(最常用)
- 原理: 借用原型链和借用构造函数的技术组合到一起,也叫伪经典继承
- 缺点: 无论什么情况下,都会调用两次超类的构造函数,一次是在创建子类原型的时候,另一次是在子类构造函数的内部
//父类 function People(name,sex,age){ this.name = name; this.sex = sex; this.age = age; } People.prototype.sayHello = function (){ console.log('你好,我是'+this.name+'今年'+this.age+'岁了'); } People.prototype.sleep = function (){ console.log(this.name+'正在睡觉'); } //子类 function Student(name,sex,age,school,sid){ //借助构造函数 People.call(this,name,sex,age); this.school = school; this.sid = sid; } //实现继承:借助原型链 Student.prototype = new People(); Student.prototype.exam = function (){ console.log(this.name + '正在考试') } Student.prototype.sayHello = function (){ console.log('敬礼!你好,我是'+this.name+'今年'+this.age+'岁了'+this.school+'的学生'); } var xiaoming = new Student('小明','男',12,'CSDN学校',123455); console.log(xiaoming); xiaoming.sayHello(); xiaoming.sleep(); xiaoming.exam();
-
原型式继承
- 认识Object.creat()
- IE9+开始支持Object.create()方法,可以根据指定的对象为原型创建出新对象,不用借助构造函数了
- 示例:
var obj2 = Object.create(obj1);
- 概念: 在没有必要“兴师动众”地创建构造函数,而只是想让新对象与现有对象“类似”的情况下,使用Object.create()即可胜任,称为原型式继承
- 认识Object.creat()
-
寄生式继承
- 编写一个函数,它接收一个参数o,返回以o为原型的新对象p,同时给p添加上预制的新方法
- 寄生式继承就是编写一个函数,它可以“增强对象”,只要把对象传入这个函数,这个函数将以此对象为“基础”创建出新对象,并为新对象赋予新的预置方法
- 在主要考虑对象而不是自定义类型和构造函数的情况下,寄生式继承也是一种有用的模式
- 缺点: 使用寄生式继承来为对象添加函数,会由于不能做到函数复用而降低效率,即"方法没有写到prototype上”
- 编写一个函数,它接收一个参数o,返回以o为原型的新对象p,同时给p添加上预制的新方法
-
寄生组合式继承
- 通过借用构造函数来继承属性
- 通过原型链的混成形式来继承方法
- 基本思路:不必为了指定子类型的原型而调用超类型的构造函数,我们所需要的无非就是超类型原型的一个副本而已。本质上,就是使用寄生式继承来继承超类型的原型,然后再将结果指定给子类型的原型
-
instanceof运算符
- instanceof运算符用来检测"某对象是不是某个类的实例”,比如:xiaoming instanceof Student
- 底层机理:检查Student.prototype属性是否在xiaoming的原型链上(多少层都行,只要在就行)