实例化和原型
在JavaScript中,使用原型定义的属性和功能会自动应用到对象的实例上,一但进行了定义,原型的属性就会变成实例化对象的属性。所有函数初始化的时候都有一个prototype属性表示原型。
// 定义一个函数
function A() {}
// 在其原型上添加属性
A.prototype.go = function () {return true;};
// 将函数赋值给变量,空函数无返回值
var a1 = A();
console.log(a1); // undefined
// 将函数作为构造器进行实例化赋值给变量
var a2 = new A();
// 变量成为一个实例
console.log(a2); // A {}
// 该实例继承了函数原型上的属性
console.log(a2.go()); // true
使用new操作符将函数作为构造器进行调用时,其上下文会被定义为新对象实例。这就表明,在构造器函数内部,可以使用this参数初始化值。在构造器内的绑定操作优先级会高于在原型上的绑定操作优先级,因为构造器的this上下文指的是实例自身。
// 使用同样的名称为构造器和原型添加属性
function A() {
this.x=1;
}
A.prototype.x = 2;
// 实例继承属性时,构造器本身的属性会覆盖原型上的属性
var a1 = new A();
console.log(a1.x); // 1
原型的改变会影响其构造器已经创建的实例对象。
// 创建构造函数并实例化
function A() {}
var a1 = new A();
// 改变其原型,已经实例化的对象上继承的属性也会被修改
A.prototype.x = 1;
console.log(a1.x);
每个JavaScript对象都有一个隐式属性constructor,该属性引用的是创建该对象的构造器。
function A() {}
var a1 = new A();
A.prototype.x = 1;
// 使用a1.constructor.prototype.x可以访问到原型上的x属性,证明了原型是实时附加在对象上的,所以在创建实力之后,更改原型也会生效。
console.log(a1.constructor.prototype.x); // 1
查找属性时,首先查找对象自身,如果没有找到,再查找构造器的原型。
function A() {
this.x=1;
this.y=2;
}
var a1 = new A();
A.prototype.y=1;
// 实例化的对象a1已经具有了y属性,即使在实例化之后再为构造器的原型添加新的y属性,也不会覆盖实力中的原来的y属性。
console.log(a1.y); // 2
利用constructor属性,可以在不知道原有构造器的情况下,利用已有的实例来创建新的实例。
function A() {}
var a1 = new A();
var a2 = new a1.constructor();
// a1与a2指向的不是同一个实例
console.log(a1 !== a2); // true
疑难陷阱
扩展原生Object.prototype时,所有对象都会接受这些额外的属性。当我们遍历对象的属性时,这些额外的属性也会被遍历。
// 为原生Object.prototype添加keys方法,返回一个属性名组成的数组。
Object.prototype.keys=function () {
var key = [];
for(var p in this){
key.push(p);
}
return key;
};
var obj = {
a:1,
b:2,
c:3
};
// keys方法本身被继承,也成为了实例obj的属性之一
console.log(obj.keys()); // ["a", "b", "c", "keys"]
使用hasOwnProperty()方法可以判断属性是实例自身的,还是继承自原型的
Object.prototype.keys=function () {
var key = [];
for(var p in this){
// 判断属性是来自实例自身还是继承自原型
if(this.hasOwnProperty(p)){
key.push(p);
}
}
return key;
};
var obj = {
a:1,
b:2,
c:3
};
console.log(obj.keys()); // ["a", "b", "c"]