深入理解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" 总结 就不用总结了吧。 同步与推荐 本文已同步至目录索引:深入理解JavaScript系列 深入理解JavaScript系列文章,包括了原创,翻译,转载等各类型的文章,如果对你有用,请推荐支持一把,给大叔写作的动力。
深入理解JavaScript系列(46):代码复用模式(推荐篇)的更多相关文章
- 深入理解JavaScript系列(45):代码复用模式(避免篇)
介绍 任何编程都提出代码复用,否则话每次开发一个新程序或者写一个新功能都要全新编写的话,那就歇菜了,但是代码复用也是有好要坏,接下来的两篇文章我们将针对代码复用来进行讨论,第一篇文避免篇,指的是要尽量 ...
- 深入理解JavaScript系列(1):编写高质量JavaScript代码的基本要点
深入理解JavaScript系列(1):编写高质量JavaScript代码的基本要点 2011-12-28 23:00 by 汤姆大叔, 139489 阅读, 119 评论, 收藏, 编辑 才华横溢的 ...
- 深入理解JavaScript系列(33):设计模式之策略模式(转)
介绍 策略模式定义了算法家族,分别封装起来,让他们之间可以互相替换,此模式让算法的变化不会影响到使用算法的客户. 正文 在理解策略模式之前,我们先来一个例子,一般情况下,如果我们要做数据合法性验证,很 ...
- 深入理解JavaScript系列(50):Function模式(下篇)
介绍 本篇我们介绍的一些模式称为初始化模式和性能模式,主要是用在初始化以及提高性能方面,一些模式之前已经提到过,这里只是做一下总结. 立即执行的函数 在本系列第4篇的<立即调用的函数表达式> ...
- 深入理解JavaScript系列(49):Function模式(上篇)
介绍 本篇主要是介绍Function方面使用的一些技巧(上篇),利用Function特性可以编写出很多非常有意思的代码,本篇主要包括:回调模式.配置对象.返回函数.分布程序.柯里化(Currying) ...
- 深入理解JavaScript系列(47):对象创建模式(上篇)
介绍 本篇主要是介绍创建对象方面的模式,利用各种技巧可以极大地避免了错误或者可以编写出非常精简的代码. 模式1:命名空间(namespace) 命名空间可以减少全局命名所需的数量,避免命名冲突或过度. ...
- 深入理解JavaScript系列(48):对象创建模式(下篇)
介绍 本篇主要是介绍创建对象方面的模式的下篇,利用各种技巧可以极大地避免了错误或者可以编写出非常精简的代码. 模式6:函数语法糖 函数语法糖是为一个对象快速添加方法(函数)的扩展,这个主要是利用pro ...
- 深入理解JavaScript系列(44):设计模式之桥接模式
介绍 桥接模式(Bridge)将抽象部分与它的实现部分分离,使它们都可以独立地变化. 正文 桥接模式最常用在事件监控上,先看一段代码: addEvent(element, 'click', getBe ...
- 深入理解JavaScript系列(42):设计模式之原型模式
介绍 原型模式(prototype)是指用原型实例指向创建对象的种类,并且通过拷贝这些原型创建新的对象. 正文 对于原型模式,我们可以利用JavaScript特有的原型继承特性去创建对象的方式,也就是 ...
随机推荐
- 基于GOJS绘制流程图
基于GOJS封装的流程图设计(展示)工具类,主要分为两个工具类: 工具库依赖于go.js.jquery以及layer.js http://gojs.net/ http://jquery.com/ ht ...
- Performance profile of a typical interior scene
------------------------------- Num faces: 7000k+ Num lights: 38 Num textures: 79 Textures on disk: ...
- 编写高质量JS代码上
想写出高效的javascript类库却无从下手: 尝试阅读别人的类库,却理解得似懂给懂: 打算好好钻研js高级函数,但权威书上的内容太零散, 即使记住“用法”,但到要“用”的时候却没有想“法”. 也许 ...
- 使用原生javascript和jQuery解析json数据
JSON(JavaScript Object Notation)是一种轻量级的数据交换格式. JSONM文件中包含了关于“名称”和“值”的信息. 有时候我们需要读取JSON格式的数据文件,在jQuer ...
- Spring Cloud使用总结
本文来自网易云社区,转载务必请注明出处. Spring Cloud 是spring团队推出的基于SpringBoot的分布式微服务框架,为开发者提供了在分布式系统(如配置管理.服务发现.断路器.智能路 ...
- pandas set_index() reset_index()
set_index() 官方定义: 使用一个或多个现有列设置索引, 默认情况下生成一个新对象 DataFrame.set_index(keys, drop=True, append=False, ...
- 2个list取差集
list操作 element in a list but not in other list,元素在一个list,不在另一个list 在数据量大的时候使用numpy的setdiff1d方法的性能非常好 ...
- bzoj 2780: [Spoj]8093 Sevenk Love Oimaster(广义SAM)
题目大意:给出n个原串,再给出m个查询串.求每个查询串出现在了多少原串中. 题解 直接对原串建一个广义SAM,然后把每一个原串放到SAM上跑一跑,记录一下每一个状态属于多少个原串,用$size$表示. ...
- AutoCAD.Net圆弧半径标注延长线
#region 注册RegApp public static void CheckRegApp(string regapptablename) { Database db = HostApplicat ...
- [Swift]在Swift中实现自增(++)、自减(--)运算符:利用extension扩展Int类
自增(++).自减(--)运算符主要用在For循环中,Swift有自己更简易的循环遍历方法,而且类似x- ++x这种代码不易维护. Swift为了营造自己的编码风格,树立自己的代码精神体系,已经不支持 ...