JavaScript – Object, Prototype, Function, Class
介绍
JavaScript 有 4 个概念一定要搞清楚.
Object, Prototype, Function, Class
简单说一下:
Object
C# 很少直接用 object, 通常都是会开一个 class, new class 出 object
但是 JavaScript 却不同, JavaScript 更多的是直接开一个 object, 比较少开 class. 而且 JavaScript 的 Object 有很多好玩的特性.
Prototype
原型链是很好的一种设计, 但坑也不少. JavaScript class 的继承就是靠 prototype 完成的, 当然 prototype 其实还可以做其它的事情.
Function
JavaScript 的 function 除了是 function, 还带有 class 的特性. 比如它可以 new
Class
JavaScript 的 class 是假的, 语法糖来的. 它外部和 C# 的 class 特性一样, 但背地里它是用 Function Prototype Object 的特性实现的.
Object
create object
const obj = { name: 'Derrick', age: 11, };
get / set property value
console.log(obj.name); // get property value obj.age = 13; // set property value
new / delete property
对象的属性是可以动态添加和删除的
obj.lastName = 'Yam'; // new property delete obj.lastName; // delete property
getter in object
甚至可以直接定义 getter 属性
const obj = { firstName: 'Derrick', lastName: 'Yam', get fullName() { return `${this.firstName} ${this.lastName}`; }, };
Object.defineProperty
对象的 property 是有一些 config 的. 通过 defineProperty 来配置.
Object.defineProperty(obj, 'age', { writable: true, enumerable: true, configurable: true, value: 11, get() { return 11; }, set(value) {}, });
writable 表示属性是否可以 set value. 如果设置成 false, 当调用 obj.age = 15 的时候, age 的值不会有任何改变 (它虽然不会报错, 但操作被无视掉了)
enumerable 表示属性是否能被遍历 (下面会讲到如何遍历属性), false 就是说不能被遍历
configurable 表示是否可以被 defineProperty. 一旦设置成 false 就不能改回来了. lock 死掉了.
get 就是 getter 方法
set 就是 setter 方法. e.g. obj.age = 15 的时候触发, value 就是 15
value 就是属性值咯
注1: writable, enumerable, configurable 默认值都是 false, value 是 undefined
注2: Cannot both specify accessors and a value or writable attribute (有 getter setter 就不可以 set value 和 writable)
getter setter
完整的 getter setter 长这样
Object.defineProperty(obj, '_age', { writable: true, enumerable: false, // 不允许被遍历 configurable: true, value: 0, }); Object.defineProperty(obj, 'age', { enumerable: true, configurable: true, get() { console.log('拦截 getter'); return this._age; }, set(value) { console.log('拦截 setter'); this._age = value; }, }); obj.age = 15; console.log(obj.age);
_age 是 "private 属性"
遍历 object
Object.keys(obj); // ['firstName', 'lastName', 'fullName'] Object.entries(obj); // [['firstName', 'Derrick'], ['lastName', 'Yam'], ['fullName', 'Derrick Yam']] for (const [key, value] of Object.entries(obj)) {}
keys 可以获取所有属性名
entries 可以获取属性和值 (es2017 才有)
注1: 属性必须是 enumerable: true 才能被遍历
注2: 属性是 Symbol 的话, 是无法被遍历的
注3: object 不是 iterator 所以不能直接使用 for of obj, 必须用 Object.keys 或者 Object.entries
如果想获取到 enumerable: false 的属性或者 Symbol 属性, 需要使用下面这 2 个方法.
Object.getOwnPropertyNames(obj);
Object.getOwnPropertySymbols(obj);
Prototype
用 Chrome DevTools 打开 Object 会发现它有一个特别的属性叫 Prototype
原型链的查找过程
prototype 有啥用呢?
prototype 也是一个对象, 可以这么理解, 一个对象里头, 又连着另一个对象.
举例: 对象 child 连着对象 parent
当 child.prop 的时候, JavaScript 会先去找 child 对象中是否有 prop 这个属性. 有的话就返回它的值.
如果没有这个属性, 那么它会接着去 prototype parent 寻找这个属性. 有的话就返回它的值.
如果还是没有那就再去 parent 的 prototype 找, 一直找直到某个 prototype = null 才结束.
指定 Prototype
const obj = {}; const objPrototype = Object.getPrototypeOf(obj); console.log(objPrototype === Object.prototype); // true console.log(Object.getPrototypeOf(Object.prototype)); // null
当我们创建一个对象时, 它默认就有连着一个 prototype. 那就是 Object.prototype
通过 Object.getPrototypeOf 可以获取到某个对象的 prototype.
Object.prototype 定义了一些 hasOwnProperty 之类的方法. 所以虽然 obj = {} 看上去是空的, 但却可以调用 obj.hasOwnProperty 方法. 就是因为原型链查找的缘故.
通过 Object.create 可以在创建对象时指定其 prototype
const myPrototype = { age: 11, }; const obj = Object.create(myPrototype); console.log(obj.age); // 11
原型链: obj > myPrototype > Object.prototype
动态替换 prototype
Object.setPrototypeOf(obj, myPrototype);
遍历 prototype 属性
上面介绍的 Object.keys, Object.entries 都只能遍历当前对象的属性.
for in 除了可以遍历对象属性还可以遍历出所有 prototype 链上的属性 (必须是 enumerable: true)
for (const key in obj) {
const isFromPrototype = !obj.hasOwnProperty(key); // 判断是当前对象还是 prototype 属性
}
小心坑
看注释
const parent = { age: 11, fullName: { firstName: '', lastName: '', }, }; const child = Object.create(parent); console.log(child.age); // read from parent child.age = 15; // 注意: 这里不是 set property to parent 而是 new property to child console.log(child.age); // 注意: read from child console.log(child.fullName.firstName); // read from parent child.fullName.firstName = 'Derrick'; // 注意: 这里是 set property to parent, 而不是 new property to child 哦 (和上面不同) console.log(child.fullName.firstName); // 注意: still read from parent
当前 AngularJavaScript 的 $scope 就使用了 prototype 概念, 导致了许多了在赋值的时候经常出现 bug. 原因就是上面这样.
你 get from prototype 不代表就是 set to prototype, 因为属性是可以动态添加的. 你以为是 set, 结果变成了 new property
Function
介绍
JavaScript 的 Function 有双重身份
第一是作为函数
第二是作为 class
es6 以后.
作为函数的部分可以用箭头函数取代
作为 class 的部分, 可以用 class 取代
所以 es6 以后比较少看到 function 这个字眼了, 也避免了混乱.
函数参数与返回
函数的参数是没有规定的, 看看下面的例子
function myFunction(arg0 = 'default value', arg1, arg2) { console.log(arguments); // [undefined, 2, 3, 4, 5]; console.log(arg0); // default value return 'value'; } myFunction(undefined, 2, 3, 4, 5);
函数只声明了 3 个参数, 但是调用的时候你可以传入 5 个, 传多传少都可以.
传少获取的时候值是 undefined, 传多可以忽略, 也可以通过 arguments 对象获取信息.
参数 + default value = optional 参数. 而且它不像 C# 那样强制你必须把 optional 放后面.
JS 在调用函数时, 传入 undefined 就表示没有传入一样. 下面 2 句是等价的
myFunction();
myFunction(undefined, undefined, undefined, undefined);
返回值也是 optional 的, 没有返回, 调用者会获取到 undefined 值.
总结: 参数, 返回值 undefined 就表示没有传入参数和没有返回值.
es6 函数参数
arguments 虽然厉害但是也相对不好理解. es6 会用 rest parameters 来取代 arguments 的作用.
function myFunction(arg0 = 'default', ...otherArgs) { console.log(arg0); // default console.log(otherArgs); // [1, 2] return 'value'; } myFunction(undefined, 1, 2);
另外箭头函数内是没有 arguments 对象的哦.