JavaScript中的原型链的机制就是对象之间的关联关系,然后就存在了行为委托的说法
委托行为就意味着某些对象在找不到属性或者方法的情况下,会把这个请求委托给另外一个对象
1. 类与对象
纯JavaScript风格实现类风格的代码,将使用jQuery来操作dom和css
// 父类
// 定义内部属性
function Widget(width, height){
this.width = width || 50;
this.height = height || 50;
this.$elem = null;
}
// 渲染的方法
Widget.prototype.render = function($where) {
if(this.$elem){
// 样式
this.$elem.css({
width: `${this.width}px`,
height: `${this.height}px`
// 放到哪个位置上
}).appendTo($where)
}
}
// 子类
function Button(width,height,label) {
// 调用“super”构造函数
// 丑陋的显式伪多态👇
Widget.call(this, width, height);
this.label = label || "Default";
this.$elem = $("<button>").text(this.label);
}
// 让Button“继承”Widget
Button.prototype = Object.create(Widget.prototype);
// 重写render(...)
Button.prototype.render = function($where){
// “super”调用
// 丑陋的显式伪多态👇,从子类方法中引用父类的基础方法,呸!!丑陋
Widget.prototype.call(this, $where);
this.$elem.click(this.onclick.bind(this)); // 绑定
}
Button.prototype.onclick = function(evt){
console.log(`Button ${this.label} clicked!`)
}
$(document).ready(function(){
var $body = $(document.body);
var btn1 = new Button(125, 30, "Hello");
var btn2 = new Button(150, 40, "world");
btn1.render($body);
btn2.render($body);
})
ES6的class语法糖🍬
class Widget{
constructor(width, height){
this.width = width || 50;
this.height = height || 50;
this.$elem = null;
}
render($where){
if(this.$elem){
// 样式
this.$elem.css({
width: `${this.width}px`,
height: `${this.height}px`
// 放到哪个位置上
}).appendTo($where)
}
}
}
class Button extends Widget {
constructor(width,height,label){
super(width,height);
this.label = label || "Default";
this.$elem = $("<button>").text(this.label);
}
render($where){
super.render($where);
this.$elem.click(this.onclick.bind(this)); // 绑定
}
onclick(evt){
console.log(`Button ${this.label} clicked!`)
}
}
$(document).ready(function(){
var $body = $(document.body);
var btn1 = new Button(125, 30, "Hello");
var btn2 = new Button(150, 40, "world");
btn1.render($body);
btn2.render($body);
})
委托设计模式的
var Widget = {
init:function(width,height){
this.width = width || 50;
this.height = height || 50;
this.$elem = null;
},
insert:function($where){
if(this.$elem){
// 样式
this.$elem.css({
width: `${this.width}px`,
height: `${this.height}px`
// 放到哪个位置上
}).appendTo($where)
}
}
}
var Button = Object.create(Widget);
Button.setup = function(width, height) {
// 委托调用
this.init(width, height);
this.label = label || "Default";
this.$elem = $("<button>").text(this.label);
}
Button.build = function($where){
// 委托调用
this.insert($where);
this.$elem.click(this.onclick.bind(this));
}
Button.click = function(evt){
console.log(`Button ${this.label} clicked!`)
}
$(document).ready(function(){
var $body = $(document.body);
var btn1 = Object.create(Button);
btn1.setup(125,30,"hello");
var btn2 = Object.create(Button);
btn2.setup(130,40,"world");
btn1.build($body);
btn2.build($body);
})
在委托设计模式中,这种对象关联风格代码相比传统原型风格代码有优势的地方,使用类构造函数的话,你需要在同一步骤中实现构造和初始化,然而许多情况把这两步分开像对象关联代码一样更灵活
2. 更好的语法
使用class语法糖,还有简洁方式声明
class Foo(){
getUser() {/*...*/}
}
简洁语法实际上会变成一个匿名函数表达式并赋值给getUser,(function(…){})
一般匿名函数没有name标识符,这会导致:
- 调用栈更难追踪;
- 自我引用(递归,事件的绑定解除等等)更难;
- 代码(稍微)更难理解;
简洁语法方式无法避免的是第2点👆,其他的话,因为是特殊性,也会有给函数对象设置一个内部的name属性
如果需要自我引用的话,就要使用具名函数表达式了
var Foo = {
bar: function(x){
if(x<10){
return Foo.bar(x*2);
}
return x;
},
// 当多个对象通过代理共享函数,使用this绑定的话,就要使用下面👇的name标识符来真正引用
baz: function baz() {
if(x) {
return baz(x*2)
}
return x;
}
}
3. 内省
内省就是检查实例的类型,主要目的是通过创建方式来判断对象的结构和功能
function Foo(){/*...*/}
Foo.prototype...
function Bar(){/*...*/}
Bar.prototype = Object.create(Foo.prototype);
var b1 = new Bar("b1");
// 让Foo和Bar互相关联
Bar.prototype instanceof Foo;// true
Object.getPrototypeOf(Bar.prototype) === Foo.prototype; //true
Foo.prototype.isPrototypeOf(Bar.prototype);// true
// 让b1关联到Foo和Bar
b1 instanceof Foo;// true
b1 instanceof Bar;// true
Object.getPrototypeOf(b1) === Bar.prototype; //true
Foo.isPrototypeOf(b1);// true
Bar.isPrototypeOf(b1);// true
上面👆这种做法,很容易人理解成是继承关系,它们只不过是关联关系而已,但是也只能这样子
还有一种更加脆弱的内省模式,但是在开发者上面用的很多
if(a1.something){
a1.somethinf();
}
这种方法就是,例如需要判断一个对象引用是否是Promise,但是判断的方法是检查对象是否有then()的方法,就可以判断它具有Promise的所有标准行为,但是也有一定的危险性