zoukankan      html  css  js  c++  java
  • 学习笔记=>《你不知道的JavaScript(上卷)第二部分》第五章:原型

    [[prototype]]:

      JavaScript中对象有一个特殊的[[prototype]]内置属性,其实就是对于其他对象的引用,几乎所有的对象在创建时

      [[prototype]]属性都会被赋予一个非空的值。

      还是一个对象属性查找的例子:

    var obj = {
         a:1
    };
    
    //引用对象属性,触发[[GET]]操作
    //对于默认的[[GET]]操作来说,第一步检查对象中是否有属性a,有的话就使用它
    obj.a;   //1

      如果在对象中找不到a的话,就要使用对象的[[prototype]]链了。

      对于默认的[[GET]]操作来说,如果无法在对象本身中找到需要的属性,就会继续访问对象的[[prototype]]链了:

    var obj = {
          a:1
    };
    
    //创建一个关联对象obj的新对象
    var newObj = Object.create(obj); console.log(newObj); //{} //结果打印的1,并不是newObj中的属性a的值,而是其[[prototype]]中的属性a的值 console.log(newObj.a); //1

      上面的例子中,在它的[[prototype]]中找到了属性a。

      如果碰到在自身中找不到属性,同时在它不为空的[[prototype]]链中也找不到的时候,它会继续查找下去,这个过程会持续

      到找到匹配的属性或者查找完整个[[prototype]]链,如果还是找不就会返回undefined。

      使用for...in循环遍历对象时原理和查找[[prototype]]链类似,任何通过原型链可以访问的(且enumerable为true)的属性都

      会被枚举出来,使用in操作符在对象中是否存在一样会查找[[prototype]]链(无论属性是否可枚举)。

    var obj = {
         a:1,
         b:2
    };
    
    Object.defineProperty(obj,'c',{
           value:3,
           writable:true,
           enumerable:false,
           configurable:true
    });
    
    var newObj = Object.create(obj);
    
    //属性c不可枚举
    for(var i in newObj){
          //原型中可枚举的属性a,b被打印出来
          console.log(i);   //a,b
    }
    
    //原型中的属性a(可枚举),c(不可枚举),通过in操作符都可以判断出来
    console.log('a' in newObj,'c' in newObj);   //true , true

       

    Object.prototype:

      所有普通的[[prototype]]链最终都会指向内置的Object.prototype,由于所有的“普通”(内置,不是特定主机的扩展)对象都

      源于(或者说吧[[prototype]]链的顶端设置为)这个Object.prototype对象,这个对象中包含许多通用的方法,想toString等。

    属性设置和屏蔽:

      当我们给对象添加一个属性的时候,并不仅仅是在对象上添加一个属性或者在修改原有属性:

        var obj = {};

        obj.a = 111;

      分析一下可能出现的几种情况:

      ① obj中已经存在属性a:属性赋值只会修改已存在的属性的值。

      ② obj和它的[[prototype]]链中都不存在属性a:属性直接被添加到对象obj中并赋值。

      ③ obj及其[[prototype]]链中国都存在a:obj中属性a赋值,且屏蔽掉[[prototype]]链中的属性,因为对象的属性赋值都会选择

       [[prototype]]链最底层的属性。

      出乎意料的情况其实发生在obj中不存在属性a,而[[prototype]]链中存在的时候:

      ④ 如果在[[prototype]]链中存在名为a的普通数据访问属性,且没有标记为writable:false的时候,那就会直接在obj对象上添

        加属性,这个属性是屏蔽属性。

      ⑤ 如果在[[prototype]]链中存在名为a的普通数据访问属性,且标记为writable:false的时候,那么属性赋值即不会修改

       [[prototype]]链上的属性,也不会在obj上创建一个新属性,如果是运行在严格模式下还会报错。否则这条一句会被忽略,

       总之,不会发生屏蔽。

      ⑥ 如果在[[prototype]]链中存在,且它是一个setter,那就一定会调那个setter,属性不会被添加到对象obj中。

          var obj = {
            a:1
          };
    
          //定义属性b为可写
          Object.defineProperty(obj,'b',{
              value:2,
              writable:true,
              enumerable:true,
              configurable:true
          });
    
          //创建对象newObj,关联上obj
          var newObj = Object.create(obj);
    
         //为newObj添加属性b
          newObj.b = 666;
    
          console.log(newObj);   //{b:666}
    
          //重新定义属性b为只读
          Object.defineProperty(obj,'b',{
              writable:false,
              enumerable:true,
              configurable:true
          });
    
         //再次创建一个指向obj的新对象,覆盖之前的newObj 
          newObj = Object.create(obj);
    
         //在其上级关联对象obj中已经存在只读的属性b的情况下,在newObj中添加一个属性啊
          newObj.b = 777;
    
          console.log(newObj);    //{} ---->创建只有属性b失败
    
          //重新定义属性b,为其设置属性访问符get和set
          Object.defineProperty(obj,'b',{
              get:function(){
                  return 'this is getter'
              },
              set:function(val){
                  console.log(val);
              },
              enumerable:true,
              configurable:true
          });
    
          newObj = Object.create(obj);
    
         //在其关联的上级对象中存在一个属性b设置了setter的情况下,newObj添加只有属性b
          newObj.b = 'newObj add b';
    
          console.log(newObj);   //'this is getter',{}

      可见,大多数像我一样的前端恐怕都下意识的以为,以上清空中,都会常见只有属性(遮罩属性)。so,还是得多看看书啊!

      当然,如果希望在⑤⑥的情况下依然和你之前想的那样创建遮罩属性,那么就不能用obj.b的形式创建,而是使用definePrototype

      赋值。

      有些情况会隐式创建遮蔽:

          var obj = {a:1};
    
          //创建一个关联obj的对象newObj
          var newObj = Object.create(obj);
    
          console.log(obj.a,newObj.a);   //1,1
    
          console.log(obj.hasOwnProperty('a'),newObj.hasOwnProperty('a'));   //true,false
    
          newObj.a ++;
    
          console.log(obj.a,newObj.a);    //1,2
    
          console.log(obj.hasOwnProperty('a'),newObj);    //true,{a:2}

      其实上面例子中之所以创建了隐式遮蔽,是因为newObj .a++;    (等价于)==》newObj.a = newObj.a + 1;

    constructor:

      function Bar(){};
    
      console.log(Bar.prototype.constructor === Bar);    //true
    
      var a = new Bar;
    
      console.log(a.constructor === Bar);      //true

      Bar.prototype默认有一个公有且不可枚举的属性constructor,这个属性引用的是对象关联的函数。

      看起了a.constructor === Bar为真意味着a确实有一个指向Bar的属性constructor,但是,事实并不是这样。

      实际上.constructor引用同样被委托(JavaScript中的原型链间的关系用“委托”表述更合适)给了Bar.prototype,

      而Bar.prototype.constructor默认指向Bar。

      Bar.prototype的constructor属性只是在Bar声明时的默认属性,如果创建一个新对象让Bar.prototype指向这个新对象,

      那么新对象不会自动获得constructor属性:

       function Bar(){};
    
       Bar.prototype = {name:'new prototype'};
    
       var a = new Bar;
    
       console.log(a.constructor === Bar);     //false
    
       console.log(a.constructor === Object);    //true

      我们已经知道了,其实本身实例a上面并没有属性constructor,实际上我们a.constructor的时候是委托给Bar.prototype的,但是我们为

      Bar.prototype指定了新的对象,而这个对象中并没有属性constructor,这样就会继续沿着[[prototype]]链往上找,然后委托到链顶端的

      Object.prototype,这个对象有属性constructor,指向内置的Object函数。

      当然,我们可以给Bar.prototype对象手动添加一个constructor属性,不过这需要添加一个符合正常情况的不可枚举属性。

       function Bar(){};
    
       Bar.prototype = {name:'new prototype'};
    
       Object.defineProperty(Bar.prototype,'constructor',{
            value:Bar,
            writable:true,
            enumerable:false,
            configurable:true
       });
    
       var a = new Bar;
    
       console.log(a.constructor === Bar);     //true
    
       console.log(a.constructor === Object);    //false

    (原型)继承:

      看一个典型的“原型风格”代码:

      function Bar(name){
          this.name = name;
      };
    
      Bar.prototype.myName = function(){
          return this.name;
      };
    
      function Baz(name,label){
          Bar.call(this,name);
          this.label = label;
      };
    
      Baz.prototype = Object.create(Bar.prototype);
    
      Baz.prototype.myLabel = function(){
          return this.label;
      };
    
      var a = new Baz('Baz','bbbbbaaaaaazzzzzz');
    
      console.log(a.myName(),a.myLabel());     //'Baz','bbbbbaaaaaazzzzzz'

      这段代码的核心部分是 “Baz.prototype = Object.create(Bar.prototype);”,我们抛弃了Baz默认的prototype对象,而是重新为它定义

      了一个新的对象。下面有两种常见的错误做法:

        1,Baz.prototype = Bar.prototype;

         //这种方式并不会创建一个关联到Bar.prototype新的对象,它是直接将Baz.prototype引用到Bar.prototype。这样的话,当你在

         //Baz.prototype上面添加方法或属性的时候,同时会直接修改Bar.prototype。

        2,Baz.prototype = new Bar;

         //当Bar中有一些副作用的修改的时候会影响到Baz的后代。

      ES6中新增了一个setPrototypeOf方法,可以用标准,可靠的方法来修改关联。对比一下我们用的方式:

      Baz.prototype = Object.create(Bar.prototype);      //ES6之前需要抛弃默认的Baz.prototype

      Object.setPrototypeOf(Baz.prototype,Bar.prototype);     //ES6中直接修改现有的Baz.prototype

    检查“类”关系:

      假设有对象a,如何寻找对象a委托的对象。在传统的面向类环境中,检查一个对象的继承祖先通常被称为"内省"(或反射)。

      第一种方式instanceof操作符:

    function Bar(){};
    
    var a = new Bar;
    
    console.log(a instanceof Bar);    //true

      instanceof操作符返回的是:在a的整条[[prototype]]链中,是否有指向Bar.prototype的对象。

      然而,这个方法只能处理对象a和函数Bar间的关系,如果想判断两个对象之间是否通过[[prototype]]关联,只用instanceof无法实现。

      第二种判断[[prototype]]反射的方法:

    function Bar(){};
    
    var a = new Bar();
    
    console.log(Bar.prototype.isPrototypeOf(a));    //true
    //对象间的反射关系
    
    var Bar = {name:'Bar'};
    
    var a = Object.create(Bar);
    
    console.log(Object.getPrototypeOf(Bar).isPrototypeOf(a));   //true

      直接获取一个对象的[[prototype]]链,在ES5中的标准方法:

          Object.getPrototypeOf(a);

      例如上面的例子中:Object.getPrototypeOf(a) === Bar.prototype;    //true

      绝大多数浏览器(也不是所有)支持一个非标准的方法来访问内部prototype属性:

        a.__proto__ === Bar.prototype   //true

      如果想直接查找[[prototype]]链的话(甚至可以通过.__proto__.__proto__......来遍历),这个方法很好用。

      和之前的constructor属性一样,__proto__并不存在当前所用对象中,也是存在与内置的Object.prototype中。

      __proto__看起了更像一个属性,其实它更像一个setter/getter,它的实现大概如下:

    //模拟实现
    Object.definePrototype(Object.prototype,'__proto__',{
            get:function(){
                  return Object.getPropertyOf(this);
            },
            set:function(protoObj){
                  //ES6中的setPrototypeOf
                  Object.setPrototypeOf(this,protoObj);
                  return protoObj;
            }
    });

    对象关联:

      在上面的例子中经常用到Object.create()来创建关联对象:

    var Bar = {
         name:'Bar'
    };
    
    var newBar = Object.create(Bar);
    
    console.log(newBar.name);    //"Bar"

      Object.create会创建一个关联到我们指定对象的对象,这样就可以充分放回prototype机制的力量(委托),并且避免不必要

      的麻烦(比如使用new的构造函数调用会生成.prototype和.constructor的引用);

      Object.create(null)会创建一个不包含原型链的对象,通常用它来存储数据。

      Object.create是ES5创建的函数,如果想要支持ES5之前的应用的话,有以下代码实现:

    if(!Object.create){
           Object.create = function(obj){
                  function F(){};
                  F.prototype = obj;
                  return new F;
           }
    }
  • 相关阅读:
    MAC记住 git的用户名密码
    webpack初学踩坑记
    __dirname和__filename和process.cwd()三者的区别
    webpack
    日期格式在ios中的兼容性
    php实现导出excel功能
    node 之koa项目学习
    nodejs之socket.io 私发消息和选择群组发消息
    nodejs之socket.io 聊天实现
    mongoDB基础语法
  • 原文地址:https://www.cnblogs.com/huangzhenghaoBKY/p/9853047.html
Copyright © 2011-2022 走看看