第一问:你知道new操作符的实现原理吗?描述下
通过new创建对象经历4个步骤:
- 1、创建一个新对象
- 2、将构造函数的作用域赋给新对象(因此this指向了这个新对象)
- 3、执行构造函数中的代码(为这个新对象添加属性);
- 4、返回新对象。
function newFunc (name) {
var o = {};
o.__proto__ = Person.prototype;//绑定Person的原型
Person.call(o, name);
return o;
}
第二问:请问下面代码输出什么?
function Person(){}
var p1 = new Person()
console.log(p1.constructor)
Person.prototype = {
name:"小红"
}
var p2 = new Person()
console.log(p2.constructor)
答案:
function Person(){}
var p1 = new Person()
console.log(p1.constructor) // [Function: Person]
Person.prototype = {
name:"小红"
}
var p2 = new Person()
console.log(p2.constructor) [Function: Object]
第三问:为什么输出的两个constructor不相同
无论什么时候,只要创建了一个新函数,就会根据一组特定的规则,为该函数创建一个prototype属性,这个属性指向函数的原型对象。在默认情况下,所有原型对象都会自动获得一个constructor属性,这个属性指向了prototype所在的函数。
上面的体题中
打印p1.constructor
时,p1没有constructor
属性,于是就往原型链查找,就在Person.prototype上找到了有constructor
,而这个constructor
指向构造函数Person,因此,打印出[Function: Person]
。
而到p2时,Person.prototype
的值已经被修改成{
name:"小红"
},发现这个值里已经没有constructor,但是prototype上必须有constructor,所以它就自己创建了constructor,并且默认指向Object.,所以打印[Function: Object]
。
第四问:你能介绍下原型链继承吗?
// 实现原型链的一种基本模式
function SuperType(){
this.property = true;
}
SuperType.prototype.getSuperValue = function(){
return this.property;
};
function SubType(){
this.subproperty = false;
}
// 继承,用 SuperType 类型的一个实例来重写 SubType 类型的原型对象
SubType.prototype = new SuperType();
SubType.prototype.getSubValue = function(){
return this.subproperty;
};
var instance = new SubType();
alert(instance.getSuperValue()); // true
其中,SubType 继承了 SuperType,而继承是通过创建 SuperType 的实例,并将该实例赋值给 SubType 的原型实现的。
实现的本质是重写子类型的原型对象,代之以一个新类型的实例。子类型的新原型对象中有一个内部属性 Prototype
指向了 SuperType 的原型,还有一个从 SuperType 原型中继承过来的属性 constructor 指向了 SuperType 构造函数。
最终的原型链是这样的:instance 指向 SubType 的原型,SubType 的原型又指向 SuperType 的原型,SuperType 的原型又指向 Object 的原型(所有函数的默认原型都是 Object 的实例,因此默认原型都会包含一个内部指针,指向 Object.prototype)
原型链继承的缺点:
1、在通过原型来实现继承时,原型实际上会变成另一个类型的实例。于是,原先的实例属性也就顺理成章地变成了现在的原型属性,并且会被所有的实例共享。这样理解:在超类型构造函数中定义的引用类型值的实例属性,会在子类型原型上变成原型属性被所有子类型实例所共享
2、在创建子类型的实例时,不能向超类型的构造函数中传递参数
第五问:既然原型链继承有以上缺点,那有没有解决方法呢?
有,就是借用构造函数继承
借用构造函数继承(也称伪造对象或经典继承)
// 在子类型构造函数的内部调用超类型构造函数;使用 apply() 或 call() 方法将父对象的构造函数绑定在子对象上
function SuperType(){
// 定义引用类型值属性
this.colors = ["red","green","blue"];
}
function SubType(){
// 继承 SuperType,在这里还可以给超类型构造函数传参
SuperType.call(this);
}
var instance1 = new SubType();
instance1.colors.push("purple");
alert(instance1.colors); // "red,green,blue,purple"
var instance2 = new SubType();
alert(instance2.colors); // "red,green,blue"
通过使用 apply() 或 call() 方法,我们实际上是在将要创建的 SubType 实例的环境下调用了 SuperType 构造函数。这样一来,就会在新 SubType 对象上执行 SuperType() 函数中定义的所有对象初始化代码。结果 SubType 的每个实例就都会具有自己的 colors 属性的副本了
。
借用构造函数的优点是解决了原型链实现继承存在的两个问题。
但是一波已平,一波又起
借用构造函数的缺点是方法都在构造函数中定义,因此函数复用就无法实现了。而且,在超类型的原型中定义的方法,对子类型而言也是不可见的,结果所有类型都只能使用构造函数模式。