JavaScript 高级程序设计 - 第 8 章 理解对象 学习笔记
本章内容量挺大的,因此笔记的话我也会分成 3-4 个部分去写,想要理解的细一点,顺便之后回顾的时候不会看的套类。
本章主要就是了解一下和理解一下什么是对象,包括 Object 上的 properties 和 mixin。
在 JavaScript 中,所有的存在(除了 primitive value)之外,都是对象,通常意义上来说,创建对象的方式有两种:
const person =newObject();
person.name ='John';
person.age =28;
person.sayHi=function(){
console.log(`Hello from ${this.name}`);};const person2 ={name:'Alex',age:18,sayHi:function(){
console.log(`Hello from ${this.name}`);},};
二者在实现上没有什么区别,只是后者使用 object literal(对象字面量)更加的方便简洁。
attributes of property
以上面的实现为例,
name
,
age
和
sayHi
是
person
的 数据属性(data properties),并且同样也可以使用
Object.defineProperty()
的方法去赋值,如:
const person2 ={name:'Alex',age:18,sayHi:function(){
console.log(`Hello from ${this.name}`);},};
Object.defineProperty(person2,'greet',{value:function(){
console.log('Hello from the other side');},});
person2.greet();
而
defineProperty
中传的第三个值,就是该 property 的 attributes。
interfacePropertyDescriptor{
configurable?:boolean;
enumerable?:boolean;
value?:any;
writable?:boolean;
get?():any;
set?(v:any):void;}/**
* Marker for contextual 'this' type
*/interfaceThisType<T>{}defineProperty<T>(o:T, p: PropertyKey, attributes: PropertyDescriptor & ThisType<any>):T;
PropertyDescriptor
中包含的,就是所有可能存在的属性,现在的规范是使用中括号括起来,如
[[Configurable]]
,不过说的都是一个东西。
- valuevalue 为 property 实际的值
- writable
person2.name ='John';Object.defineProperty(person2,'name',{writable:false,});console.log(person2.name);person2.name ='Dick';console.log(person2.name);Object.defineProperty(person2,'name',{writable:true,value:'Rewritten',});console.log(person.name);
结果如下:可以看到,当writable
设置为 false 之后,重新对 property 赋值也不无法对其进行修改。就算是之后重新修改writable
也不行非严格模式下这样的执行会被忽略,严格模式下这么做会报错 - configurableconfigurable 是一个 boolean,当一个属性的 configurable 被设置成 false,意味着当前的属性无法被使用
defineProperty
被重新定义、无法被删除、无法修改configurable
,enumerable
和writable
。如果writable
的值为true
,那么value
还是可以被修改的,否则这个对象就被彻底 freeze,再也无法修改。Object.defineProperty(person2,'age',{configurable:false,writable:false,});Object.defineProperty(person2,'age',{enumerable:false,});
顺便,Object.freeze()
等同于将configurable
和writable
设为 false。 - enumerable这个 attribute 代表了当前属性是否会出现在
for...in
中for(const key in person2){ console.log(key);}Object.defineProperty(person2,'name',{enumerable:false,});for(const key in person2){ console.log(key);}
- getgetter 是另一个相对而言比较新的内容,它主要的目的就是为了处理一些返回值,比如:
const person2 ={_id:1,name:'Alex',age:18,sayHi:function(){ console.log(`Hello from ${this.name}`);},getid(){return`id is: ${this._id}`;},};console.log(person2.id);
这个案例中,person2 没有直接声明id
property,但是可以通过 getter 返回一个id
property,其原因在于,它的具体实现为:const person =newObject();person.name ='John';person.age =28;person._id =2;person.sayHi=function(){ console.log(`Hello from ${this.name}`);};Object.defineProperty(person,'id',{get:function(){returnthis._id;},});console.log(person.id);
使用 getter 还有另一个原因,放到下面 setter 一起说。 - setsetter 的作用和 getter 相似,其用法如下:
const person2 ={_id:1,name:'Alex',age:18,sayHi:function(){ console.log(`Hello from ${this.name}`);},getid(){return`id is: ${this._id}`;},/** * @param {string} arg */setrealName(arg){this.name ='my name is: '+ arg;},};
同样也可以用defineProperty
去实现。JS 中出现 getter 和 setter 的另一个原因就在于,ES6 之前 JS 中是没有私有属性的。于是社区约定俗成了一个规范,就是在 property name 中添加_
代表不应该被访问的私有属性,要去访问和修改这个私有属性,就需要通过 getter 和 setter 去实现。这样实际保存值的 property,即this.__privateValue
,就不会直接被用户所访问。
定义的方法为
defineProperty
,获取 properties 的方法有两个:
Object.getOwnPropertyDescriptor()
和
Object.getOwnPropertyDescriptors()
。前者需要知道 property 的名称,后者则会直接返回当前对象上的 properties。
console.log(Object.getOwnPropertyDescriptor(person2,'name'));
console.log(Object.getOwnPropertyDescriptors(person2));
mixin
mixin 是一个被提到很多次的 concept,如果找一下的话,会发现 vue、scss 等都会发现直接有 mixin 这一属性或是函数。
ES6 也提供了
Object.assign()
的静态方法,它会遍历接收到的参数,然后将参数中所有可枚举 (
Object.propertyIsEnumerable()
为
true
的 properties) 和 自由(
Object.hasOwnProperty()
为
true
的 properties) 复制到目标属性上。
这可以很方便的实现 mixin,以下面代码为例:
const writeFile ={write:function(){
console.log('Assume I can read file.');},};const scanFile ={scan:function(){
console.log('Assume I can scan file');},};const readWriter = Object.assign({}, readFile, writeFile);
console.log(readWriter);
readFile.read();const readWriterScanner = Object.assign({}, readWriter, scanFile);
readWriterScanner.scan();
可以看到,使用 mixin 提供更加灵活的语法,并且直接地满足了 SOLID 中的 single responsibility principle 和 interface segregation principle 两条原则。
liskov substitution principle 的判定我觉得稍微有点麻烦,逻辑上来说它要满足的事子类和父类,实现上来说
Object.assign()
完成了 shallow copy……
因为 JS 没有办法提供很好的静态检查,因此当使用 mixin 还会有其他的一些问题,如属性被重写的问题,依旧用上面的分代码为例,这里添加一点新的实现:
const fileOperator ={read:function(){
console.log('I read file randomly.');},scan:function(){thrownewError('scan file fail.');},};const errorFileOperator = Object.assign({}, scanFile, fileOperator);
errorFileOperator.scan();
可以看到,在这个 mixin 中,
scan
被
fileOperator
重写了,所以这里的输出结果不是做一个 log,而是抛出异常:
搭配上 JS 中不存在的类型见擦,当项目规模比较大的情况下,尤其是出现 mixin 套用 mixin 的情况,property 的管理会成为一个比较麻烦的事情。
顺便,只是针对
Object.assign()
的话,它是没有回滚的概念的,也就是说,如果操作中出现一些异常,可能会出现一个部分实现复制的新对象。
总结一下 mixin 的优点和缺点。
优点:
- 提高代码的复用性
- 提高代码的灵活性
- 模块化
- composition over inheritance这是一个 OOP 常用的概念,组成偏向于继承,这样可以做到比较轻松的修改组成部分的代码,而不用担心影响到继承部分的功能
缺点:
- 命名冲突假设说多个对象中都存在
id
这个属性,那么 JS 就无法 capture 这个问题 - 排序很重要假设说多个对象中都存在
id
这个属性,那么最后一个 id 将会重写其他的 id - 直接依赖就不太像 spring 一样下面有底层的 DI 管理,很多时候要实现 mixin 还是得手动 cv,这样就会造成直接依赖(必须要知道对象,才能够实现 assign)
- 提高代码复杂性
reference
- Object.freeze()
- SOLID,面向对象设计五大基本原则
版权归原作者 GoldenaArcher 所有, 如有侵权,请联系我们删除。