学习笔记=>《你不知道的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;
       }
}

猜你喜欢

转载自www.cnblogs.com/huangzhenghaoBKY/p/9853047.html
今日推荐