深入理解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系列(46):代码复用模式(推荐篇)相关推荐
- 深入理解JavaScript系列(33):设计模式之策略模式(转)
介绍 策略模式定义了算法家族,分别封装起来,让他们之间可以互相替换,此模式让算法的变化不会影响到使用算法的客户. 正文 在理解策略模式之前,我们先来一个例子,一般情况下,如果我们要做数据合法性验证,很 ...
- 深入理解JavaScript系列(1):编写高质量JavaScript代码的基本要点
才华横溢的Stoyan Stefanov,在他写的由O'Reilly初版的新书<JavaScript Patterns>(JavaScript模式)中,我想要是为我们的读者贡献其摘要,那会 ...
- 深入理解JavaScript系列(27):设计模式之建造者模式
介绍 在软件系统中,有时候面临着"一个复杂对象"的创建工作,其通常由各个部分的子对象用一定的算法构成:由于需求的变化,这个复杂对象的各个部分经常面临着剧烈的变化,但是将它们组合在一 ...
- 深入理解JavaScript系列(33):设计模式之策略模式
介绍 策略模式定义了算法家族,分别封装起来,让他们之间可以互相替换,此模式让算法的变化不会影响到使用算法的客户. 正文 在理解策略模式之前,我们先来一个例子,一般情况下,如果我们要做数据合法性验证,很 ...
- 深入理解JavaScript系列(1):编写高质量JavaScript代码的基本要点(转)
才华横溢的Stoyan Stefanov,在他写的由O'Reilly初版的新书<JavaScript Patterns>(JavaScript模式)中,我想要是为我们的读者贡献其摘要,那会 ...
- 深入理解JavaScript系列——汤姆大叔
原文地址 深入理解JavaScript系列文章,包括了原创,翻译,转载,整理等各类型文章,如果对你有用,请推荐支持一把,给大叔写作的动力. 深入理解JavaScript系列(1):编写高质量JavaS ...
- [转载]深入理解JavaScript系列 --汤姆大叔
深入理解JavaScript系列文章,包括了原创,翻译,转载,整理等各类型文章,如果对你有用,请推荐支持一把,给大叔写作的动力. 深入理解JavaScript系列(1):编写高质量JavaScript ...
- 深入理解JavaScript系列(转自汤姆大叔)
深入理解JavaScript系列(转自汤姆大叔) 深入理解JavaScript系列文章,包括了原创,翻译,转载,整理等各类型文章,如果对你有用,请推荐支持一把,给大叔写作的动力. 深入理解JavaSc ...
- 深入理解JavaScript系列(4):立即调用的函数表达式
前言 大家学JavaScript的时候,经常遇到自执行匿名函数的代码,今天我们主要就来想想说一下自执行. 在详细了解这个之前,我们来谈了解一下"自执行"这个叫法,本文对这个功能的叫 ...
最新文章
- oracle plsql开启并行,Oracle开启并行的几种方法
- R语定义函数对宽分布(wide distribution )、有偏分布(skew distribution)的数据进行对数变换(符号对数变换函数、signed log transformation)
- Swift - 闭包的介绍及用法(以数组排序为例)
- 开发Java系统程序员要注意的基本知识
- 强大的grep命令使用经验
- gitattributes手册
- 函的红头文件格式制作_Excel实现批量制作年会邀请函,这个方法,你一定不能错过...
- 【海洋女神原创】一个完整的Installshield安装程序实例—艾泽拉斯之海洋女神出品(二) --基本设置二...
- MySQL和Mariadb都启动不了了_linux centos7mariadb安装成功启动不了 解决思路
- 打印出数字字符串的偶位数
- 《机器学习实战》学习总结(一)KNN分类算法原理
- 计算机二级考试操作题outlook,2017年计算机考试二级考试操作题.doc
- 达观杯文本智能处理(3)
- [转]Fedora Core Linux 9 中安装VMware Tools-6.5.0
- 【CSS基础笔记】——盒模型、块级元素、行内元素、浮动、对齐、定位
- Redis下载部署配置运行及修改登录地址、端口和密码
- 【UG】二次开发如何调试
- 机器学习中的K-means算法原理与R语言实例
- 四、vue 项目使用高德地图画面(多边形)
- KL15 KL30 KL31 KL50 KLR
热门文章
- html表格文字纵向排列,table中如何竖排英文?
- 除开利润原因,景区为何坚持卖天价水?
- SSH 公钥私钥 详细解释
- Magica Cloth 属性介绍
- oracle db file sequential read,性能优化 ---- db file sequential read
- XP系统自动修复命令(sfc)详解
- mac安装git的时候踩过的坑
- 现代机械设计类SCI杂志
- JMeter 5.0:造测试数据(加载第三方包)
- Siemens Simcenter FloEFD 2021.1.0 v5312 for NXSeries