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


Posted in Javascript onMarch 04, 2015

介绍

本文介绍的四种代码复用模式都是最佳实践,推荐大家在编程的过程中使用。

模式1:原型继承

原型继承是让父对象作为子对象的原型,从而达到继承的目的:

function object(o) {

    function F() {

    }
    F.prototype = o;

    return new F();

}
// 要继承的父对象

var parent = {

    name: "Papa"

};
// 新对象

var child = object(parent);
// 测试

console.log(child.name); // "Papa"


// 父构造函数

function Person() {

    // an "own" property

    this.name = "Adam";

}

// 给原型添加新属性

Person.prototype.getName = function () {

    return this.name;

};

// 创建新person

var papa = new Person();

// 继承

var kid = object(papa);

console.log(kid.getName()); // "Adam"


// 父构造函数

function Person() {

    // an "own" property

    this.name = "Adam";

}

// 给原型添加新属性

Person.prototype.getName = function () {

    return this.name;

};

// 继承

var kid = object(Person.prototype);

console.log(typeof kid.getName); // "function",因为是在原型里定义的

console.log(typeof kid.name); // "undefined", 因为只继承了原型

同时,ECMAScript5也提供了类似的一个方法叫做Object.create用于继承对象,用法如下:
/* 使用新版的ECMAScript 5提供的功能 */

var child = Object.create(parent);
var child = Object.create(parent, {

    age: { value: 2} // ECMA5 descriptor

});

console.log(child.hasOwnProperty("age")); // true

而且,也可以更细粒度地在第二个参数上定义属性:

// 首先,定义一个新对象man

var man = Object.create(null);
// 接着,创建包含属性的配置设置

// 属性设置为可写,可枚举,可配置

var config = {

    writable: true,

    enumerable: true,

    configurable: true

};
// 通常使用Object.defineProperty()来添加新属性(ECMAScript5支持)

// 现在,为了方便,我们自定义一个封装函数

var defineProp = function (obj, key, value) {

    config.value = value;

    Object.defineProperty(obj, key, config);

}
defineProp(man, 'car', 'Delorean');

defineProp(man, 'dob', '1981');

defineProp(man, 'beard', false);

所以,继承就这么可以做了:

var driver = Object.create( man );

defineProp (driver, 'topSpeed', '100mph');

driver.topSpeed // 100mph

但是有个地方需要注意,就是Object.create(null)创建的对象的原型为undefined,也就是没有toString和valueOf方法,所以alert(man);的时候会出错,但alert(man.car);是没问题的。

模式2:复制所有属性进行继承

这种方式的继承就是将父对象里所有的属性都复制到子对象上,一般子对象可以使用父对象的数据。

先来看一个浅拷贝的例子:

/* 浅拷贝 */

function extend(parent, child) {

    var i;

    child = child || {};

    for (i in parent) {

        if (parent.hasOwnProperty(i)) {

            child[i] = parent[i];

        }

    }

    return child;

}
var dad = { name: "Adam" };

var kid = extend(dad);

console.log(kid.name); // "Adam"
var dad = {

    counts: [1, 2, 3],

    reads: { paper: true }

};

var kid = extend(dad);

kid.counts.push(4);

console.log(dad.counts.toString()); // "1,2,3,4"

console.log(dad.reads === kid.reads); // true

代码的最后一行,你可以发现dad和kid的reads是一样的,也就是他们使用的是同一个引用,这也就是浅拷贝带来的问题。

我们再来看一下深拷贝:

/* 深拷贝 */

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);

console.log(kid.counts.toString()); // "1,2,3,4"

console.log(dad.counts.toString()); // "1,2,3"
console.log(dad.reads === kid.reads); // false

kid.reads.paper = false;

深拷贝以后,两个值就不相等了,bingo!

模式3:混合(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(

                { eggs: 2, large: true },

                { butter: 1, salted: true },

                { flour: '3 cups' },

                { sugar: 'sure!' }

                );
console.dir(cake);

mix函数将所传入的所有参数的子属性都复制到child对象里,以便产生一个新对象。

那如何我们只想混入部分属性呢?该个如何做?其实我们可以使用多余的参数来定义需要混入的属性,例如mix(child,parent,method1,method2)这样就可以只将parent里的method1和method2混入到child里。上代码:

// Car 

var Car = function (settings) {

    this.model = settings.model || 'no model provided';

    this.colour = settings.colour || 'no colour provided';

};
// Mixin

var Mixin = function () { };

Mixin.prototype = {

    driveForward: function () {

        console.log('drive forward');

    },

    driveBackward: function () {

        console.log('drive backward');

    }

};


// 定义的2个参数分别是被混入的对象(reciving)和从哪里混入的对象(giving)

function augment(receivingObj, givingObj) {

    // 如果提供了指定的方法名称的话,也就是参数多余3个

    if (arguments[2]) {

        for (var i = 2, len = arguments.length; i < len; i++) {

            receivingObj.prototype[arguments[i]] = givingObj.prototype[arguments[i]];

        }

    }

    // 如果不指定第3个参数,或者更多参数,就混入所有的方法

    else {

        for (var methodName in givingObj.prototype) {

            // 检查receiving对象内部不包含要混入的名字,如何包含就不混入了

            if (!receivingObj.prototype[methodName]) {

                receivingObj.prototype[methodName] = givingObj.prototype[methodName];

            }

        }

    }

}
// 给Car混入属性,但是值混入'driveForward' 和 'driveBackward'*/

augment(Car, Mixin, 'driveForward', 'driveBackward');
// 创建新对象Car

var vehicle = new Car({ model: 'Ford Escort', colour: 'blue' });
// 测试是否成功得到混入的方法

vehicle.driveForward();

vehicle.driveBackward();

该方法使用起来就比较灵活了。

模式4:借用方法

一个对象借用另外一个对象的一个或两个方法,而这两个对象之间不会有什么直接联系。不用多解释,直接用代码解释吧:

var one = {

    name: 'object',

    say: function (greet) {

        return greet + ', ' + this.name;

    }

};
// 测试

console.log(one.say('hi')); // "hi, object"
var two = {

    name: 'another object'

};
console.log(one.say.apply(two, ['hello'])); // "hello, another object"
// 将say赋值给一个变量,this将指向到全局变量

var say = one.say;

console.log(say('hoho')); // "hoho, undefined"
// 传入一个回调函数callback

var yetanother = {

    name: 'Yet another object',

    method: function (callback) {

        return callback('Hola');

    }

};

console.log(yetanother.method(one.say)); // "Holla, undefined"
function bind(o, m) {

    return function () {

        return m.apply(o, [].slice.call(arguments));

    };

}
var twosay = bind(two, one.say);

console.log(twosay('yo')); // "yo, another object"


// ECMAScript 5给Function.prototype添加了一个bind()方法,以便很容易使用apply()和call()。
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);

console.log(twosay2('Bonjour')); // "Bonjour, another object"
var twosay3 = one.say.bind(two, 'Enchanté');

console.log(twosay3()); // "Enchanté, another object"

总结

就不用总结了吧。

Javascript 相关文章推荐
javascript学习笔记(五) Array 数组类型介绍
Jun 19 Javascript
js函数的延迟加载实现代码
Oct 11 Javascript
java与javascript之间json格式数据互转介绍
Oct 29 Javascript
调用innerHTML之后onclick失效问题的解决方法
Jan 28 Javascript
jquery解析xml字符串示例分享
Mar 25 Javascript
一个JavaScript用逗号分割字符串实例
Sep 22 Javascript
vue2 中如何实现动态表单增删改查实例
Jun 09 Javascript
jQuery+vue.js实现的九宫格拼图游戏完整实例【附源码下载】
Sep 12 jQuery
如何在js代码中消灭for循环实例详解
Jul 29 Javascript
Vue+Node服务器查询Mongo数据库及页面数据传递操作实例分析
Dec 20 Javascript
Vuex的API文档说明详解
Feb 05 Javascript
vue项目中openlayers绘制行政区划
Dec 24 Vue.js
深入理解JavaScript系列(45):代码复用模式(避免篇)详解
Mar 04 #Javascript
深入理解JavaScript系列(44):设计模式之桥接模式详解
Mar 04 #Javascript
JS实现FLASH幻灯片图片切换效果的方法
Mar 04 #Javascript
javascript下拉框选项单击事件的例子分享
Mar 04 #Javascript
js实现仿QQ秀换装效果的方法
Mar 04 #Javascript
深入理解JavaScript系列(43):设计模式之状态模式详解
Mar 04 #Javascript
深入理解JavaScript系列(42):设计模式之原型模式详解
Mar 04 #Javascript
You might like
Zerg剧情介绍
2020/03/14 星际争霸
PHP中3种生成XML文件方法的速度效率比较
2012/10/06 PHP
thinkphp 多表 事务详解
2013/06/17 PHP
php定义数组和使用示例(php数组的定义方法)
2014/03/29 PHP
php冒泡排序、快速排序、快速查找、二维数组去重实例分享
2014/04/24 PHP
10条php编程小技巧
2015/07/07 PHP
PHP中的switch语句的用法实例详解
2015/10/21 PHP
ThinkPHP框架实现的邮箱激活功能示例
2018/06/15 PHP
php ajax数据传输和响应方法
2018/08/21 PHP
JS getMonth()日期函数的值域是0-11
2010/02/15 Javascript
jQuery实现HTML5 placeholder效果实例
2014/12/09 Javascript
JavaScript更改原始对象valueOf的方法
2015/03/19 Javascript
浅谈javascript中call()、apply()、bind()的用法
2015/04/20 Javascript
JS实现窗口加载时模拟鼠标移动的方法
2015/06/03 Javascript
浅谈vue的iview列表table render函数设置DOM属性值的方法
2017/09/30 Javascript
Vue2.0仿饿了么webapp单页面应用详细步骤
2018/07/08 Javascript
vue如何截取字符串
2019/05/06 Javascript
js纯前端实现腾讯cos文件上传功能的示例代码
2019/05/14 Javascript
angular inputNumber指令输入框只能输入数字的实现
2019/12/03 Javascript
学习python (2)
2006/10/31 Python
Python Trie树实现字典排序
2014/03/28 Python
Python分治法定义与应用实例详解
2017/07/28 Python
Python使用sorted排序的方法小结
2017/07/28 Python
python redis连接 有序集合去重的代码
2019/08/04 Python
python 解压、复制、删除 文件的实例代码
2020/02/26 Python
超级实用的8个Python列表技巧
2020/08/24 Python
Scrapy项目实战之爬取某社区用户详情
2020/09/17 Python
html5的canvas实现3d雪花飘舞效果
2013/12/27 HTML / CSS
Html5 语法与规则简要概述
2014/07/29 HTML / CSS
针对HTML5的Web Worker使用攻略
2015/07/12 HTML / CSS
Clarks西班牙官方在线商店:clarks鞋
2019/05/03 全球购物
高二历史教学反思
2014/01/25 职场文书
优秀班主任经验交流材料
2014/06/02 职场文书
简爱电影观后感
2015/06/10 职场文书
Go语言使用select{}阻塞main函数介绍
2021/04/25 Golang
详解Python魔法方法之描述符类
2021/05/26 Python