一、构造函数和原型
1.1 概述
在ES6之前,JS中并没有引入类的概念
ES6:ECMAScript 6.0,2015.06发版,但是目前浏览器的JavaScript是ES5版本,大多数高版本的浏览器也支持ES6,不过只实现了ES6的部分特性和功能
在ES6之前,对象不是基于类创建的,而是用一种称为构建函数的特殊函数来定义对象和他们的特征
创建对象的三种方式:
- 对象字面量
- new Object()
- 自定义构造函数
//1.利用对象字面量 var obj1={} //2.利用new Object()创建对象 var obj2=new Object() //3.利用构造函数创建对象 function Star(uname, age){ this.uname=uname; this.age=age; this.sing=function(){ console.log('我会唱歌'); } var ldh = new Star('刘德华', 18);
1.2 构造函数
构造函数 是一种特殊的函数,主要用来初始化对象,即为对象成员变量赋初始值,他总是和new一起使用,我们可以把对象中一些公共的属性和方法抽取出来,然后封装到这个函数里面
new 在执行的时候会做四件事情:
- 在内存中创建一个新的空对象
- 让this指向这个新的对象
- 执行构造函数里面的代码,给这个新对象添加属性和方法
- 返回这个新对象(所以构造函数里面不需要return)
实例成员和静态成员
实例成员:构造函数内部通过 this 添加的成员,uname age sing就是实例成员,只能通过实例化的对象来访问
静态成员:在构造函数本身上添加的成员,静态函数只能通过构造函数来访问,不能通过对象来访问
1.3 构造函数的问题
对同一个构造函数创建不同的实例对象,这时针对相同的方法,内存中会针对不同的对象单独分配不同的内存控件来存放同一个函数,这样就会消耗大量的内存空间,我们希望针对相同的方法,只需要开辟出一个内存
1.4 构造函数的原型 prototype
构造函数通过原型分配的函数是所有对象所共享的
JavaScript规定,每一个构造函数都有一个prototype属性,指向另外一个对象,注意这个prototype就是一个对象,这个对象的所有属性和方法,都会被构造函数所拥有
我们可以吧那些不变的方法,直接定义在prototype对象上,这样所有的对象的实例就可以共享这些方法
1.原型是什么?
一个对象,我们也称为prototype为原型对象,他是每一个构造函数都有的对象
2.原型的作用是什么?
共享方法
注意!:一般情况下,我们的公共属性定义到构造函数里面,公共的方法我们放到原型对象身上
1.5 对象原型__proto__
对象都会有一个属性__proto__指向构造函数的prototype原型对象,之所以我们对象可以使用构造函数prototype原型对象的属性和方法,就是因为对象有__proto__原型的存在
- __proto__对象原型和原型对象prototype是等价的
- __proto__对象原型的意义就在于为对象的查找机制提供一个方向或者说一条线路,但是他是一个非标准书写,因此实际开发中,不可以使用这个属性,他只是内部指向原型对象prototype
方法的查找规则:
首先看看实例对象身上是否有这个方法,如果有就执行这个对象身上的方法,如果没有这个方法,因为有__proto__的存在,就去构造函数原型对象prototype身上去查找这个方法
1.6 constructor构造函数
对象原型(__proto__)和构造函数原型对象(prototype)里面都有一个属性constructor属性,constructor我们称为构造函数,因为它指回构造函数本身
function Star(uname,age){ this.uname = uname; this.age = age; } Star.prototype.sing = function(){ console.log('我会唱歌'); }
Star.prototype.movie = function(){
console.log('我会演电影');
}
var ldh = new Star('刘德华',18);
console.log(Star.prototype.constructor);
console.log(ldh.__proto__.constructor);
//以上两行会输出以下内容(构造函数本身)
//如果通过下面对象赋值的方式来给原型赋值,就会把原来的原型中的constructor覆盖掉,这就需要我们手动指回原来的构造函数
Star.prototype = {
constructor: Star, //让原型对象重新指向原来的构造函数
sing: function(){
console.log('我会唱歌');
},
movie: function(){
console.log('我会演电影');
}
}
再次打印的结果就指向的不是原来的Star构造函数了,要解决这个问题,只需要在上面的代码中加入
constructor:Star,
constructor主要用于记录该对象引用于哪个构造函数(对象是通过哪个构造函数创建出来的),他可以让原型对象重新指向原来的构造函数
比如上面的例子中,对象ldh是通过构造函数Star创建出来的
1.7 构造函数、实例、原型对象三者之间的关系
1.8 原型链
1.9 JS的成员查找机制
1.10 原型对象的this指向
//构造函数 function Star(uname,age){ this.uname = uname; this.age = age; } //原型对象函数 Star.prototype.sing = function(){ console.log('我会唱歌') } var ldh = new Star('刘德华', 18) ldh.sing();
1. 在构造函数中,里面的this指向的是对象实例
2.原型对象函数里面的this指向的是实例对象(一般情况下this指向它的调用者)
1.11 扩展内置对象
可以通过原型对象,对原来的内置对象进行扩展自定义的方法,比如给数组增加自定义求偶数和的功能
Array.prototype.sum = function(){ var sum = 0; for(var i=0;i<this.length;i++){ sum += this[i]; } return sum; }
注意:数组和字符串内置对象不能给原型对象覆盖操作Array.prototype = {},只能是Array.prototype.XXX= function(){}的方式
(如果采用对象赋值的形式追加自定义的方法,会覆盖掉原来的方法)
二 继承
ES6之前并没有提供extends 继承,我们可以通过构造函数 + 原型对象模拟实现继承,被称为组合继承
2.1 call()
调用这个函数,并且修改函数运行时的this指向
fun.call(thisArg, arg1, arg2, ...)
thisArg:当前调用函数this的指向对象
arg1, arg2:传递的其他参数
function fn(x,y) { console.log('我想喝手磨咖啡');
console.log(this); //函数未被调用的时候,函数中的this指向的是window,这一行代码会输出 window }
//调用函数而方式: //1. fn(); //2. 使用call()调用函数 fn.call();
//3. 使用call()改变函数的 this 指向
var o = {
name: 'andy'
};
fn.call(o,1,2); //此时函数中的 this 就指向了 o 这个对象,第一个参数的作用就是改变this的指向,后面两个参数依次传递给x,y
2.2 借用构造函数继承父类型属性
核心原理:通过call()把父类型的this指向子类型的this,这样就可以实现子类型继承父类型的属性,使得子构造函数也拥有了父构造函数的一些属性
//1. 父构造函数 function Father(uname,age){ //this 指向父构造函数的对象实例 this.uname = uname; this.age = age; } //2. 子构造函数 function Father(uname,age){ //this 指向子构造函数的对象实例 //下面的代码通过调用Father构造函数,再使用call改变Father函数中的this指向,指向了子构造函数中的this Father.call(this, uname, age); }
2.3 借用原型对象继承方法
//1. 父构造函数 function Father(uname,age){ //this 指向父构造函数的对象实例 this.uname = uname; this.age = age; } Father.prototype.money = function(){ console.log('我有10000') } //2. 子构造函数 function Father(uname,age){ //this 指向子构造函数的对象实例 //下面的代码通过调用Father构造函数,再使用call改变Father函数中的this指向,指向了子构造函数中的this Father.call(this, uname, age); this.score = score; } // Son.prototype = Father.prototype; 这样直接赋值会有问题,如果修改了子原型对象,父原型对象也会跟着一起变化
Son.prototype = new Father();
//如果利用对象的形式修改了原型对象,别忘了利用constructor指回原来的构造函数
Son.prototype.constructor = Son; Son.prototype.exam = function() { console.log('孩子要考试'); } var son = new Son('刘德华',18, 100); console.log(son); console.log(Father.prototype)
如果想要继承父构造函数中的 money方法,我们可以通过
Son.prototype = Father.prototype;
这一行代码,来把父构造函数中的方法继承过来,但是这样做的结果就是,如果在子构造函数的原型对象中添加一个 exam 方法,会发现,父原型对象中也有了 exam这个方法,这是我们不希望看到的
在下图中,也就是我们把Son原型对象纸箱课 Father原型对象,修改子原型对象也就修改了父原型对象
要想避免上面那种问题,我们通过创建一个 父构造函数的实例对象
Son.prototype = new Father();
将Son原型对象指向了 Father实例对象,通过Father实例对象,我们可以通过实例原型访问到Father原型对象中的 money方法,这样Son原型对象也可以访问到Father原型对象中的money方法,同时我们在Son原型对象中添加的exam方法也不会出现在Father原型对象中
但是当我们执行了这一行代码后,由于 new Father() 创建的是一个实例对象,即他也是一个对象,之前说过,如果通过对象的形式给原型对象添加方法,会覆盖掉原来原型对象中的其他方法,包括构造函数,因此我们需要再将Son原型对象中的构造函数的指向更改回来,将他指回子构造函数
Son.prototype.constructor = Son;
三、ES5中的新增方法
3.1 数组方法
forEach(), map(), filter(), some(), every();
array.forEach(function(currentValue, index, arr){}); currentValue: 数组当前项的值 index:数组当前项的索引 arr:数组对象本身
// filter()方法创建一个新的数组,新数组中的元素是通过检查指定数组中符合条件的所有元素,主要用于筛选数组,直接返回一个新数组
array.filter(function(currentValue, index, arr));
currentValue: 数组当前项的值
index:数组当前项的索引
arr:数组对象本身
// some()方法用于检测数组中是否满足指定条件,查找数组中是否有满足条件的元素
//返回值是布尔值,如果查找多返回true,否则返回false
// 如果找到第一个满足条件的元素,则终止循环,不再继续查找
array.some(function(currentValue, index, arr)); currentValue: 数组当前项的值 index:数组当前项的索引 arr:数组对象本身
3.2 字符串方法
trim(): 去除字符串联两端的空格
3.3 对象方法
//获取对象自身所有的属性 Object.keys(obj)
- 效果类似for...in...
- 返回一个由属性名组成的数组
Object.defineProperty(obj, prop, descriptor)
obj:必需,目标对象
prop:必需,需定义或修改的属性的名字
descriptor:必需,目标属性所拥有的特性
Object.defineProperty(obj, prop, descriptor)
obj:必需,目标对象
prop:必需,需定义或修改的属性的名字
descriptor:必需,目标属性所拥有的特性,以对象的形式书写{}
对象的内容可设置为以下内容: