ES6引入了一个很甜的语法糖就是 class, class 可以帮助开发者回归到 Java 时代的面向对象编程而不是 ES5 中被诟病的面向原型编程. 我也在工作的业务代码中大量的使用 class, 很少去触及 prototype 了.
两面性:
-
class 语法糖的本质还是prototype, 所以应该回归到写 prototype 上.
-
既然有 class 的写法了, 而且继承上也相比原型好写, 好理解许多, 所以应该向前看, 摒弃掉以前的 prototype 写法.
睿智而理性的读者, 你的理解是其中之一还是二者兼备? 我的看法是: 语法糖存在即合理, 语法糖不仅仅是更高层级的封装, 它可以避免写出没有语法糖时候的 bug, 但是语法糖不是语言本身的特性, 所以也一定要理解背后的成因, 加上原型是 java script 里面特别特别重要的知识点, 不能不去深究. 你可以不用, 但不能不懂.
好了, 来看看 ES5 的面向对象编程
什么是对象
ECMA的官方解释: 无序属性的集合, 属性可包括基本值, 函数, 对象
注意无序二字, 可理解为包含一定属性或方法的键值对. 是的, 本质上, 对象就是包含键值对映射的集合, 键是属性的名字, 值就是属性
属性的类型
-
数据属性
- configurable
- 表示是否可以被配置(除了 enumerable 和 writable 之外), 包含但不限于属性属性转化为访问器属性, 主要是用于 delete 的限制
- enumerable
- 是否可以被 for in 或 Object.keys 到
- value
- 属性具体的值, 默认 undefined
- writable
- 可修改 value
- configurable
-
访问器属性
- configurable
- 表示是否可以被配置(除了 enumerable 和 writable 之外), 包含但不限于属性属性转化为访问器属性, 主要是用于 delete 的限制
- enumerable
- 是否可以被 for in 或 Object.keys 到
- get
- 获取属性的值, 默认 undefined
- set
- 设置属性的值, 默认 undefined
- configurable
注意, 第二个访问器属性就是被著名的 React 和 Vue 实现数据响应的原理之一. 再注意, 以上所有的 bool 属性在没有进行配置的时候都默认为 false.
如何实现这两者的转化呢
在 configurable 为 true 的情况下, 凡是包含 value 或 writable 的会默认为数据属性, 会将原有的 get 和 set 属性删除, 反之如果设置了 get 或 set, 那么就会认为为访问器属性, 将 value 和 writable 删除
const o = {}
Object.defineProperty(o, 'name', {
configurable: true,
enumerable: false, // 可不写, 默认为 false
value: 'lorry',
writable: false // 可不写, 默认为 false
})
console.log(o)//{name: "lorry"}
o.name = 'jiang'// 不会改变, 因为 writable 为 false
console.log(o)//{name: "lorry"}
Object.keys(o)// []
// 转化为访问器属性
o['_name'] = 'lorry'; // 设置私有属性
Object.defineProperty(o, 'name', {
get: function(){return this._name},
set: function(newName){this._name = newName},
configurable: false,
enumerable: true
})
console.log(o); // {_name: "lorry"}
o.name = 'setted jiang'
console.log(o.name); // setted jiang
Object.keys(o); // ["_name", "name"]
其他的方式
除了Object.defineProperty
之外, 还有其他的跟对象属性相关的原生方法
-
Object.defineProperties(o, {attr1:{}, attr2:{}})
, 批量设置一个对象多个属性 -
Object.getOwnPropertyDescriptor(o, attrName)
, 获取对象某个属性的配置 -
Object.getOwnPropertyDescriptors(o)
, 获取对象所有属性的配置
对象的创建
工厂模式
function createObject(name) {
var o = new Object();
o.name = name;
o.sayName = function() {console.log(this.name)};
return o;
}
const p1 = createObject('lorry')
优点: 简单直观 缺点: 无法进行对象识别, 没有 instanceof 可以去追溯.
构造函数模式
function Person(name) {
this.name = name;
this.sayName = function() {console.log(this.name)};
}
const p1 = new Person('lorry')
注意, 凡是构造函数都应该首字母大写 优点:
-
不显式创建对象(实质还是有创建新对象)
-
使用 this 的上下文对象
-
不用 return(默认隐式创建的新对象)
-
能够使用
p1 instanceof Person
进行对象识别
缺点:
- 每个实例都会生成新的属性和方法, 会造成内存的浪费(在当今性能过剩的年代, 这个实质上不算什么问题, 只是显得代码不是很规范和专业)
原型模式
function Person() {};
Person.prototype.name = 'Lorry';
Person.prototype.sayName = function() {
console.log(this.name);
}
const p1 = new Person();
const p2 = new Person();
p1.sayName(); // lorry;
console.log(p1.sayName === p2.sayName) // true
可以看到两个实例p1 和 p2 共享同一个 name 属性和 sayName 的方法, 会节省内存.
注意, 在原型上的方法和属性是不会被 hasOwnProperty()
检测出来的(Object.keys()同样如此), 但是在in
中是有的.比如
p1.hasOwnProperty('name'); // false
Object.keys(p1); // []
'name' in p1; // true
一种更简单的定义方法
function Person(){};
Person.prototype = {
name: 'lorry',
sayName: function(){
console.log(this.name)
},
//ES6
sayName2() {
console.log(this.na