对象
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, // 不可枚举,仍然可以访问
})
不变性
- 对象常量结合
writable:false和configurable:false就可以创建一个真正的常量属性
var myObject = {}
Object.defineProperty(myObject, "FAVORIF_NUMBER", {
value: 42,
writable: false,
configurable: false
})
- 禁止扩展
Object.preventExtensions(myObject) - 密封
Object.seal(...)密封设置configurable:false调用preventExtensions() - 冻结
Object.freeze(...)冻结设置writable:false调用Object.seal()
[[Get]]
对象默认的内置 [[Get]] 操作
- 首先在对象中查找是否有名称相同的属性, 如果找到就会返回这个属性的值。
- 然后,如果没有找到名称相同的属性,遍历可能存在的 [[Prototype]] 链;
- 最后,如果无论如何都没有找到名称相同的属性,那 [[Get]] 操作会返回值 undefined
[[Put]]
- 属性是否是访问描述符?如果是并且存在 setter 就调用 setter。
- 属性的数据描述符中 writable 是否是 false ?如果是,在非严格模式下静默失败,在 严格模式下抛出
TypeError异常。 - 如果都不是,将该值设置为属性的值。
存在性
in 操作符会检查属性是否在对象及其 [[Prototype]] 原型链中。
hasOwnProperty(..) 只会检查属性是否在 myObject 对象中,不会检查 [[Prototype]] 链。
遍历
for..in遍历对象是无法直接获取属性值的,因为它实际上遍历的是对象中的所有可枚举属性,你需要手动获取属性值。
数组的辅助迭代器
forEach(..)会遍历数组中的所有值并忽略回调函数的返回值。
every(..) 会一直运行直到 回调函数返回 false(或者“假”值)
some(..)会一直运行直到回调函数返回 true(或者 “真”值)
every和some都是类似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)
寄生继承,原型继承
混入
显式混入
你只能复制对共享 函数对象的引用
// 非常简单的 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.create和Object.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》