@by Ruth92(转载请注明出处)

第6章:代码复用模式

GoF 在其著作中提出的有关创建对象的建议原则:

—— 优先使用对象组合,而不是类继承。

  • 传统模式:使用类继承;
  • 现代模式:“类式继承”,不以类的方式考虑。

代码重用才是最终目的,继承只是实现这一目标的方法之一。

☞ 使用类式继承时的预期结果
// 父构造函数
function Parent(name) {
this.name = name || 'Adam';
} Parent.prototype.say = function() {
return this.name;
} // 空白的子构造函数
function Child(name) {} // 继承
inherit(Child, Parent); // 需要自己实现

类式继承#1——默认模式

function inherit(C, P) {
C.prototype = new P();
} var kid = new Child();
kid.name = 'Patrick';
kid.name; // 'Patrick'
kid.say(); // 'Adam'

需要记住:原型属性应该指向一个对象,而不是一个函数,因此它必须指向一个由父构造函数所创建的实例(一个对象),而不是指向构造函数本身。

缺点:1)同时继承了两个对象的属性,即添加到 this 的属性以及原型属性。在绝大多数时候,并不需要这些自身的属性,因为它们很可能是指向一个特定的实例,而不是复用。2)不支持参数传递。

类式继承#2——借用构造函数

// 父构造函数
function Parent(name) {
this.name = name || 'Adam';
} Parent.prototype.say = function() {
return this.name;
} // 空白的子构造函数
function Child(name) {
Parent.apply(this, arguments);
} var kid = new Child('Patrick');
kid.name; // 'Patrick'
typeof kid.say; // 'undefined'

优点:解决了从子构造函数到父构造函数的参数传递问题,可获得父对象自身成员的真实副本。

缺点:无法从原型中继承任何东西,并且原型也仅是添加可重用方法及属性的位置,它并不会为每个实例重新创建原型。

☞ 通过借用构造函数实现多重继承

function Cat() {
this.legs = 4;
this.say = function() {
return "meaowww";
}
} function Bird() {
this.wings = 2;
this.fly = true;
} function CatWings() {
Cat.apply(this);
Bird.apply(this);
} var jane = new CatWings();
console.log(jane);

类式继承#3——借用和设置原型

主要思想:结合前两种模式,先借用构造函数,然后设置子构造函数的原型使其指向一个构造函数创建的新实例。

function Chlid(name) {
Parent.apply(this, arguments);
} Child.prototype = new Parent(); // 第一次调用 var kid = new Chlid('Patrick'); // 第二次调用
kid.name; // 'Patrick'
kid.say(); // 'Patrick'
delete kid.name;
kid.say(); // 'Adam'

缺点:需要两次调用父构造函数(类式继承模式#3)。

类式继承#4——共享原型

【经验法则】:可复用成员应该转移到原型中而不是放置在 this 中。

因此,出于继承的目的,任何值得继承的东西都应该放置在原型中实现。所以,可以仅将子对象的原型与父对象的原型设置为相同的即可。

function inherit(C, P) {
C.prototype = P.ptototype;
}

优点:不需要两次调用父构造函数。这种模式能够提供简短而迅速的原型链查询,这是由于所有的对象实际上共享了同一个原型。

缺点:如果在继承链下方的某处存在一个子对象或者孙子对象修改了原型,它将会影响到所有的父对象和祖先对象。

类式继承#5——临时构造函数(代理函数或代理构造函数模式)

通过断开父对象与子对象的原型之间的直接链接关系,从而解决共享同一个原型所带来的问题,同时还能够继续受益于原型链带来的好处。

function inherit(C, P) {
// 空白函数F():充当子对象和父对象之间的代理
var F = function() {};
F.prototype = P.prototype;
C.prototype = new F(); // 存储超类
C.uber = P.ptototype; // 重置构造函数指针
C.prototype.constructor = C;
} var kid = new Child(); /**
* 优化:避免在每次需要继承时都创建临时(代理)构造函数
* 使用即时函数,并且在比闭包中存储代理函数
*/
var inherit = (function() {
var F = function() {}; return function(C, P) {
F.prototype = P.prototype;
C.prototype = new F();
C.uber = P.prototype; // 原始父对象的引用
C.prototype.constructor = C; // 重置构造函数的指针
}
}());

☞ Klass
var klass = function(Parent, props) {
var Child, F, i; // 1. 新构造函数
Child = function() {
if (Child.uber && Child.uber.hasOwnProperty("__construct")) {
Child.uber.__construct.apply(this, arguments);
}
if (Child.prototype.hasOwnProperty("__construct")) {
Child.prototype.__construct.apply(this, arguments);
}
}; // 2. 继承
Parent = Parent || Object;
F = function() {};
F.prototype = Parent.prototype;
Child.prototype = new F();
Child.uber = Parent.prototype;
Child.prototype.constructor = Child; // 3. 添加实现方法
for (i in props) {
if (props.hasOwnProperty) {
Child.prototype[i] = props[i];
}
} // 返回该 'class'
return Child;
} // 测试
var Man = klass(null, {
__construct: function(what) {
console.log("Man's constructor");
this.name = what;
},
getName: function() {
return this.name;
}
}); var SuperMan = klass(Man, {
__construct: function(what) {
console.log("SuperMan's constructor");
},
getName: function() {
var name = SuperMan.uber.getName.call(this);
return "I am " + name;
}
}); var clark = new SuperMan('Clark Kent');
clark.getName(); // 'I am Clark Kent'
☞ 原型继承
/**
* 使用字面量创建父对象
*/
var parent = {
name: 'Papa'
}; function object(o) {
function F() {};
F.prototype = o;
return new F();
} // 新对象
var child = object(parent); /**
* 使用构造函数创建父对象
*/
function Person() {
this.name = 'Adam';
} Person.prototype.getName = function() {
return this.name;
} /**
* 继承方法1
*/
var papa = new Person();
var kid = object(papa);
kid.getName(); // 'Adam' /**
* 继承方法2:
* 仅继承现有构造函数的原型对象
*/
var kid2 = object(Person.prototype); typeof kid.getName; // 'function'
typeof kid.name; // 'undefined' /**
* 继承方法3:
* 使用 Object.create(),可以扩展新对象自身的属性,并返回该新对象
*/
var child = Object.create(parent);
var child2 = Object.create(parent, {
age: {value: 2}
})
child2.hasOwnProperty('age'); // true

通过复制属性实现继承

由于 JavaScript 中的对象时通过引用传递的,在使用 浅复制 的时候,如果改变了子对象的属性,并且该属性恰好是一个对象,那么这种操作表示也正在修改父对象。

使用 深复制 可以创建对象的真实副本。jQuery库中的 extend() 可创建深度复制的副本。

/**
* 浅复制
*/
function extend(parent, child) {
var i;
child = child || {};
for (i in parent) {
if (parent.hasOwnProperty(i)) {
child[i] = parent[i];
}
}
return child;
} var dad = {
counts: [1, 2, 3],
reads: {paper: true}
};
var kid = extend(dad);
kid.counts.push(4);
dad.counts.toString(); // '1,2,3,4'
dad.reads === kid.reads; // true /**
* 深复制
*/
function extendDeep(parent, child) {
var i,
toStr = Object.prototype.toString,
astr = '[object Array]'; child = child || {}; for (i in parent) {
if (parent.hasOwnProperty(i)) {
if (typeof parent[i] === 'object') {
child[i] = (toStr.call(parent[i]) === astr) ? [] : {};
extendDeep(parent[i], child[i]);
} else {
child[i] = parent[i];
}
}
}
return child;
} // 测试
var dad = {
counts: [1, 2, 3],
reads: {paper: true}
};
var kid = extendDeep(dad); kid.counts.push(4);
kid.counts.toString(); // '1,2,3,4'
dad.counts.toString(); // '1,2,3' dad.reads === kid.reads; // false
kid.reads.paper = false; kid.reads.web = true;
dad.reads.paper; // true
☞ 混入——针对通过属性复制实现继承的思想做的进一步扩展

mix-in 模式并不是复制一个完整的对象,而是从多个对象中复制出任意的成员并将这些成员组合成一个新的对象。

实现:遍历每个参数,并复制出传递给该函数的每个对象中的每个属性。

function mix() {
var arg, prop, child = {};
for (arg = 0; arg < arguments.length; arg += 1) {
for (prop in arguments[arg]) {
if (arguments[arg].hasOwnProperty(prop)) {
child[prop] = arguments[arg][prop];
}
}
}
return child;
} var cake = mix(
{egg: 2, large: true},
{butter: 1, salted: true},
{flour: '3 cups'},
{sugar: 'sure!'}
);
☞ 借用方法
/**
* 借用数组的方法
*/
function f() {
var args = [].slice.call(arguments, 1, 3);
// 或者
// var args = Array.prototype.slice.call(arguments, 1, 3);
return args;
} f(1, 2, 3, 4, 5, 6); // [2, 3] /**
* 借用和绑定
*/
var one = {
name: 'object',
say: function(greet) {
return greet + ', ' + this.name;
}
}; one.say('hi'); // 'hi, object' var two = {
name: 'another object'
}; one.say.apply(two, ['hello']); // 'hello, another object'
one.say.call(two, 'bye'); // "bye, another object"
var say = one.say.bind(two, 'bind');
say(); // "bind, another object" /**
* this都指向了全局对象
*/
// 给变量赋值
// `this` 将指向全局变量
var say = one.say;
say('hoho'); // 'hoho, ' // 作为回调函数
var yetanother = {
name: 'Yet another object',
method: function(callback) {
return callback('Hola');
}
};
yetanother.method(one.say); // 'Hola, ' /**
* 解决办法:bind()函数
*/
function bind(o, m) {
return function() {
return m.apply(o, [].slice.call(arguments));
};
} var twosay = bind(two, one.say);
twosay('yo'); // 'yo, another object'; /**
* Function.prototype.bind()实现
*/
if (typeof Function.prototype.bind === 'undefined') {
Function.prototype.bind = function(thisArg) {
var fn = this,
slice = Array.prototype.slice,
args = slice.call(arguments, 1); return function() {
return fn.apply(thisArg, args.concat(slice.call(arguments)));
};
};
} var twosay2 = one.say.bind(two);
twosay2('Bonjour'); // 'Bonjour, another object' var twosay3 = one.say.bind(two, 'Enchante');
twosay3(); // 'Enchante, another object'

《JavaScript模式》第6章 代码复用模式的更多相关文章

  1. 《JavaScript 模式》读书笔记(6)— 代码复用模式2

    上一篇讲了最简单的代码复用模式,也是最基础的,我们普遍知道的继承模式,但是这种继承模式却有不少缺点,我们下面再看看其它可以实现继承的模式. 四.类式继承模式#2——借用构造函数 本模式解决了从子构造函 ...

  2. javascript代码复用模式(二)

    前面说到,javascript的代码复用模式,可分为类式继承和非类式继承(现代继承).这篇就继续类式继承. 类式继承模式-借用构造函数 使用借用构造函数的方法,可以从子构造函数得到父构造函数传任意数量 ...

  3. 深入理解JavaScript系列(46):代码复用模式(推荐篇)

    介绍 本文介绍的四种代码复用模式都是最佳实践,推荐大家在编程的过程中使用. 模式1:原型继承 原型继承是让父对象作为子对象的原型,从而达到继承的目的: function object(o) { fun ...

  4. javascript代码复用模式

    代码复用有一个著名的原则,是GoF提出的:优先使用对象组合,而不是类继承.在javascript中,并没有类的概念,所以代码的复用,也并不局限于类式继承.javascript中创建对象的方法很多,有构 ...

  5. 深入理解JavaScript系列(45):代码复用模式(避免篇)

    介绍 任何编程都提出代码复用,否则话每次开发一个新程序或者写一个新功能都要全新编写的话,那就歇菜了,但是代码复用也是有好要坏,接下来的两篇文章我们将针对代码复用来进行讨论,第一篇文避免篇,指的是要尽量 ...

  6. 《JavaScript 模式》读书笔记(6)— 代码复用模式3

    我们之前聊了聊基本的继承的概念,也聊了很多在JavaScript中模拟类的方法.这篇文章,我们主要来学习一下现代继承的一些方法. 九.原型继承 下面我们开始讨论一种称之为原型继承(prototype ...

  7. javascript代码复用模式(三)

    前面谈到了javascript的类式继承.这篇继续部分类式继承,及一些现代继承. 类式继承模式-代理构造函数 这种模式通过断开父对象与子对象之间原型之间的直接链接关系,来解决上次说到的共享一个原型所带 ...

  8. javascript-代码复用模式

    代码复用模式 1)使用原型继承            函数对象中自身声明的方法和属性与prototype声名的对象有什么不同:      自身声明的方法和属性是静态的, 也就是说你在声明后,试图再去增 ...

  9. Atitit 代码复用的理解attilax总结

    Atitit 代码复用的理解attilax总结 1.1. 继承1 1.1.1. 模式1:原型继承1 1.1.2. 模式2:复制所有属性进行继承 拷贝继承1 1.1.3. 模式3:混合(mix-in)1 ...

随机推荐

  1. bootstrap-validator验证问题总结

    bootstrap-validator是一个优秀的验证器,使用中遇到如下问题,总结如下: 1.<button type="submit" name="submit2 ...

  2. Claims Identity

    using System;using System.Collections.Generic;using System.Linq;using System.Security.Claims;using S ...

  3. Windows下面安装easy_install报UnicodeDecodeError: 'ascii' codec can't decode byte解决方法

    在运行python ez_setup.py install后, 发现是在下载并解压setuptools-2.1,并运行setup.py时出现如下错误: 提示D:\Python27\lib\mimety ...

  4. mac 安装 Scrapy

    一.pip安装Scrapy 运行命令 sudo pip install Scrapy(不带sudo 可能会出现 Permission denied) 然后 pip freeze 查看已经有 scrap ...

  5. json对象,数组,字符串总结

    关于json对象,数组,字符串的总结 什么是json? JSON(JavaScript Object Notation)  一种轻量级的数据交换格式,JSON采用完全独立于语言的文本格式...(来自百 ...

  6. 运用js解决java selenium元素定位问题

    一.解决定位并操作uneditable元素 尝试了通过id,xpath等等定位元素后点击都提示Element is not clickable at point 再看了下可以click的元素发现上面有 ...

  7. ijg库解码超大型jpeg图片

    1. ijg库解码超大型jpeg图片(>100M)的时候,如何避免内存溢出. 采用边解码边压缩的策略,每次解码一行或者若干行图片数据,然后对于这些解码的数据,进行DQT(量化处理,过滤掉高频的数 ...

  8. Google API在线生成二维码的方法

    1.先看一个实例,是用Google API生成西部e网的网站地址www.weste.net二维码的方法: http://chart.apis.google.com/chart?cht=qr&c ...

  9. jpype调用jar

    import easyguiimport osfrom jpype import * jarpath = "d:\jar"print "jarPath: %s" ...

  10. iOS开发拓展篇—音频处理(音乐播放器3)

    iOS开发拓展篇—音频处理(音乐播放器3) 说明:这篇文章主要介绍音频工具类和播放工具类的封装. 一.控制器间数据传递 1.两个控制器之间数据的传递 第一种方法:self.parentViewCont ...