深入理解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 相关文章推荐
初学prototype,发个JS接受URL参数的代码
Sep 25 Javascript
JQuery中根据属性或属性值获得元素(6种情况获取方法)
Jan 17 Javascript
jquery中通过父级查找进行定位示例
Jun 28 Javascript
Jquery Ajax方法传值到action的方法
May 11 Javascript
下拉框select的绑定示例
Sep 04 Javascript
javascript结合ajax读取txt文件内容
Dec 05 Javascript
jQuery实现带玻璃流光质感的手风琴特效
Nov 20 Javascript
JQuery移动页面开发之屏幕方向改变与滚屏的实现
Dec 03 Javascript
js判断当前页面用什么浏览器打开的方法
Jan 06 Javascript
jQuery插件ajaxFileUpload使用详解
Jan 10 Javascript
Angular实现搜索框及价格上下限功能
Jan 19 Javascript
微信小程序实现购物页面左右联动
Feb 15 Javascript
深入理解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
php 生成饼图 三维饼图
2009/09/28 PHP
JS 网站性能优化笔记
2011/05/24 PHP
php的array数组和使用实例简明教程(容易理解)
2014/03/20 PHP
PHP获取指定函数定义在哪个文件中以及其所在的行号实例
2014/05/08 PHP
PHP生成数组再传给js的方法
2014/08/07 PHP
Linux系统下php获得系统分区信息的方法
2015/03/30 PHP
php 自定义错误日志实例详解
2016/11/12 PHP
JavaScript随机排序(随即出牌)
2010/09/17 Javascript
收集的一些Array及String原型对象的扩展实现代码
2010/12/05 Javascript
addEventListener和attachEvent二者绑定的执行函数中的this不相同
2012/12/09 Javascript
『jQuery』名称冲突使用noConflict方法解决
2013/04/22 Javascript
js单词形式的运算符
2014/05/06 Javascript
JS本地刷新返回上一页代码
2016/07/25 Javascript
Node.js包管理器Yarn的入门介绍与安装
2016/10/17 Javascript
浅析vue component 组件使用
2017/03/06 Javascript
VueJS 集成 Medium Editor的示例代码 (自定义编辑器按钮)
2017/08/24 Javascript
详解webpack + react + react-router 如何实现懒加载
2017/11/20 Javascript
bootstrap 弹出框modal添加垂直方向滚轴效果
2018/07/09 Javascript
vue-cli整合vuex的时候,修改actions和mutations,实现热部署的方法
2018/09/19 Javascript
从0搭建vue-cli4脚手架
2020/06/17 Javascript
Python字典操作简明总结
2015/04/13 Python
八大排序算法的Python实现
2021/01/28 Python
Python AES加密实例解析
2018/01/18 Python
Python Pandas批量读取csv文件到dataframe的方法
2018/10/08 Python
Python析构函数__del__定义原理解析
2020/11/20 Python
LN-CC日本:高端男装和女装的奢侈时尚目的地
2019/09/01 全球购物
英国标志性生活方式品牌:Skinnydip London
2019/12/15 全球购物
运动鞋、街头服装、手表和手袋的实时市场:StockX
2020/11/25 全球购物
自我评价范文
2013/12/22 职场文书
好人好事事迹材料
2014/02/12 职场文书
《可爱的动物》教学反思
2014/02/22 职场文书
《绿色蝈蝈》教学反思
2014/03/02 职场文书
新闻专业毕业生英文求职信
2014/03/19 职场文书
学校群众路线专项整治方案
2014/10/31 职场文书
python glom模块的使用简介
2021/04/13 Python
Nginx如何获取自定义请求header头和URL参数详解
2022/07/23 Servers