@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. MyBatis简介

  2. consul模板配置参数值示例

    参看https://github.com/hashicorp/consul-template#examples // This is the address of the Consul agent. ...

  3. nodejs搭建http-server

      很多时候我们都需要搭建一个简单的服务器,部署在IIS,阿帕奇,或者用nodejs,网上很多关于nodejs搭建server的文章,但都是要创建server.js,很麻烦, 在这里我分享一个创建ht ...

  4. mac 安装redis

    一.下载 官网http://redis.io/ (搞不懂为啥被墙) 二.安装 将下载的tar.gz文件复制到 /usr/local 文件夹下 解压 sudo tar -zxvf redis3.1.6. ...

  5. 客户端判断是否为IE9以上版本

    function detectBrowser() { var browser = navigator.appName if(navigator.userAgent.indexOf("MSIE ...

  6. sizeof既是关键字,又是运算符(操作符),但不是函数!

    sizeof是关键字吗 sizeof是关键字,这一点毋庸置疑.你不能将sizeof定义为任何标识符.查看C语言标准文档里的说明: sizeof是运算符(操作符)吗 C语言中,sizeof是运算符(操作 ...

  7. 再说最后一次!关于不再更新SkySRS的理由!

    再说最后一次!关于不再更新SkySRS的理由! https://www.itiankong.net/thread-195937-1-1.html Skyfree 发表于 2012-5-1 14:53: ...

  8. Mysqldump源码分析

    版权声明:本文由王珏原创文章,转载请注明出处: 文章原文链接:https://www.qcloud.com/community/article/261 来源:腾云阁 https://www.qclou ...

  9. Unity中有两种Animation Clip

    http://blog.csdn.net/zzxiang1985/article/details/51291861 在Unity中,我们有两种方法创建Animation Clip. 一种(后面简称方法 ...

  10. h.SSL协议栈整体分解

    1.SSL整体框图 SSL协议是应用层次(http协议)和TCP层级的一个可选的位置,可以从下面的图中非常清楚看到该层次: 绿色的框图就是这个SSL./TLS的位置,最右面的SSL/TLS图可以进一步 ...