深入理解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 相关文章推荐
基于jquery自定义的漂亮单选按钮RadioButton
Nov 19 Javascript
鼠标滚轮改变图片大小的示例代码
Nov 20 Javascript
Lab.js初次使用笔记
Feb 28 Javascript
百度UEditor编辑器如何关闭抓取远程图片功能
Mar 03 Javascript
PHP 数组current和next用法分享
Mar 05 Javascript
JavaScript中0和&quot;&quot;比较引发的问题
May 26 Javascript
学习Javascript闭包(Closure)知识
Aug 07 Javascript
weex里Vuex state使用storage持久化详解
Sep 09 Javascript
Vue之Vue.set动态新增对象属性方法
Feb 23 Javascript
vue裁切预览组件功能的实现步骤
May 04 Javascript
centos 上快速搭建ghost博客方法分享
May 23 Javascript
通过jQuery学习js类型判断的技巧
May 27 jQuery
深入理解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 和 MySQL 基础教程(三)
2006/10/09 PHP
php 页面执行时间计算代码
2008/12/04 PHP
php date与gmdate的获取日期的区别
2010/02/08 PHP
PHP有序表查找之插值查找算法示例
2018/02/10 PHP
ThinkPHP 3使用OSS的方法
2018/07/19 PHP
浅析PHP反序列化中过滤函数使用不当导致的对象注入问题
2020/02/15 PHP
JAVASCRIPT下判断IE与FF的比较简单的方式
2008/10/17 Javascript
javascript 浏览器判断 绑定事件 arguments 转换数组 数组遍历
2009/07/06 Javascript
JavaScript 学习笔记之变量及其作用域
2015/01/14 Javascript
JQuery中serialize()用法实例分析
2015/02/06 Javascript
关于事件mouseover ,mouseout ,mouseenter,mouseleave的区别
2015/10/12 Javascript
javascript中获取class的简单实现
2016/07/12 Javascript
JS获取字符串实际长度(包含汉字)的简单方法
2016/08/11 Javascript
简单理解Vue中的nextTick方法
2018/01/30 Javascript
nodejs实现套接字服务功能详解
2018/06/21 NodeJs
利用Promise自定义一个GET请求的函数示例代码
2019/03/20 Javascript
bootstrap table实现横向合并与纵向合并
2019/07/18 Javascript
微信小程序实现音乐播放页面布局
2020/12/11 Javascript
[00:59]DOTA2背景故事第二期之四大基本法则
2020/07/07 DOTA
Python根据区号生成手机号码的方法
2015/07/08 Python
python实现发送和获取手机短信验证码
2016/01/15 Python
python、java等哪一门编程语言适合人工智能?
2017/11/13 Python
使用Python写一个量化股票提醒系统
2018/08/22 Python
Python编写合并字典并实现敏感目录的小脚本
2019/02/26 Python
python3.6根据m3u8下载mp4视频
2019/06/17 Python
解决Djang2.0.1中的reverse导入失败的问题
2019/08/16 Python
pytorch 可视化feature map的示例代码
2019/08/20 Python
解决Django migrate不能发现app.models的表问题
2019/08/31 Python
Python爬虫实现使用beautifulSoup4爬取名言网功能案例
2019/09/15 Python
Lulu & Georgia官方网站:购买地毯、家具、抱枕、壁纸、床上用品等
2018/03/19 全球购物
澳大利亚小众服装品牌:Maurie & Eve
2018/03/27 全球购物
军校本科大学生自我评价
2014/01/14 职场文书
法人代表委托书
2014/04/04 职场文书
事业单位岗位说明书
2015/10/08 职场文书
Python深度学习之实现卷积神经网络
2021/06/05 Python
Nginx动静分离配置实现与说明
2022/04/07 Servers