1.属性设置和屏蔽
我们首先来完整讲解一下给对象设置属性的过程
ini
myObject.foo = 'bar'
如果myObject对象中包含foo的普通数据访问属性(对象的一些理解 - 掘金 (juejin.cn)),这条赋值语句只会修改已有的属性值。
如果foo即出现在myObject中也出现在myObject的原型链上层,那么就会发生屏蔽。myObject.foo会选择原型链中最底层的foo属性。
如果foo只出现在myObject.foo原型链上层会出现三种情况:
- 如果原型链上层存在名为foo的普通数据访问属性,并且没有被标记为只读(writable:false),那么就会在myObject中添加一个名为foo的新属性,这是屏蔽属性
- 如果原型链上层存在名为foo的普通数据访问属性,并且被标记为只读(writable:false)。在严格模式下会报错,非严格模式下会发生屏蔽。(只读属性会阻止原型链下层隐式创建同名属性,是为了模拟类属性的继承,可以看成这个属性会被myObject继承,这样一来myObject的foo属性也是只读了)
- 如果原型链上层存在名为foo并且是一个setter,那么就一定会调用这个setter。foo不会被添加到myObject中,也不会重新定义foo这个setter
如你所见,三种情况只有一种是发生了属性屏蔽的。如果希望在第2、3种情况下也屏蔽foo,那就不能使用=操作符来赋值,而是使用Object.defineProperty()来向myObject添加foo
javascript
const anotherObject = {
a:2
}
var myObject = Object.create(anotherObject)
myObject.a++
anotherObject.a //2
myObject.a//3
这里的myObject.a++通过委托找到anotherObject.a的值为2,但是myObject.a++相当于myObject.a = myObject.a + 1,这里通过=运算符将3这个值赋给了myObject,发生了属性屏蔽。如果想要anotherObject.a的值增加只能通过anotherObject.a++
2.'类'函数
实际上,JavaScript中的'类'和面向类的语言中是不同的,在面向类的语言中,类可以被复制(或者说实例化)很多次,就像用模具制作东西一样。
在JavaScript中,我们通过new Foo()生成一个新对象时,这个新对象的原型链上就会被关联到Foo.prototype对象。这两个对象是互相关联的,我们没有初始化一个类,也没有从'类'中复制任何行为到一个对象中,我们只是让这两个对象互相关联。
所以我们常说的'原型继承'其实不太准确,继承意味着复制操作,JavaScript(默认)并不会复制对象属性。相反,JavaScript会在两个对象之间创建一个关联,这样一个对象可以通过委托访问另一个对象的属性和函数。委托这个术语更准确地描述了对象中的关联机制
3.(原型)继承
我们已经看过许多JavaScript程序中常用的模拟类行为的方法,但是如果没有'继承',JavaScript的类只是一个空架子
javascript
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 = Object.create(Foo.prototype)
Bar.prototype.myLabel = function(){
return this.label
}
var a = new Bar('a','obj a')
a.myName() //'a'
a.myLabel() //'obj a'
这里我们为了使用到Foo.prototype的myName方法,使用了Bar.prototype = Object.create(Foo.prototype)。这段代码的意思是:Object.create()会凭空创造一个'新'对象并将新对象内部的__proto__值关联到你指定的对象(本例中是Foo.prototype)。换句话是,创建一个新的Bar.prototype对象并把它关联到Foo.prototype对象,即此时Foo.prototype.__ proto__ === Foo.prototype
注意,以下两种方式是常见的错误做法,实际上他们都存在一些问题
javascript
//和你想要的机制不一样
Bar.prototype = Foo.prototype
//基本上满足你的需求,但是可能会产生一些副作用
Bar.prototype = new Foo()
Bar.prototype = Foo.prototype并不会让两个对象产生关联,它只是让Bar.prototype 直接引用 Foo.prototype对象,但我们执行类似Bar.prototype.myLabel时会直接修改Foo.prototype对象本身。显然这不是你想要的结果,否则根本不需要Bar对象,直接使用Foo就好。
Bar.prototype = new Foo()的确会让Bar.prototype 关联到 Foo.prototype,但是这里使用了Foo()函数的'构造函数调用',如果Foo有一些副作用(比如输出日志,给this添加数据属性),就会影响到Bar()的后代,后果不堪设想。
因此要创建一个合适的关联对象,我们要使用Object.create(),这样做的唯一缺点是需要创建一个新对象然后旧对象会被垃圾回收,不能直接修改已有的默认对象
4.行为委托
在软件架构中,大多数开发者理所应当的地认为类是唯一的代码组织方式,但是现在我们知道了更强大的设计模式:行为委托。我们并不像类一样复制父类的属性和方法,只是遇到没有找到的属性或者方法,我们会委托给原型链上的对象。
行为委托更像是兄弟关系,互相委托,而不是父类和子类的关系,原型链的机制本质上就是行为委托机制。我们可以在JavaScript中努力实现类机制(继承、混入、多态),也可以拥抱原型链的委托机制