Prototype:js继承机制的设计思想就是,原型对象的所有属性和方法,都能被实例对象共享,不仅节省了内存,还体现了实例对象之间的联系。
JS中规定,每一个函数都有一个prototype属性,指向一个对象。
我们来看下下边代码:
function test(){}
console.log(test.prototype) //{constructor: ƒ}
打印结果:
上边的代码,函数test默认具有prototype属性,指向一个对象。
对于普通函数来说,该属性基本无用,但是对于构造函数来说,它生成实例的时候,该属性会自动成为实例对象的原型。
function Test(){
this.name = "lxc";
}
Test.prototype.age = 20;
var test1 = new Test();
var test2 = new Test();
console.log(test1.age); //20
console.log(test2.age); // 20
在Test构造函数的原型上添加属性age,两个实例对象都共享了该属性,当实例对象本身没有某个属性或方法的时候,它会到原型上边找此属性和方法。
总结下:
原型对象的作用,就是定义所有实例对象共享的属性和方法。这也是它被称为原型对象的原因,而实例对象可以视作从原型对象衍生出来的子对象。
js规定,所有对象都有自己的原型对象。一方面,任何一个对象,都可以充当其他对象的原型;另一方面,由于原型对象也是对象,所以它也有自己的原型。因此,就会形成一个“原型链”,对象到原型,在到原型的原型··· ···
如果层层上溯,所有对象的原型最终都可以上溯到Object.prototype,既Object构造函数的prototype属性。也就是说所有的对象都继承了Object.prototype的属性。这就是所有对象都有valueOf和toString方法的原因,因为都是从Object.prototype继承的。
注意:原型链的尽头,也就是Object.prototype的原型是null。null没有任何属性和方法,没有自己的原型。
Object.getPrototype(Object.prototype)//null
Object.getPrototypeOf()返回参数对象的原型。
读取对象的某个属性时,js引擎先寻找对象本身的属性,如果找不到,就到它的原型上边找,如果还找不到,就到原型的原型上边找,如果直到最顶层Object.prototype还是找不到,则返回undefined。如果对象自身和它的原型,都定义了一个同名属性,那么优先读取对象自身的属性。
注意,一级级向上,在整个原型链上找某个属性,对性能是有影响的,‘所寻找的属性在越上层的原型对象,对性能的影响越大。如果寻找某个不存在的属性,将会便利整个原型链。
eg:让构造函数的原型指向数组,那么实例对象可以调用数组的方法。
function Test(){
}
Test.prototype = new Array();
Test.prototype.constructor = Test;
var test = new Test();
test.push(1,2,3,4)
console.log(test)//[1,2,3,4]
test instanceof Array // true
上边代码,test是构造函数Test的实例对象,构造函数的原型指向了一个数组的实例,使得test可以调用数组的方法,最后一行instanceof表达式,用来比较一个对象是否为某个构造函数的实例。
prototype对象有一个constructor属性,默认指向prototype对象所在的构造函数。
function Test(){
}
var test = new Test();
console.log(Test.prototype.constructor === Test)//true
由于constructor属性定义在prototype对象上边,意味着可以被所有实例对象继承。
function Test(){
}
var test = new Test();
console.log(Test.prototype.constructor === Test)//true
console.log(test.constructor === Test.prototype.constructor)//true
console.log(test.hasOwnProperty("constructor"))//false
constructor属性可以判断到底是哪一个构造函数产生的。
function Test(){
}
Test.prototype.age = 20;
var test = new Test();
var newTest = new test.constructor();
console.log(newTest.age)//20
上边代码,test是Test构造函数的实例对象,因为test.constructor指向Test构造函数,所以我们利用这一点可创建一个新的实例对象。
下边我们来封装一个调用实例方法来创建另一个实例方法:
function Test(){
}
Test.prototype.myName = "lxc";
Test.prototype.createCopy = function(){
return new this.constructor()
}
var test = new Test();
var newTest = test.createCopy();
console.log(newTest.myName) // "lxc"
上边代码,我们在Test构造函数的原型上封装了一个方法,里边this指向调用createCopy方法的实例,实现了调用实例对象的属性来创建另外一个实例对象。
如果将修改了原型对象,一般会同时把原型上边的constructor属性也修改了,防止引用时出错。
function Test(){
}
Test.prototype = {
name:"lxc"
};
var test = new Test();
console.log(Test.prototype.constructor === Test) // false
console.log(Test.prototype.constructor === Object) // true
上边代码,我们把Test构造函数的原型改成一个对象,在调用constructor时,他不会指向Test了,而是指向一个对象,因为普通对象的构造函数是Object。
所以,当我们现在该原型上边加方法的时候,不要去覆盖原型,而是用点的形式添加:
function Test(){}
Test.prototype.say = function(){
alert("我的名字叫lxc")
}
var test = new Test();
test.say()//我的名字叫lxc
instanceof运算符返回一个布尔值,表示对象是否为某个构造函数的实例。
var date = new Date();
console.log(date instanceof Date) //true
利用instanceof运算符,还可以巧妙地解决,调用构造函数时,忘了加new命令的问题:
function Test(){
if(this instanceof Test){
this.name = "lxc";
this.age = 20;
}else{
return new Test();
}
}
var test = Test();
console.log(test.name) //lxc
上边的代码使用instanceof运算符,在函数体内判断this关键字是否为构造函数Test的实例,如果不是,就表明忘了加new命令了。
让构造函数继承另一个构造函数,是非常常见的需求。这可以分成三步实现。
第一步:是在子类的构造函数中,调用父类的构造函数。
第二部:让子类的原型指向父类的原型。
第三部:子类构造函数原型的constructor属性指向子类构造函数。(如果不手动修改,子类构造函数原型的constructor会指向父类Father,也就是说子类的实例对象的构造函数会发生改变)
function Father(){
this.name = "lxc";
this.age = 20;
}
Father.prototype.say = "hehe";
var father = new Father();
function Son(){
this.a = "1";
Father.call(this);
}
Son.prototype = Object.create(Father.prototype)
Son.prototype.constructor = Son;
Son.prototype.method = fucntion(){}
var son = new Son();
console.log(son.say)//"hehe"
上边代码,在子类构造函数中,通过call来借用父类的构造函数中的属性,然后让子类的原型赋值为Object.create(Father.prototype),而不是直接等于Father.prototype,否则后边两行的操作对父类构造函数有影响,会把Father.prototype一起改掉。。。
js中不提供多重继承功能,既不允许一个对象同时继承多个对象。但是可以通过变通方法,实现这个功能:
function Father1(){
this.name = "lxc";
this.age = 20;
}
Father1.prototype.say = function(){
console.log("呵呵")
}
// var father1 = new Father1();
function Father2(){
this.height = "170cm";
}
Father2.prototype.weight = function(){
console.log("60kg")
}
// var father2 = new Father2();
function Son(){
Father1.call(this);
Father2.call(this);
}
Son.prototype = Object.create(Father1.prototype);
Object.assign(Son.prototype,Father2.prototype);
Son.prototype.constructor = Son;
var son = new Son();
son.say() // "呵呵"
son.weight() // "60kg"
上边代码,子类Son同时继承了父类Fahter1和Father2,跟构造函数的继承差不多,唯一的区别在于用到了Object.assign()方法,浅层拷贝对象;此方法在前几篇文章中介绍过。。。
在ES6中新增了一个方法,new.target ,判断是否使用new来创建一个构造函数,成立则返回该构造函数本身,否则返回undefined。
function Person(name,age){
if(new.target === 'undefined'){
throw new Error('must return new')
}
this.name = name;
this.age = age;
}
var person = new Person('lxc',20)