前言
嘿,骚年,你是否还在使用JavaScript的"伪"类应付了事,而没有使用更简洁的"行为委托"设计模式?你是否听说连接到其他对象的对象?
如果都没有,那就接着往下看吧。
属性描述符
首先来了解一点准备知识,什么是属性描述符呢?即writable(可写)、enumerable(可枚举)、configurable(可配置)。我们可以使用Object.defineProperty()来添加一个新属性或者修改一个已有属性(configurable为true)并对特性进行设置
writable
writable决定是否可以修改属性的值
var obj = {}
Object.defineProperty( obj, 'a', {
value: 2,
writable: false, //不可写
configurable: true,
enumerable: true
})
obj.a = 3;
console.log(obj.a); //2
如上,修改a失败,因为我们配置了writable:false。如果在严格模式下,会直接报错。
configurable
configurable决定属性是否可以被配置
var obj = {
a: 2
}
obj.a = 3;
console.log(obj.a); //3
Object.defineProperty( obj, 'a', {
value: 4,
writable: true,
configurable: false, //不可配置
enumerable: true
});
console.log(obj.a) //4
obj.a = 5;
console.log(obj.a) //5
Object.defineProperty( obj, 'a', {
value: 4,
writable: true,
configurable: true, //不可配置
enumerable: true
}); //TypeError
不管是否处于严格模式,尝试修改一个不可配置的属性描述符都会出错;除了无法修改,configurable还会禁止删除这个属性。
enumerable
控制属性是否会出现在对象的属性枚举中
var obj = {
a: 1
}
Object.`defineProperty`( obj, 'b', {
value: 2,
writable: true,
configurable: true,
enumerable: true
});
Object.`defineProperty`( obj, 'c', {
value: 3,
writable: true,
configurable: true,
enumerable: false
})
for(let i in obj) {
console.log(obj[i])
} // 1, 2
如上,c无法被枚举到
原型
JavaScript中的对象都有一个[[Prototype]]内置属性,其实就算对于其他对象的引用。思考下面代码:
function Foo(name) {
this.name = name
}
function Bar(name, label) {
Foo.call(this, name)
this.label = label
}
Bar.prototype = Object.create( Foo.prototype )
console.log(Bar instanceOf Foo) //true
这段代码的核心部分就算语句Bar.prototype = Object.create( Foo.prototype )。调用Object.create()会凭空创建一个"新"对象并把新对象内部的[[Prototype]]关联到指定的对象(Foo.prototype).即创建一个新的Bar.prototype对象并把它关联到Foo.prototype。
行为委托
首先看看面向类和面向委托的理论设计:
类理论
如果使用类,通常会创建一个父类,然后子类继承父类,且类鼓励你去重写它的方法。
下面是伪代码
class Task {
id;
//构造函数Task()
Task(ID) {id = ID;}
outputTask() { output(id); }
}
class XYZ inherits Task {
label;
//构造函数XYZ()
XYZ(ID, Label) { super(ID); label = label; }
outputTask() { super(); output( label );}
}
class ABC inherits Task { .... }
现在你需要实例化子类XYZ的一些副本然后使用实例来执行"XYZ"。这些实例有Task定义的公共部分和XYZ定义的特殊行为。同理ABC也一样。
委托理论
首先需要定义一个Task对象,它会有所有任务都能使用的具体方法,然后对于每个任务('XYZ','ABC')你都需要定义一个对象来存储对应的数据和自己特殊行为。然后将这些具体任务对象关联带Task对象上,让它们在需要的时候可以进行委托。
Task = {
setID: function(ID) { this.id = ID; }
outputID: function() { console.log(this.id); }
};
//让XYZ委托Task
XYZ = Object.create(Task)
XYZ.prepareTask = function( ID, Label ) {
this.setID( ID );
this.label = label;
}
//ABC....
可以看出来代码更加简洁并且更加容易懂了吧(桃某人觉得是这样hhhh)
比较思维模型
思维模型要画图,太恼火了,我就直接上代码了,参考《你不知道的JavaScript》(上卷170页)
//面向对象风格
function Foo(who) {
this.me = who;
}
Foo.prototype.identify = function() {
return "I am " + this.me;
}
function Bar(who) {
Foo.call(this, who)
}
Bar.prototype = Object.create( Foo.prototype );
Bar.prototype.speak = function() {
alert( "Hello," + this.identify() + "." );
};
var b1 = new Bar( "b1" );
var b2 = new Bar( "b2" );
b1.spreak();
b2.spreak();
子类Bar继承父类Foo,然后生成了b1和b2两个实例。b1委托Bar.prototype,Bar.prototype委托Foo.prototype。
//对象关联风格
Foo = {
init: function(who) {
this.me = who;
},
identify: function() {
return " I am " + this.me;
}
};
Bar = Object.create( Foo );
Bar.speak = function() {
alert("Hello, " + this.identify() + '.');
};
var b1 = Object.create( Bar )
var b2 = Object.create( Bar )
b1.init( 'b1' )
b2.init( 'b2' )
b1.speak()
b2.speak()
这段代码中我们利用[[prototype]]把b1委托给Bar,并把Bar委托给Foo,与上面的一模一样。但是代码简洁了很多,因为我们只是将对象关联起来了,并不需要模仿类的行为。