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

介绍

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

模式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"

总结

就不用总结了吧。

(0)

相关推荐

  • JavaScript代码复用模式实例分析

    任何编程都提出代码复用,否则话每次开发一个新程序或者写一个新功能都要全新编写的话,那就歇菜了,但是代码复用也是有好要坏,接下来的两篇文章我们将针对代码复用来进行讨论,第一篇文避免篇,指的是要尽量避免使用这些模式,因为或多或少有带来一些问题:第二排是推荐篇,指的是推荐大家使用的模式,一般不会有什么问题. 模式1:默认模式 代码复用大家常用的默认模式,往往是有问题的,该模式使用Parent()的构造函数创建一个对象,并且将该对象赋值给Child()的原型.我们看一下代码: 复制代码 代码如下: fu

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

    介绍 任何编程都提出代码复用,否则话每次开发一个新程序或者写一个新功能都要全新编写的话,那就歇菜了,但是代码复用也是有好要坏,接下来的两篇文章我们将针对代码复用来进行讨论,第一篇文避免篇,指的是要尽量避免使用这些模式,因为或多或少有带来一些问题:第二排是推荐篇,指的是推荐大家使用的模式,一般不会有什么问题. 模式1:默认模式 代码复用大家常用的默认模式,往往是有问题的,该模式使用Parent()的构造函数创建一个对象,并且将该对象赋值给Child()的原型.我们看一下代码: 复制代码 代码如下:

  • JavaScript代码复用模式详解

    代码复用及其原则 代码复用,顾名思义就是对曾经编写过的代码的一部分甚至全部重新加以利用,从而构建新的程序.在谈及代码复用的时候,我们首先可以想到的是继承性.代码复用的原则是: 优先使用对象组合,而不是类继承 在js中,由于没有类的概念,因此实例的概念也就没多大意义,js中的对象是简单的键-值对,可以动态的创建和修改它们. 但在js中,我们可以使用构造函数和new操作符来实例化一个对象,这与其他使用类的编程语言在语法上有其相似之处. 例如: var trigkit4 = new Person();

  • 简单谈谈javascript代码复用模式

    代码复用有一个著名的原则,是GoF提出的:优先使用对象组合,而不是类继承.在javascript中,并没有类的概念,所以代码的复用,也并不局限于类式继承.javascript中创建对象的方法很多,有构造函数,可以使用new创建对象,并且可以动态的修改对象.javascript的非类式继承(可称为现代继承模式)复用方法也很多,例如,利用其它对象组合成所需要的对象,对象混入技术,借用和复用所需要的方法. 类式继承模式-默认模式 两个构造函数Parent和Child的例子: 复制代码 代码如下: fu

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

    介绍 本文介绍的四种代码复用模式都是最佳实践,推荐大家在编程的过程中使用. 模式1:原型继承 原型继承是让父对象作为子对象的原型,从而达到继承的目的: 复制代码 代码如下: function object(o) {     function F() {     } F.prototype = o;     return new F(); } // 要继承的父对象 var parent = {     name: "Papa" }; // 新对象 var child = object(p

  • 深入理解JavaScript系列(1) 编写高质量JavaScript代码的基本要点

    具体一点就是编写高质量JavaScript的一些要素,例如避免全局变量,使用单变量声明,在循环中预缓存length(长度),遵循代码阅读,以及更多. 此摘要也包括一些与代码不太相关的习惯,但对整体代码的创建息息相关,包括撰写API文档.执行同行评审以及运行JSLint.这些习惯和最佳做法可以帮助你写出更好的,更易于理解和维护的代码,这些代码在几个月或是几年之后再回过头看看也是会觉得很自豪的. 书写可维护的代码(Writing Maintainable Code ) 软件bug的修复是昂贵的,并且

  • 深入理解JavaScript系列(3) 全面解析Module模式

    简介 Module模式是JavaScript编程中一个非常通用的模式,一般情况下,大家都知道基本用法,本文尝试着给大家更多该模式的高级使用方式. 首先我们来看看Module模式的基本特征: 模块化,可重用 封装了变量和function,和全局的namaspace不接触,松耦合 只暴露可用public的方法,其它私有方法全部隐藏 关于Module模式,最早是由YUI的成员Eric Miraglia在4年前提出了这个概念,我们将从一个简单的例子来解释一下基本的用法(如果你已经非常熟悉了,请忽略这一节

  • 深入理解JavaScript系列(47):对象创建模式(上篇)

    介绍 本篇主要是介绍创建对象方面的模式,利用各种技巧可以极大地避免了错误或者可以编写出非常精简的代码. 模式1:命名空间(namespace) 命名空间可以减少全局命名所需的数量,避免命名冲突或过度.一般我们在进行对象层级定义的时候,经常是这样的: 复制代码 代码如下: var app = app || {}; app.moduleA = app.moduleA || {}; app.moduleA.subModule = app.moduleA.subModule || {}; app.mod

  • 深入理解JavaScript系列(2) 揭秘命名函数表达式

    前言 网上还没用发现有人对命名函数表达式进去重复深入的讨论,正因为如此,网上出现了各种各样的误解,本文将从原理和实践两个方面来探讨JavaScript关于命名函数表达式的优缺点. 简单的说,命名函数表达式只有一个用户,那就是在Debug或者Profiler分析的时候来描述函数的名称,也可以使用函数名实现递归,但很快你就会发现其实是不切实际的.当然,如果你不关注调试,那就没什么可担心的了,否则,如果你想了解兼容性方面的东西的话,你还是应该继续往下看看. 我们先开始看看,什么叫函数表达式,然后再说一

  • 深入理解JavaScript系列(4) 立即调用的函数表达式

    前言 大家学JavaScript的时候,经常遇到自执行匿名函数的代码,今天我们主要就来想想说一下自执行. 在详细了解这个之前,我们来谈了解一下"自执行"这个叫法,本文对这个功能的叫法也不一定完全对,主要是看个人如何理解,因为有的人说立即调用,有的人说自动执行,所以你完全可以按照你自己的理解来取一个名字,不过我听很多人都叫它为"自执行",但作者后面说了很多,来说服大家称呼为"立即调用的函数表达式". 本文英文原文地址:http://benalman

  • Javascript 严格模式use strict详解

    严格模式:由ECMA-262规范定义的JavaScript标准,对javascrip的限制更强. (非严格的模式,被称为"马虎模式/稀松模式/懒散模式".) 一.严格模式的使用 严格模式可以在脚本或函数级别实现.(即全局和局部模式) 1.全局 在js文件的最前面添加    "use strict" 2.局部 在函数内部添加   "use strict",如下 function fn() { "use strict"; //so

  • 从表单校验看JavaScript策略模式的使用详解

    众所周知的是,表单确实在前端,唔,或者说在网页中占有不小的比重.事实上,几乎每一个中大型网站都会有"登录注册"以验证用户信息.防止一些不可名状的隐患... 那么表单的优劣就成了前端开发者急需解决的问题.其实我更愿意称为"代码的可读性"或"可复用性"以及"是否冗杂". 表单也有"优劣"?你在开玩笑嘛? 我想你可以认真看下下面的代码,它用到了一些"新知识": <form action=

  • JavaScript代码异常监控实现过程详解

    这篇文章主要介绍了JavaScript代码异常监控实现过程详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 JavaScript异常一般有两方面:语法错误和运行时错误.两种错误的捕获和处理方式不同,从而影响具体的方案选型.通常来说,处理JS异常的方案有两种:try...catch捕获 和 window.onerror捕获.以下就两种方案分别分析各自的优劣. 虽然语法错误本应该在开发构建阶段使用测试工具避免,但难免会有马失前蹄部署到线上的时候.

随机推荐