本文深入解析JavaScript中对象与prototype指针的关系,阐述原型链继承的核心原理。通过对比直接赋值与new中间函数的实现方式,详细剖析constructor属性的修正、实例属性覆盖机制以及Object.getPrototypeOf的正确用法,帮助开发者掌握正确的继承模式并避免常见陷阱。
JavaScript对象与原型基础
在JavaScript中,对象通常由一个指向prototype的指针以及自身的属性列表构成。JavaScript在创建对象时遵循写时复制(Copy-on-Write)的理念。
只有构造函数具备prototype属性,而原型链继承的本质是创建一个新的指针,使其指向该构造函数的prototype属性。
prototype属性之所以特殊,是由JavaScript读取属性时的遍历机制决定的。从本质上看,它仅仅是一个普通的指针。
内置构造函数与原型链
常见的内置构造函数包括:
1. Object 2. Function 3. Array 4. Date 5. String
接下来通过具体示例进行说明。
//每个function都有一个默认的属性prototype,而这个prototype的constructor默认指向这个函数
//注意Person.constructor 不等于 Person.prototype.constructor. Function实例自带constructor属性
functionPerson(name){
this.name = name;
};
Person.prototype.getName =function(){
returnthis.name;
};
var p =newPerson("ZhangSan");
console.log(Person.prototype.constructor===Person);// true
console.log(p.constructor===Person);// true ,这是因为p本身不包含constructor属性,所以这里其实调用的是Person.prototype.constructor
原型继承的目标与矛盾
我们的目标主要包含两点:
1. 表明Person类继承自Animal类
2. 表明p2是Person类的实例
通过修改prototype属性的指向,使Person能够获取Animal中prototype属性所包含的方法,从而实现Person对Animal的继承(即人是野兽)。
functionPerson(name){
this.name = name;
};
Person.prototype.getName =function(){
returnthis.name;
};
var p1 =newPerson("ZhangSan");
console.log(p.constructor===Person);// true
console.log(Person.prototype.constructor===Person);// true
functionAnimal(){}
Person.prototype =newAnimal();//之所以不采用Person.prototype = Animal.prototype,是因为new 还有其他功能,最后总结。
var p2 =newPerson("ZhangSan");
//(p2 -> Person.prototype -> Animal.prototype, 所以p2.constructor其实就是Animal.prototype.constructor)
console.log(p2.constructor===Person);// 输出为false ,但我们的本意是要这里为true的,表明p2是Person的实例。此时目的1达到了,目的2没达到。
如果采用如下修正方式:
Person.prototype = new Animal();
Person.prototype.constructor = Person;
此时p2.constructor确实指向了Person,表明p2是Person类的实例,但引发了新的问题:目的2虽然达成,但目的1未能实现。
目的1和目的2在此时相互矛盾,因为此时的prototype同时表达了两个冲突的含义:
1. 表示父类是谁
2. 作为自身实例的原型进行复制
因此,我们不能直接使用prototype属性来判断父类,而应使用getPrototypeOf()方法来确认父类身份。
Person.prototype =newAnimal();
Person.prototype.constructor=Person;
var p2 =newPerson("ZhangSan");
p2.constructor//显示 function Person() {}
Object.getPrototypeOf(Person.prototype).constructor//显示 function Animal() {}
这样将两个概念分离,通过hasOwnProperty()方法,可以清晰地区分访问的是实例属性还是原型属性。
new运算符与this指向
new运算符执行了哪些操作?
当执行代码var p = new Person()时,new运算符执行以下操作:
创建一个空白对象
创建一个指向Person.prototype的指针
将该对象通过this关键字传递给构造函数并执行构造函数。
具体而言,在以下代码中:
Person.prototype.getName =function(){}
如果我们通过:
var person =newPerson();
其实类似于
var person =newObject();
person.getName =Person.prototype.getName;
因此,通过person.getName()调用方法时,this指向的是新创建的对象,而非prototype对象。
原型扩展与Function.prototype
这在为现有函数添加新功能时会用到,我们可以这样扩展现有方法:
//function myFunc 的写法基本上等于 var myFunc = new Function();
function myFunc (){
}
myFunc =function(func){
//可以在这里做点其他事情
returnfunction(){
//可以在这里做点其他事情
return func.apply(this,arguments);
}
}(myFunc)
也可以在Function.prototype方法中直接通过this访问上述代码中myFunc所指向的对象。
function myFunc (){
}
if(!Function.prototype.extend){
Function.prototype.extend =function(){
var func =this;
returnfunction(){
func.apply(this,arguments);
}
}
};
var myFunc = myFunc.extend();
正确的继承实现方案
总结
如果采用Person.prototype = Animal.prototype来表示Person继承自Animal,instanceof方法同样会显示p也是Animal的实例,返回true。
不采用此方法的原因主要有两点:
1. new创建了一个新对象,从而避免了在设置Person.prototype.constructor = Person时导致Animal.prototype.constructor的值变为Person,而是动态给这个新创建的对象一个constructor实例属性。这样实例上的属性constructor就覆盖了Animal.prototype.constructor,使得Person.prototype.constructor和Animal.prototype.constructor分开。
2. Animal自身的this对象的属性无法传递给Person。
但如果像下面这样直接调用构造函数,可能会失败或产生其他影响。
Person.prototype =newAnimal();
为了避免这种情况,我们引入了一个中间函数。因此正确的做法应该是:
Person.prototype =(funtion(){
function F(){};
F.prototype =Animal.prototype
returnnew F();
})()
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,同时也希望多多支持本站!