读《你不知道的Javascript》

133 阅读6分钟

对象

JavaScript中万物皆是对象是错的,因为有许多特殊的对象子类例如函数(一等公民)、数组

内置对象

"I am a string" 并不是一个对象,它只是一个字面量,并且是一个不可变的值。 如果要在这个字面量上执行一些操作,比如获取长度、访问其中某个字符等,那需要将其 转换为 String 对象,引擎在必要时语言会自动把字符串字面量转换成一个 String 对象

var strPrimitive = "I am a string";
"I am a string" instanceof String; // false
console.log( strPrimitive.length ); // 13
console.log( strPrimitive.charAt( 3 ) ); // "m"
属性描述符
object.defineProperty(myObject, 'a', {
    value: 2,
    writable: false, // 不可写
    configurable: false,// 禁止删除这个属性;单向操作;设置后单向修改writable为false
    enumerable: false, // 不可枚举,仍然可以访问
})
不变性
  1. 对象常量结合 writable:falseconfigurable:false 就可以创建一个真正的常量属性
var myObject = {}
Object.defineProperty(myObject, "FAVORIF_NUMBER", {
    value: 42,
    writable: false,
    configurable: false
})
  1. 禁止扩展Object.preventExtensions(myObject)
  2. 密封Object.seal(...)密封设置configurable:false调用preventExtensions()
  3. 冻结Object.freeze(...)冻结设置writable:false调用Object.seal()
[[Get]]

对象默认的内置 [[Get]] 操作

  1. 首先在对象中查找是否有名称相同的属性, 如果找到就会返回这个属性的值。
  1. 然后,如果没有找到名称相同的属性,遍历可能存在的 [[Prototype]] 链;
  1. 最后,如果无论如何都没有找到名称相同的属性,那 [[Get]] 操作会返回值 undefined
[[Put]]
  1. 属性是否是访问描述符?如果是并且存在 setter 就调用 setter。
  2. 属性的数据描述符中 writable 是否是 false ?如果是,在非严格模式下静默失败,在 严格模式下抛出 TypeError 异常。
  3. 如果都不是,将该值设置为属性的值。
存在性

in 操作符会检查属性是否在对象及其 [[Prototype]] 原型链中。

hasOwnProperty(..) 只会检查属性是否在 myObject 对象中,不会检查 [[Prototype]] 链。

遍历

for..in遍历对象是无法直接获取属性值的,因为它实际上遍历的是对象中的所有可枚举属性,你需要手动获取属性值。

数组的辅助迭代器

forEach(..)会遍历数组中的所有值并忽略回调函数的返回值。

every(..) 会一直运行直到 回调函数返回 false(或者“假”值)

some(..)会一直运行直到回调函数返回 true(或者 “真”值)

everysome都是类似for、break

ES6 增加了一种用来遍 历数组的 for..of循环语法、循环首先会向被访问对象请求一个迭代器对象,然后通过调用迭代器对象的 next() 方法来遍历所有返回值。

var myArray = [ 1, 2, 3 ];
var it = myArray[Symbol.iterator]();
it.next(); // { value:1, done:false } 
it.next(); // { value:2, done:false } 
it.next(); // { value:3, done:false } 
it.next(); // { done:true }

和数组不同,普通的对象没有内置的 @@iterator,所以无法自动完成 for..of 遍历。之所以要这样做,有许多非常复杂的原因,不过简单来说,这样做是为了避免影响未来的对象 类型

你可以给任何想遍历的对象定义 @@iterator,举例来说:

var myObject = { a: 2, b: 3 }; 
Object.defineProperty(myObject, Symbol.iterator, { 
    enumerable: false, 
    writable: false, 
    configurable: true, 
    value: function() { 
        var o = this; 
        var idx = 0; 
        var ks = Object.keys( o ); 
        return { 
            next: function() { 
                return { value: o[ks[idx++]], done: (idx > ks.length) }; 
            } 
        };
    }
}); 
// 手动遍历 myObject 
var it = myObject[Symbol.iterator](); 
it.next(); // { value:2, done:false }  
it.next(); // { value:3, done:false }  
it.next(); // { value:undefined, done:true } // 用 for..of 遍历 myObject 
for (var v of myObject) { 
    console.log( v ); 
} 
// 2 
// 3

迭代器模式、观察者模式、工厂模式、单例模式

继承(inheritance)

寄生继承,原型继承

js从原型链到继承

混入

显式混入

你只能复制对共享 函数对象的引用

// 非常简单的 mixin(..) 例子 :
function mixin( sourceObj, targetObj ) {
    混合对象“类” | 137
    for (var key in sourceObj) {
        // 只会在不存在的情况下复制
        if (!(key in targetObj)) {
        targetObj[key] = sourceObj[key];
        }
    }
    return targetObj;
}

显式混入模式的一种变体被称为“寄生继承

首先我们复制一份 Vehicle 父类(对象)的定义,然后混入子类(对象)的定 义(如果需要的话保留到父类的特殊引用),然后用这个复合对象构建实例

//“传统的 JavaScript 类”Vehicle
function Vehicle() {
    this.engines = 1;
}
Vehicle.prototype.ignition = function() {
    console.log( "Turning on my engine." );
};
Vehicle.prototype.drive = function() {
    this.ignition();
    console.log( "Steering and moving forward!" );
};
混合对象“类” | 139
//“寄生类”Car
function Car() {
    // 首先,car 是一个 Vehicle
    var car = new Vehicle();
    // 接着我们对 car 进行定制
    car.wheels = 4;
    // 保存到 Vehicle::drive() 的特殊引用
    var vehDrive = car.drive;
    // 重写 Vehicle::drive()
    car.drive = function() {
        vehDrive.call( this );
        console.log("Rolling on all " + this.wheels + " wheels!")
    };
     
    return car;
}
var myCar = new Car();
myCar.drive();
// 发动引擎。Turning on my engine.
// 手握方向盘!Steering and moving forward!
// 全速前进 Rolling on all 4 wheels!

(相对)多态(polymorphism)

Car 重写了继承自父类的 drive() 方法,但是之后 Car 调用了 Vehicle.drive.call( this )方法, 这表明 Car 可以引用继承来的原始 drive() 方法。这个技术被称为相对多态

隐式混入

var Something = {
    cool: function() {
    this.greeting = "Hello World";
    this.count = this.count ? this.count + 1 : 1;
    }
};
​
Something.cool();
Something.greeting; // "Hello World"
Something.count; // 1
var Another = {
cool: function() {
    // 隐式把 Something 混入 Another
    Something.cool.call( this );
}
};
Another.cool();
Another.greeting; // "Hello World"
Another.count; // 1(count 不是共享状态)
原型链

关联两对象 Ojbect.createObject.setPrototypeOf

Bar.ptototype = Ojbect.create(Foo.prototype) // 丢弃掉之前的对象
Object.setPrototypeOf(Bar.prototype, Foo.prototype) // es6新语法// 不使用以下关联
Bar.prototype = Foo.prototype // 直接使用foo原型对象,修改bar会同时修改foo
Bar.prototype = new Foo() // 使用构造函数,如果有副作用(修改状态,给this添加数据属性等)会影响bar

instanceof

在 a的整条 [[Prototype]] 链中是否有指向 Foo.prototype 的对象?

var a = {}
function Foo () {}
a instanceof Foo // false
原型继承
function Foo(name) {
    this.name = name;
}
Foo.prototype.myName = function() {
    return this.name;
};
function Bar(name,label) {
    Foo.call( this, name );
    this.label = label;
}
// 我们创建了一个新的 Bar.prototype 对象并关联到 Foo.prototype
Bar.prototype = Object.create( Foo.prototype );
// 注意!现在没有 Bar.prototype.constructor 了
// 如果你需要这个属性的话可能需要手动修复一下它
Bar.prototype.myLabel = function() {
    return this.label;
};
var a = new Bar( "a", "obj a" );
a.myName(); // "a"
a.myLabel(); // "obj a"
委托(本身不具备,委托关联对象实现 )

行为委托认为对象之间是兄弟关系,互相委托,而不是父类和子类的关系。JavaScript 的 [[Prototype]] 机制本质上就是行为委托机制。

对象关联(对象之前互相关联)是一种编码风格,它倡导的是直接创建和关联对象,不把它们抽象成类。对象关联可以用基于 [[Prototype]] 的行为委托非常自然地实现。

var foo = {
    something() {
        console.log("something")
    }
}
var bar = Object.create(foo)
bar.something()
匿名函数
/*
导致:
    1. 调用栈更难追踪
    2. 自我引用更难(递归,事件(解除)绑定)
    3. 代码难以理解 
*/
var Foo = {
    bar () {}
    bar: function bar() {}
}
class

ES6 的 class 让 [[Prototype]] 变得更加难用而且隐藏了 JavaScript 对象最重要 的机制——对象之间的实时委托关联

class 基本上只是现有 [[Prototype]](委托!)机制的一种语法糖

《你不知道的Javascript》