JavaScript原型与原型链

2017年04月23日Web前端

原型在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后,到底进行了什么操作呢?

  1. 创建(构造)一个全新的对象。
  2. 对新变量执行原型链接。
  3. 新对象会被绑定到函数调用的this上。
  4. 如果函数没有返回其他变量,该函数会自动返回这个新对象。

假如我们多次使用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

三、总结

把以上所有的总结下,就可以得到这张图: