深度剖析前端JavaScript中的原型(JS的工具原型)

      
深度剖析前端JavaScript中的原型(JS的工具原型)                   这张图片有点劝退了,哈哈哈~ 
深度剖析前端JavaScript中的原型(JS的工具原型)

 

 

   

通过原型机制,JavaScript 中的工具从其他工具继续功效特征;这种继续机制与经典的面向工具编程语言的继续机制差异。本文将探讨这些差异,注释原型链若何事情,并领会若何通过 prototype 属性向已有的组织器添加方式

 

  

 

基于原型的语言?

 

      JavaScript 常被形貌为一种基于原型的语言 (prototype-based language)——每个工具拥有一个原型工具,工具以其原型为模板、从原型继续方式和属性。原型工具也可能拥有原型,并从中继续方式和属性,一层一层、以此类推。这种关系常被称为原型链 (prototype chain),它注释了为何一个工具会拥有界说在其他工具中的属性和方式。

 

准确地说,这些属性和方式界说在Object的组织器函数(constructor functions)之上的prototype属性上,而非工具实例自己。

 

在传统的 OOP 中,首先界说“类”,今后确立工具实例时,类中界说的所有属性和方式都被复制到实例中。在 JavaScript 中并不云云复制——而是在工具实例和它的组织器之间确立一个链接(它是__proto__属性,是从组织函数的prototype属性派生的),之后通过上溯原型链,在组织器中找到这些属性和方式。

 

注重: 明白工具的原型(可以通过Object.getPrototypeOf(obj)或者已被弃用的__proto__属性获得)与组织函数的prototype属性之间的区别是很主要的。前者是每个实例上都有的属性,后者是组织函数的属性。也就是说,Object.getPrototypeOf(new Foobar())Foobar.prototype指向着同一个工具。

 

以上形貌很抽象;我们先看一个例子。

 

 

      在javascript中,函数可以有属性。 每个函数都有一个特殊的属性叫作原型(prototype ,正如下面所展示的。请注重,下面的代码是自力的一段(在网页中没有其他代码的情况下,这段代码是平安的)。为了最好的学习体验,你最好打开一个控制台 (在Chrome和Firefox中,可以按Ctrl+Shift+I来打开)切换到”控制台” 选项卡, 复制粘贴下面的JavaScript代码,然后按回车来运行

    function doSomething(){}
    console.log( doSomething.prototype );
    // It does not matter how you declare the function, a
    //  function in javascript will always have a default
    //  prototype property.
    var doSomething = function(){}; 
    console.log( doSomething.prototype );

 

正如上面所看到的, doSomething 函数有一个默认的原型属性,它在控制台上面泛起了出来. 运行这段代码之后,控制台上面应该泛起了像这样的一个工具.

    {
        constructor: ƒ doSomething(),
        __proto__: {
            constructor: ƒ Object(),
            hasOwnProperty: ƒ hasOwnProperty(),
            isPrototypeOf: ƒ isPrototypeOf(),
            propertyIsEnumerable: ƒ propertyIsEnumerable(),
            toLocaleString: ƒ toLocaleString(),
            toString: ƒ toString(),
            valueOf: ƒ valueOf()
        }
    }

 

现在,我们可以添加一些属性到 doSomething 的原型上面,如下所示.

    function doSomething(){}
    doSomething.prototype.foo = "bar";
    console.log( doSomething.prototype );

 

效果:

    {
        foo: "bar",
        constructor: ƒ doSomething(),
        __proto__: {
            constructor: ƒ Object(),
            hasOwnProperty: ƒ hasOwnProperty(),
            isPrototypeOf: ƒ isPrototypeOf(),
            propertyIsEnumerable: ƒ propertyIsEnumerable(),
            toLocaleString: ƒ toLocaleString(),
            toString: ƒ toString(),
            valueOf: ƒ valueOf()
        }
    }

 然后,我们可以使用 new 运算符来在现在的这个原型基础之上,确立一个 doSomething 的实例。准确使用 new 运算符的方式就是在正常挪用函数时,在函数名的前面加上一个 new 前缀. 通过这种方式,在挪用函数前加一个 new ,它就会返回一个这个函数的实例化工具. 然后,就可以在这个工具上面添加一些属性:

    function doSomething(){}
    doSomething.prototype.foo = "bar"; // add a property onto the prototype
    var doSomeInstancing = new doSomething();
    doSomeInstancing.prop = "some value"; // add a property onto the object
    console.log( doSomeInstancing );

 

效果:

    {
        prop: "some value",
        __proto__: {
            foo: "bar",
            constructor: ƒ doSomething(),
            __proto__: {
                constructor: ƒ Object(),
                hasOwnProperty: ƒ hasOwnProperty(),
                isPrototypeOf: ƒ isPrototypeOf(),
                propertyIsEnumerable: ƒ propertyIsEnumerable(),
                toLocaleString: ƒ toLocaleString(),
                toString: ƒ toString(),
                valueOf: ƒ valueOf()
            }
        }
    }

 就像上面看到的doSomeInstancing __proto__ 属性就是doSomething.prototype. 然则这又有什么用呢? 

 

好吧,当你接见 doSomeInstancing 的一个属性, 浏览器首先查找 doSomeInstancing 是否有这个属性. 若是 doSomeInstancing 没有这个属性, 然后浏览器就会在 doSomeInstancing __proto__ 中查找这个属性(也就是 doSomething.prototype). 若是 doSomeInstancing 的 __proto__ 有这个属性, 那么 doSomeInstancing 的 __proto__ 上的这个属性就会被使用. 否则, 若是 doSomeInstancing 的 __proto__ 没有这个属性, 浏览器就会去查找 doSomeInstancing 的 __proto__ __proto__ ,看它是否有这个属性. 

 

默认情况下, 所有函数的原型属性的 __proto__ 就是 window.Object.prototype. 以是 doSomeInstancing 的 __proto__ __proto__ (也就是 doSomething.prototype 的 __proto__ (也就是 Object.prototype)) 会被查找是否有这个属性. 若是没有在它内里找到这个属性, 然后就会在 doSomeInstancing 的 __proto__ __proto__ __proto__ 内里查找. 然而这有一个问题: doSomeInstancing 的 __proto__ __proto__ __proto__ 不存在. 最后, 原型链上面的所有的 __proto__ 都被找完了, 浏览器所有已经声明晰的 __proto__ 上都不存在这个属性,然后就得出结论,这个属性是 undefined.

    function doSomething(){}
    doSomething.prototype.foo = "bar";
    var doSomeInstancing = new doSomething();
    doSomeInstancing.prop = "some value";
    console.log("doSomeInstancing.prop:      " + doSomeInstancing.prop);
    console.log("doSomeInstancing.foo:       " + doSomeInstancing.foo);
    console.log("doSomething.prop:           " + doSomething.prop);
    console.log("doSomething.foo:            " + doSomething.foo);
    console.log("doSomething.prototype.prop: " + doSomething.prototype.prop);
    console.log("doSomething.prototype.foo:  " + doSomething.prototype.foo);

 

效果:

    doSomeInstancing.prop:      some value
    doSomeInstancing.foo:       bar
    doSomething.prop:           undefined
    doSomething.foo:            undefined
    doSomething.prototype.prop: undefined
    doSomething.prototype.foo:  bar

 

 

 

明白原型工具

让我们回到 Person() 组织器的例子。请把下面代码例子依次写入浏览器控制台。。

本例中我们首先将界说一个组织器函数:

    function Person(first, last, age, gender, interests) {
  
      // 属性与方式界说
  
    };

然后在控制台确立一个工具实例:

    var person1 = new Person('Bob', 'Smith', 32, 'male', ['music', 'skiing']);

 

在 JavaScript 控制台输入 “person1.“,你会看到,浏览器将凭据这个工具的可用的成员名称举行自动补全:

深度剖析前端JavaScript中的原型(JS的工具原型)

 在这个列表中,你可以看到界说在 person1 的原型工具、即 Person() 组织器中的成员—— nameagegenderinterestsbiogreeting。同时也有一些其他成员—— watchvalueOf 等等——这些成员界说在 Person() 组织器的原型工具、即 Object 之上。下图展示了原型链的运作机制。

深度剖析前端JavaScript中的原型(JS的工具原型)

 

那么,挪用 person1 的“现实界说在 Object 上”的方式时,会发生什么?好比:

    person1.valueOf()

 

这个方式仅仅返回了被挪用工具的值。在这个例子中发生了如下历程:

  • 浏览器首先检查,person1 工具是否具有可用的 valueOf() 方式。
  • 若是没有,则浏览器检查 person1 工具的原型工具(即 Person组织函数的prototype属性所指向的工具)是否具有可用的 valueof() 方式。
  • 若是也没有,则浏览器检查 Person() 组织函数的prototype属性所指向的工具的原型工具(即 Object组织函数的prototype属性所指向的工具)是否具有可用的 valueOf() 方式。这里有这个方式,于是该方式被挪用。

 

注重:必须重申,原型链中的方式和属性没有被复制到其他工具——它们被接见需要通过前面所说的“原型链”的方式。

注重:没有官方的方式用于直接接见一个工具的原型工具——原型链中的“毗邻”被界说在一个内部属性中,在 JavaScript 语言尺度中用 [[prototype]] 示意(参见 ECMAScript)。然而,大多数现代浏览器照样提供了一个名为 __proto__ (前后各有2个下划线)的属性,其包罗了工具的原型。你可以实验输入 person1.__proto__person1.__proto__.__proto__,看看代码中的原型链是什么样的!

WPF在Gmap.net中将Marker动起来

 

 

prototype 属性:继续成员被界说的地方

那么,那些继续的属性和方式在哪儿界说呢?若是你查看 Object 参考页,会发现左侧列出许多属性和方式——大大跨越我们在 person1 工具中看到的继续成员的数目。某些属性或方式被继续了,而另一些没有——为什么呢?

 

缘故原由在于,继续的属性和方式是界说在 prototype 属性之上的(你可以称之为子命名空间 (sub namespace) )——那些以 Object.prototype. 开头的属性,而非仅仅以 Object. 开头的属性。prototype 属性的值是一个工具,我们希望被原型链下游的工具继续的属性和方式,都被储存在其中。

 

于是 Object.prototype.watch()Object.prototype.valueOf() 等等成员,适用于任何继续自 Object() 的工具类型,包罗使用组织器确立的新的工具实例。

Object.is()Object.keys(),以及其他不在 prototype 工具内的成员,不会被“工具实例”或“继续自 Object() 的工具类型”所继续。这些方式/属性仅能被 Object() 组织器自身使用。

 

 

注重:这看起来很新鲜——组织器自己就是函数,你怎么可能在组织器这个函数中界说一个方式呢?实在函数也是一个工具类型

 

    1. 你可以检查已有的 prototype 属性。回到先前的例子,在 JavaScript 控制台输入:
          Person.prototype
    2. 输出并不多,究竟我们没有为自界说组织器的原型界说任何成员。缺省状态下,组织器的 prototype 属性初始为空缺。现在实验:
          Object.prototype

 

你会看到 Object prototype 属性上界说了大量的方式;如前所示,继续自 Object 的工具都可以使用这些方式。

 

 

JavaScript 中四处都是通过原型链继续的例子。好比,你可以实验从 StringDateNumberArray 全局工具的原型中寻找方式和属性。它们都在原型上界说了一些方式,因此当你确立一个字符串时:

    var myString = 'This is my string.';

myString 立刻具有了一些有用的方式,如 split()indexOf()replace() 等。

主要:prototype 属性大概是 JavaScript 中最容易混淆的名称之一。你可能会以为,this 关键字指向当前工具的原型工具,实在不是(还记得么?原型工具是一个内部工具,应当使用 __proto__ 接见)。prototype 属性包罗(指向)一个工具,你在这个工具中界说需要被继续的成员。

 

create()

Object.create() 方式可以确立新的工具实例。

    1. 例如,在上个例子的 JavaScript 控制台中输入:
                var person2 = Object.create(person1);
    2. create() 现实做的是从指定原型工具确立一个新的工具。这里以 person1 为原型工具确立了 person2 工具。在控制台输入:
                person2.__proto__

 

效果返回工具person1

 

 

constructor 属性

每个实例工具都从原型中继续了一个constructor属性,该属性指向了用于组织此实例工具的组织函数。

    1. 例如,继续在控制台中实验下面的指令:
              person1.constructor
              person2.constructor

      都将返回 Person() 组织器,由于该组织器包罗这些实例的原始界说。

      一个小技巧是,你可以在 constructor 属性的末尾添加一对圆括号(括号中包罗所需的参数),从而用这个组织器确立另一个工具实例。究竟组织器是一个函数,故可以通过圆括号挪用;只需在前面添加 new 关键字,便能将此函数作为组织器使用。

      1. 在控制台中输入:
                      var person3 = new person1.constructor('Karen', 
                          'Stephenson', 26,
                          'female', 
                         ['playing drums', 'mountain climbing']);
      2. 现在实验接见新建工具的属性,例如:
                        person3.name.first
                        person3.age
                        person3.bio()

 

正常事情。通常你不会去用这种方式确立新的实例;但若是你恰好由于某些缘故原由没有原始组织器的引用,那么这种方式就很有用了。

此外,constructor 属性另有其他用途。好比,想要获得某个工具实例的组织器的名字,可以这么用:

    instanceName.constructor.name

详细地,像这样:

    person1.constructor.name

 

 

修改原型

从我们从下面这个例子来看一下若何修改组织器的 prototype 属性。

向组织器的 prototype 添加了一个新的方式:

    function Person(first, last, age, gender, interests) {

      // 属性与方式界说

    };

    var person1 = new Person('Tammi', 'Smith', 32, 'neutral', ['music', 'skiing', 'kickboxing']);

    Person.prototype.farewell = function() {
      alert(this.name.first + ' has left the building. Bye for now!');
    }

 

然则 farewell() 方式仍然可用于 person1 工具实例——旧有工具实例的可用功效被自动更新了。这证明晰先前形貌的原型链模子。这种继续模子下,上游工具的方式不会复制到下游的工具实例中;下游工具自己虽然没有界说这些方式,但浏览器会通过上溯原型链、从上游工具中找到它们。这种继续模子提供了一个壮大而可扩展的功效系统。

 

你很少看到属性界说在 prototype 属性中,由于云云界说不够天真。好比,你可以添加一个属性:

    Person.prototype.fullName = 'Bob Smith';

但这不够天真,由于人们可能不叫这个名字。用 name.firstname.last 组成 fullName 会好许多:

    Person.prototype.fullName = this.name.first + ' ' + this.name.last;

 

然而,这么做是无效的,由于本例中 this 引用全局局限,而非函数局限。接见这个属性只会获得 undefined undefined。但这个语句若放在 先前界说在 prototype 上的方式中则有用,由于此时语句位于函数局限内,从而能够乐成地转换为工具实例局限。你可能会在 prototype 上界说常属性 (constant property) (指那些你永远无需改变的属性),但一般来说,在组织器内界说属性更好。

:关于 this 关键字指代(引用)什么局限/哪个工具,这个问题超出了本文讨论局限。事实上,这个问题有点庞大,若是现在你没能明白,也不用忧郁。

 

事实上,一种极其常见的工具界说模式是,在组织器(函数体)中界说属性、在 prototype 属性上界说方式。云云,组织器只包罗属性界说,而方式则分装在差异的代码块,代码更具可读性。例如:

    // 组织器及其属性界说

    function Test(a,b,c,d) {
      // 属性界说
    };

    // 界说第一个方式

    Test.prototype.x = function () { ... }

    // 界说第二个方式

    Test.prototype.y = function () { ... }

    // 等等……

 

 

 

 

本文先容了 JavaScript 工具原型,包罗原型链若何允许工具之间继续特征、prototype 属性、若何通过它来向组织器添加方式。

若是人人有任何疑问即可留言反馈,会在第一时间回复反馈,谢谢人人!

文章参考泉源:MDN文档

本文为Tz张无忌文章,读后有收获可以请作者喝杯咖啡,转载请文章注明出处:https://www.cnblogs.com/zhaohongcheng/

 

原创文章,作者:28x0新闻网,如若转载,请注明出处:https://www.28x0.com/archives/6093.html