《你不知道的JS(上)》读书笔记3

第4章 混合对象“类”

  • 多态在继承链的不同层次中一个方法名可以被多次定义,当调用方法时会自动选择合适的定义。
  • 子类对继承到的一个方法进行“重写”,不会影响父类中的方法,这两个方法互不影响,因此才能使用相对多台引用访问父类中的方法。

混入

  • 混入:模拟类的复制行为

  • 显式混入:手动实现复制功能,但是对象只能复制引用,无法复制被引用的对象或者函数本身。

    1
    2
    3
    4
    5
    6
    7
    8
    function mixin(sourceObj,targetObj){
    for(var key in sourceObj){
    if(!key in targetObj){
    targetObj[key]=sourceObj[key];
    }
    }
    return targetObj;
    }
  • 寄生继承:显式混入模式的一种变体,既是显式的又是隐式的。

  • 隐式继承:类似显式混入伪多态(OtherObj.methodName.call(this, ...))

第5章 原型

  • 使用for..in遍历对象时原理和查找[[Prototype]]链类似,任何可以通过原型链访问到(并且enumerable)的属性都会被枚举。

属性设置和屏蔽

myObject.foo="bar"

  1. myObject对象中有foo,原型链上没有:会修改已有属性值。

  2. myObject对象中有foo,原型链上也有foo:屏蔽,修改myObject中的foo。

  3. myObject没有foo,原型链上有foo:三种情况

    3.1 原型链上有foo,且没有被标记成只读。会在myObject中添加一个名为foo的新属性。

    3.2 原型链上有foo,但是是只读的。赋值语句会被忽略,严格模式下会抛出错误。

    3.3 圆形脸上有foo,并且是一个setter。赋值语句被忽略,并调用这个setter。

  4. myObject没有foo,原型链上也没有:在myObject上添加foo。

“类”函数

1
2
3
4
5
6
7
8
function Foo(){
...
}
var a=new Foo();
Objecr.getPrototypeOf(a) === Foo.prototype; //true
Foo.prototype.constructor === Foo; //true
a.constructor === Foo;
//true 实际a本身并没有.constructor属性,会委托[[Prototype]]链上的Foo.prototype
  • 继承:继承意味着复制操作,但是JS并不会复制对象属性。JS会在两个对象之间创建一个关联,这样一个对象就可以通过委托访问另一个对象的属性和函数。
  • 差异继承:在描述对象行为时,使用其不同于普遍描述的特质。

继承

  • Object.create(新创建对象的原型对象)会创建一个拥有null[[Prototype]]连接的新对象,并把新对象内部的[[Prototype]]对象并把它关联到你指定的对象。
  • a=Object.create(b)b是新创建对象a的[[prototype]]
  • Bar.prototype=Object.create(Foo.prototype)创建一个关联到Bar.prototype的新对象。
    • LHS先查找Bar.prorotype,找到后对其重新赋值
    • 赋予的值是Foo.prototype
  • 在ES6后,可以使用新方法:Object.setPrototyprOf(Bar.prototype,Foo.prototype)

检查”类”关系

  • 内省(反射):检查一个实例的继承祖先
  • 对象和函数之间的关系:instanceof
  • 两个对象之间的关系:isPrototyprOf
  • 直接获取一个对象的[Protoype]链:Object.getPrototypeOf(a)或者a._proto

第6章 行为委托

  • 类和委托的区别:
    • 在[[prototype]]委托中,最好把数据成员保存在委托者而不是委托目标上
    • 在类中,会利用多态、重写的优势。但是在委托中,要尽量避免这么做,否则就需要笨拙脆弱的语法: 隐式继承:(OtherObj.methodName.call(this, ...))
    • 调用位置触发this的隐式绑定规则。
  • 委托行为意味着某些对象在找不到属性或者方法引用时会把这个请求委托给另一个对象

比较思维模型

  • 面向对象(原型)风格代码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    function Foo(who){
    this.me=who;
    }
    Foo.prototype.identify=function(){
    return "I am"+this.me;
    }
    function Bar(who){
    Foo.call(this,who);
    }
    Bar.prototype=Object.create(Foo.prototype);
    Bar.prototype.speak=function(){
    alert("Hello,"+this.identify()+".");
    }
    var b1=new Bar("b1");
    var b2=new Bar("b2");
    b1.speak();
    b2.speak();

    js-1

  • 对象关联风格代码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    Foo={
    init:function(who){
    this.me=who;
    },
    identify:function(){
    return "I am"+this.me;
    }
    }
    Bar=Object.create(Foo);
    Bar.speak=function(){
    alert("Hello,"+this.identify()+".")
    }
    var b1=Object.create(Bar);
    b1.init("b1");
    var b2=Object.create(Bar);
    b2.init("b2");
    b1.speak();
    b2.speak();

  • 使用构造函数,需要在同一步骤中实现构造和初始化var b1=new Bar(),但是对象关联风格代码分开两步,b1=Object.create(Bar);b1.init("b1");可以根据需要在创建后对象后,再在合适的位置初始化,更灵活,更具有优势。

  • 行为委托模式中,Bar和Foo都是对象,它们之间是兄弟关系,并不是父类和子类的关系。

更好的语法

  • ES6中可以在任意兑现改的字面形式中使用简洁方法声明:

    1
    2
    3
    4
    var LoginController={
    errors:[],
    bar(){}
    }
  • 存在的问题是:在对象创建方法的语法,其实是一个匿名函数表达式并赋值给bar属性,因此不具备自我引用的词法标识符。

内省

  • 常见但脆弱的内省模式——鸭子类型:if(a1.something){a1.something()}
  • ES6中的Promise就是典型的“鸭子类型”

ES6中的class

ES6的class解决了哪些问题

  • 不再使用杂乱的.prototype
  • extends直接继承,不需要Object.create()
  • 可以通过super()实现多态
  • class字面语法不能声明属性,只能声明方法,避免获取其他地方的属性。
  • 可以通过extends扩展对象子类型,甚至是内置的对象子类型。

class陷阱

  • 若修改父类的方法,子类的所有实例都会受到影响。
  • 无法定义类成员属性,如果要跟踪实例之间共享状态,必须要使用.prototype语法
  • class中会有意外屏蔽的问题
  • super不一定会绑定到合适的对象,需要使用toMethod(…)手动绑定