美文网首页
JavaScript原型继承工作原理

JavaScript原型继承工作原理

作者: 星球小霸王 | 来源:发表于2018-03-02 11:06 被阅读0次
    原型继承的定义

    当你阅读关于JS原型继承的解释时,你时常会看到以下这段文字:

    当查找一个对象的属性时,JavaScript 会向上遍历原型链,直到找到给定名称的属性为止。——出自JavaScript秘密花园

    大多数JavaScript的实现用 proto 属性来表示一个对象的原型链。在这篇文章里我们将看到proto与 prototype 的区别何在。

    注:proto 是一个不应在你代码中出现的非正规的用法,这里仅仅用它来解释JavaScript原型继承的工作原理。

    以下代码展示了JS引擎如何查找属性:

    function getProperty(obj, prop) {
        if (obj.hasOwnProperty(prop))
            return obj[prop]
    
        else if (obj.__proto__ !== null)
            return getProperty(obj.__proto__, prop)
    
        else
            return undefined
    }
    

    让我们举一个常见的例子:二维点,拥有二维坐标 x y ,同似拥有一个 print 方法。

    用之前我们说过的原型继承的定义,我们创建一个对象 Point ,拥有三个属性:x,y 和 print。为了能创建一个新的二维点,我们需要创建一个新的对象,让他其中的 proto 属性指向Point :

    var Point = {
        x: 0,
        y: 0,
        print: function () { console.log(this.x, this.y); }
    };
    
    var p = {x: 10, y: 20, __proto__: Point};
    p.print(); // 10 20
    

    JavaScript怪异的原型继承
    令人困惑的是,每个教授原型继承的人都不会给出上面那样的代码,反而会给出下面这样的代码:

    function Point(x, y) {
        this.x = x;
        this.y = y;
    }
    Point.prototype = {
        print: function () { console.log(this.x, this.y); }
    };
    
    var p = new Point(10, 20);
    p.print(); // 10 20
    

    这和说好的不一样啊,这里 Point 变成了函数,然后还有个什么 prototype 的属性,而且有了new 运算符。这他喵的是什么情况?

    new 运算符是如何工作的

    造物者 Brendan Eich 想让JS和传统的面向对象的编程语言差不太多,如Java和C++。在这些语言里,我们采用 new 运算符来给类实例化一个新的对象。所以他在JS里写了一个 new 运算符。

    C++里有用来初始化实例属性的构造函数概念,因此 new 运算符必须针对函数。
    我们需要将对象的方法放到一个地方去,既然我们在用原型语言,我们就把它放到函数的原型属性中去。
    new 运算符接受一个函数 F 及其参数:new F(arguments...)。这一过程分为三步:

    创建类的实例。这步是把一个空的对象的 proto 属性设置为 F.prototype 。
    初始化实例。函数 F 被传入参数并调用,关键字 this 被设定为该实例。
    返回实例。
    现在我们知道了 new 是怎么工作的,我们可以用JS代码实现一下:

    function New (f) {
        var n = { '__proto__': f.prototype }; /*第一步*/
        return function () {
            f.apply(n, arguments);            /*第二步*/
            return n;                         /*第三步*/
        };
    }
    

    一个小小的例子来看一下他的工作状况:

    function Point(x, y) {
        this.x = x;
        this.y = y;
    }
    Point.prototype = {
        print: function () { console.log(this.x, this.y); }
    };
    
    var p1 = new Point(10, 20);
    p1.print(); // 10 20
    console.log(p1 instanceof Point); // true
    
    var p2 = New (Point)(10, 20);
    p2.print(); // 10 20
    console.log(p2 instanceof Point); // true
    

    JavaScript中真正的原型继承

    JS的ECMA规范只允许我们采用 new 运算符来进行原型继承。但是大宗师 Douglas Crockford 却发现了一种可以利用 new 来实现真正的原型继承的方式!他写下了 Object.create 函数如下:

    Object.create = function (parent) {
        function F() {}
        F.prototype = parent;
        return new F();
    };
    

    这看起来蛮奇怪的,但却是相当的简洁:它创建了新的对象,并将其原型设置为你想设置的任意值。如果我们允许使用 proto ,那我们也可以这样写:

    Object.create = function (parent) {
        return { '__proto__': parent };
    };
    

    下面这段代码就是让我们的 Point 采用真正的原型继承:

    var Point = {
        x: 0,
        y: 0,
        print: function () { console.log(this.x, this.y); }
    };
    
    var p = Object.create(Point);
    p.x = 10;
    p.y = 20;
    p.print(); // 10 20
    

    结论
    我们已经了解了JS原型继承是什么,以及JS如何用特定的方式来实现之。然而使用真正的原型继承(如 Object.create 以及 proto)还是存在以下缺点:

    标准性差:proto 不是一个标准用法,甚至是一个不赞成使用的用法。同时原生态的Object.create 和道爷写的原版也不尽相同。
    优化性差: 不论是原生的还是自定义的 Object.create ,其性能都远没有 new 的优化程度高,前者要比后者慢高达10倍。

    相关文章

      网友评论

          本文标题:JavaScript原型继承工作原理

          本文链接:https://www.haomeiwen.com/subject/vzaoxftx.html