JavaScript原型与原型链
原型在JS中是十分重要的,系统的了解下对深入学习JS将有巨大的帮助。
一、创建对象(构造方式)
我们可以用内置的方法或自定义的方式来实例化一个对象。
var arr = new Array(); // 与字面量[]一致
var str = new String(); // 与字面量''不同,typeof不同
var Person = function() { // 构造方法(变量大写,提示这是构造方法)
this.name = 'aaa';
this.place = 'hangzhou';
this.print = function() {
console.log('hello world!');
};
}
var per = new Person(); // 实例化对象
我们可以通过new来实例化一个个对象,而调用了new后,到底进行了什么操作呢?
- 创建(构造)一个全新的对象。
- 对新变量执行原型链接。
- 新对象会被绑定到函数调用的this上。
- 如果函数没有返回其他变量,该函数会自动返回这个新对象。
假如我们多次使用new去实例化对象的话,得到的对象的属性是不能复用的,这样的话,也就浪费了部分内存,这个缺陷可以用原型方式来解决。
var per1 = new Person();
var per2 = new Person();
per1.print == per2.print; // false,没有共用。
二、原型对象
prototype属性
JS所有的函数都有一个prototype属性,这个属性引用(指向)了一个对象,即原型对象,也简称原型。 紧接着刚才的代码,chrome下控制台输出一下结果:
console.log(Array.prototype); // [Symbol(Symbol.unscopables): Object]
console.log(String.prototype); // String {length: 0, [[PrimitiveValue]]: ""}
console.log(Person.prototype); // Object {}
截图见下:
因为Array和String是内置对象,至于他们的prototype我们先不看。直接看Person的prorotype属性,他直接指向的是一个Object,且含有constructor和__proto__属性。
既然prototype指向的是个对象,那么我们就可以给这个对象添加一些属性。稍调整以上的代码,
var Person = function(name) {
this.name = name;
this.place = 'hangzhou';
this.print = function() {
console.log('hello world!');
};
}
Person.prototype.showName = function() {
console.log(this.name);
};
var per1 = new Person('aaa');
var per2 = new Person('bbb');
per1.showName == per2.showName; // true
我们发现per1和per2都是可以访问到的,且这个方法成了这两个对象的公共方法。
这是因为当我们对对象取点操作时,发现本身并没有这个属性,就会沿着prototype向上查找,即查找Person原型对象上有没有这个属性,然后发现了,并使用了这个方法。
至于区分这个属性是对象的还是原型对象,我们可以使用in和hasOwnProperty()方法来区分。hasOwnProperty只会查询对象本身的属性,而in会遍历所有的属性,包括原型链上的属性。
constructor属性
在刚才的控制台上看到原型对象下有个constructor属性,这个属性是创建构造函数时,指向函数本身的一个属性,控制台输出的话,结果是很明显的。
我们会发现,实例化的对象上也有这个属性,而这个属性也是指向构造函数本身的。
Person.prototype.constructor == per1.constructor; // true
Person.prototype.constructor == Person; // true
原型上的prototype属性是可以被改变的,比如直接给prototype赋值个对象,constructor就会被覆盖了。
Person.prototype = {
showName: function() {
console.log(this.name);
},
print : function() {
console.log('hello world!');
}
};
Person.prototype.constructor; // function Object() { [native code] }
Person.prototype.constructor == Person; // false
所以当用这种方式赋值时,需要手动设置constructor属性为Person。 注:我们可以用instanceof来判断某个对象是否是该构造函数的实例。
__proto__属性
__proto__属性指向函数对象的原型对象,实例化的对象也有这个属性。这个属性会一直继承自原始对象,直至最终的null。
Person.prototype.__proto__ == Object.prototype; // true
Person.prototype.__proto__.__proto__; // null
per1.__proto__ == Person.prototype; // true
三、总结
把以上所有的总结下,就可以得到这张图: