前两天看完了 js 面向对象精要, 有所收获, 因此趁热打铁做一下记录. 这本书很薄, 只用了两天午休的时间就看完了, 但作者的讲述是十分清晰的.
引用类型和原始类型
js 中的数据, 要么是一个对象, 要么从一个对象中获取
原始类型
- 原始类型直接保存值, 引用类型保存指向内存的指针
- 用原始类型给变量赋值时, 值会直接被赋值到改变量中
- 对于原始类型, 用
typeof
函数 - 原始类型拥有方法, 但它们不是对象, js 使它们用起来像对象, 是为了提供语言上的一致性体验.
引用类型(对象)
创建对象
- 构造函数和
new
操作符 - 使用字面量
内建类型
- 六种内建类型
- 鉴别引用类型使用
instanceof
函数 - 所有类型都继承自 Object
- 使用
Array.isArray
鉴别数组. 由于不同框架会实现不同的 Array 实例.
原始封装类型
String, Number, Boolean
- 每当读取字符串, 数字或布尔值时, 原始封装对象会自动创建
- 原始封装对象仅存在于该语句
- 语句执行完成后会自动销毁所创建的原始封装对象
函数
函数也是一个对象, 内部独有一个 [[call]]
属性, 表明该对象可以被执行.
两种定义方式:
var a = function() {
}
// 这种写法会有声明提升, 函数定义会被提升到上下文的顶部
function a() {
}
函数的参数
- 保存在 arguments 对象中(这个对象的行为表现类似于数组)
- arguments 不是 Array 的实例
- arguments.length 表示期望的参数的长度
- js 不存在函数重载, 但可以根据 arguments 的状态去模拟
this 对象
- js 的每个函数的作用域中都有一个 this 对象, 代表 调用改函数的对象
- 改变 this 指向的三种方法
- call(target, param1, param2, ...)
- apply(target, [params...])
- bind(target, 其余参数会被设为新函数中的命名参数)
理解对象
- 属性第一次被添加给对象时, js 在对象上调用一个名为
[[put]]
的内部方法 - 调用
[[put]]
方法在对象上创建了自有属性 - 对已有属性赋新值时, 调用了内部的
[[set]]
方法 - 使用
in
操作符判断属性是否存在于方法中, 其实就是在 hashtable 中查找一个键是否存在.in
操作符会检查自由属性和原型属性 - delete 删除一个属性, 其实是调用了内部的
[[delete]]
方法
属性枚举
- [[Enumerable]] 属性表示属性是否可枚举
- 使用
for-in
循环枚举一个对象的可枚举属性 - Object.keys() 得到一个对象的所有可枚举自有属性的名称数组.
属性类型
数据属性和访问器属性
- 数据属性包含值
- 访问器属性不包含值, 包含 setter 和 getter 两个函数
- 只定义 getter 就是只读
- [[Configureable]] 决定该属性是否可配置, 手动声明的所有默认属性都是可枚举, 可配置的
- 可以用
Object.defineProperty()
方法改变属性特征. 需要为所有特征指定一个值, 否则布尔值类型的特征会被默认设置为 false. - 无法将不可配置的属性变成可配置的.
- 数据属性有一个 [[value]] 属性, 里面保存了值. 还有一个 [[writable]] 表示是否可写.
- 访问器属性也有两个额外特征, [[get]] 和 [[set]]
- 定义多重属性使用 Object.defineProperties() 函数
- Object.getOwnPropertyDescriptor() 方法获取属性的特征
- [[extensible]] 是否可修改, false 时表示不可修改, 无法添加新属性
- Object.preventExtensions() 创建一个不可扩展的对象
- Object.isExtensible() 方法检查 [[extensible]] 的值
- Object.seal() 方法创建一个封印的对象, [[extensible]] 为 false, 所有属性的 [[Configureable]] 也是 false
- Object.isSealed() 方法判断一个对象是否被封印
- Object.freeze() 方法冻结一个对象. 改对象是一个所有属性都是只读的 sealed 对象
- Object.isFrozen() 判断是否被冻结
构造函数和原型函数
构造函数就是你用 new 创建对象时调用的函数, 所有使用同一个构造函数创建的对象都有相同的属性和方法
- 与普通函数的区别: 首字母大写
- 调用构造函数时, new 关键字会自动创建 this 对象, 且类型就是构造函数的类型.
- 也可以在构造函数中显式 return, 如果 return 的是对象, 则返回该对象实例; 如果是原始值, 会被忽略, 返回的依旧是对象实例.
- 忘记用 new 调用构造函数时, this 是 window
原型对象
几乎所有函数都有 prototype
属性, 所有创建的实例都共享原型对象, 且可以访问到原型对象的属性.
- 鉴别一个属性是否是原型对象的属性:
var hasPrototypeProperty(object, name) {
var result = (name in object) && !(Object.hasOwnProperty(name))
return result
}
- 对象实例通过 [[prototype]] 跟踪其原型对象
- 当 new 一个对象时, 构造函数的原型对象会被赋值给该对象的 [[prototype]] 属性
- 可以使用 Object.getPrototypeOf() 方法读取 [[prototype]] 属性的值
- 对于所有的泛用对象, 其 [[prototype]] 属性始终指向 Object.prototype
- 使用
_proto_
属性可直接读写 [[prototype]] 属性. - 原型属性无法赋值
- 可以直接用字面量替换原型对象, 但需要注意的是 constructor 属性需要手动替换, 不指定的话默认是指向 Object
- 可以给内建对象的 prototype 增加方法, 但不推荐, 因为可能会误导其他程序员
继承
js 中的继承是通过原型对象链继承的.
对象通常都继承自 Object.prototype, 因此也都有一下方法:
hasOwnProperty()
properTyIsEnumerable()
isPrototypeOf()
// 当一个操作符作用于一个对象时, 就会调用 valueOf() 方法
// 原始封装类型重写了该方法, 使 String 有不同的表现形式
valueOf()
// 当 valueOf 方法返回的是一个引用时, 就会调用 toString 方法
toString()
对象的继承
- 只需要指定哪个对象是新对象的 [[prototype]], 也可以用 Object.create() 方法显式创建
- 所有继承链的末端通常是 Object.prototype, 其 [[prototype]] 为 null
- 访问父类方法时, 使用 call 或 apply 指定 this
对象模式
私有成员:
- 通过
_name
来约束 - 通过立即函数来返回对象
- 通过闭包返回对象, 在闭包内调用外部的变量(模块模式)
混入:
一个对象在不改变原型对象链的情况下, 得到了另一个对象的属性和方法, 被称为混入
混入的实现:
- 使用 for-in 浅拷贝
- 使用 foreach 实现 mixin 函数
var mixin = function(receiver, supplier) {
Object.keys(supplier).forEach(function(property) {
var descriptor = Object.getOwnPropertyDescriptor(supplier, property)
Object.defineProperty(receiver, property, descriptor)
return receiver
})
}
作用域安全的构造函数
在函数内判断自己是否被 new 调用, 处理不同的情况
网友评论