JavaScript:原型链Ⅰ(proto)

265 阅读5分钟

类和原型

JavaScript也是一门面向语言。面向对象,但它与类无关。

受之前学习java的影响。很长一段时间,我觉得“对象”和“类”绑定在一起的。
但对象的实现源于继承,通过类只是一种方法,另一种是原型。

原型本质上是一个对象

在JavaScript中,所有的对象都有一个属性,叫proto。这是JavaScript引擎内置的,即一个对象的原型。

在控制台随便打印一个对象都可以看到一个灰色的__proto__
下划线是为了防止开发者也自定义一个属性叫proto,将内置的proto覆盖。

proto作用和类一样,实现继承,实现创造对象,但它更简洁

通过原型来继承(prototypal inheritance)

继承
inheritance, one object gets access to the properties and methods of another object. 继承就是一个对象获取另一个对象的属性和方法
只说缺点:通过类来继承太冗长。一个类往往会被被private、protected、public之类的词修饰,proto通过原型继承简洁很多。

尝试改变对象的原型

举个栗子。我们有两个对象,一个叫A,另一个叫B。
A有一个属性叫aProperty,我们可以通过A.aProperty找到它;B有一个属性叫bProperty,我们可以通过B.bProperty找到它。
如果写A.bProperty,当然不行,因为A没有bProperty这个属性。

但如果我们将A的原型指定为B(A.__proto__ = B)。
当我们写A.bProperty,会首先在A自己的属性中去寻找,没有找到。再去他的原型——B中寻找,找到了就可以用了。 不用写成A.__ proto __.bProperty

A.__proto__ = B只是举个栗子,不要真的这么写,下面会解释为什么。

JSE会自动去A原型中寻找,这样我们就实现了继承,通过将A的__proto__指定为B,我们获得了B的属性和方法。

如果指定B的原型为C,我们还可以写A.cProperty——没能在A中找到的属性,去A的原型B中找,没能在B中找到的属性,去C中找...

一层一层往一个对象的原型找找下去,所以有个词语,叫原型链(prototype chain)。

改变proto指向,实现继承

var person = {
    firstname: 'Default',
    lastname: 'Default',
    getFullName: function() {
        return this.firstname + ' ' + this.lastname;  
    }
}
var john = {
    firstname: 'John',
    lastname: 'Doe'
}
console.log(person);
console.log(john);

将john的原型设置为person后

var person = {
    firstname: 'Default',
    lastname: 'Default',
    getFullName: function() {
        return this.firstname + ' ' + this.lastname;  
    }
}
var john = {
    firstname: 'John',
    lastname: 'Doe'
}
john.__proto__ = person;
console.log(john.getFullName());//John Doe //注意:this的指向的是调用它的最近的对象,所以是john
console.log(person);
console.log(john);

展开注释看看john这个对象的__proto__有什么差别

红色箭头指的,叫base object

base object/Object.prototype

之前说所有的对象都有原型(proto),其实不太对,base object是唯一的一个例外。它没有原型。

var a = {};
var b = function(){};
var c = [];

在控制台试试下面的属性

红色箭头所指就是base object,它没有原型了。

解释一下为什么红色箭头所指,不是红色箭头下面那个:a.__proto__是base object,a.__proto__.__proto__被认为是base object上的一个名为__proto__属性,回车,没有找到,所以打印出null,而不是报错.但如果再继续写a.__proto__.__proto__.__proto__,就是再一个空对象(a.__proto__.__proto__)再去找,自然就报错了。

也就是说,对于a这种最普通的对象,往下找一层就是base object

数组和函数这种特别的对象,往下找两层是base object。

这种指向是by reference,不是by value。就是说只有一块内存存着这个base object。所有的object,最最最最底层都是它。用这个例子证明只有一个base object:

a.__proto__.myProperty = 'brynn';
console.log(b.__proto__.__proto__.myProperty)//brynn

通过a在base object上加一个属性(myProperty),通过b,我们拿到了这个属性

Object.prototype里有什么?

可以看到是一些method,比较常用的是toString,这也是为什么在JavaScript中可以一个对象可以很轻松转化为字符串,它从base object找到的这个方法。

所有的对象都可以找到这个方法。

Function.prototype里有什么?

不知道为什么,我的控制台没法把这一部分展开,里面也是一些方法,常用的有call/bind/apply

Array.prototype里有什么?

可以看到是一些属性和方法,所以对于一个数组,在JavaScript中可以轻松的用.push/.pop/.length...

所有的数组对象都可以找到这些方法。(其实只要是数组就可以,相关:<还没写原始类型和自动装箱>)

通过proto继承:extends

A如果想用B的属性和方法,可以将A的proto设为B。

像这样(A.__proto__ = B)

但并不推荐这样做,这也是为什么proto前后都要加两个下划线——我们可能会取一个属性的名字叫proto,但不太可能叫__proto__:并不 希望开发者手动设置一个对象的原型。

因为这会降低效率——假设A和B都是两个普通的对象。A和B都是在prototype chain的第二层,第一层是base object(即a和b的原型都是Object.prototype)。
A如果用到 Object.prototype里的属性或方法,往下找一层就可以找到Object.prototype。

但如果我们将A的原型设为B,那么,A升级了,它现在是原型链的第三层。
如果现在写要用到Object.prototype里的转字符串方法A.toString, 会先从A自己的属性中找,再去A的原型B中找,再去B的原型Object.prototype。终于找到了。

三层,当然会花费更多的时间。 不让开发者手动设置__proto__的目的,就是让每一个对象都尽可能离Object.prototype近一些。

但如果我们需要在A上用B的属性,我们可以用extends。

在es6之前,并没有extends,各大library和framework会自己来实现这个功能。

摘自underscoreJS的extend:

var createAssigner = function(keysFunc, undefinedOnly) {
    return function(obj) {
    var length = arguments.length;
    if (length < 2 || obj == null) return obj;
    for (var index = 1; index < length; index++) {
        var source = arguments[index],
            keys = keysFunc(source),
            l = keys.length;
        for (var i = 0; i < l; i++) {
        var key = keys[i];
        if (!undefinedOnly || obj[key] === void 0) obj[key] = source[key];
        }
    }
    return obj;
    };
};

看不懂上面的代码也没关系,只要知道:

它实现了将B有的,A没有的属性给了A,并且A和B还是同级的关系。

var a = new Number(3);
var b ={
    value:3
};


console.log(a);
    Number {3}
        __proto__:Number
        [[Primitive]]:3
console.log(b); 
    {value:3}
        value:3
        __proto__:Object
        

a是一个特别一点的对象,在prototype chain的第三层,第二层是一个叫Number的proto,最底层才是base object.

b是一个最普通的对象,在prototype chain的第二层;