对象原型

前言

嘿,骚年,你是否还在使用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,与上面的一模一样。但是代码简洁了很多,因为我们只是将对象关联起来了,并不需要模仿类的行为。

上一篇:“东方明珠”的云上选秀 契合业务构造互联网媒体生态


下一篇:【云栖号案例 | 互联网】Ping++上云 打造百万级交易量的大数据平台