加载中...

(46)代码复用模式(推荐篇)


介绍

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

模式1:原型继承

原型继承是让父对象作为子对象的原型,从而达到继承的目的:

  1. function object(o) {
  2. function F() {
  3. }
  4. F.prototype = o;
  5. return new F();
  6. }
  7. // 要继承的父对象
  8. var parent = {
  9. name: "Papa"
  10. };
  11. // 新对象
  12. var child = object(parent);
  13. // 测试
  14. console.log(child.name); // "Papa"
  15. // 父构造函数
  16. function Person() {
  17. // an "own" property
  18. this.name = "Adam";
  19. }
  20. // 给原型添加新属性
  21. Person.prototype.getName = function () {
  22. return this.name;
  23. };
  24. // 创建新person
  25. var papa = new Person();
  26. // 继承
  27. var kid = object(papa);
  28. console.log(kid.getName()); // "Adam"
  29. // 父构造函数
  30. function Person() {
  31. // an "own" property
  32. this.name = "Adam";
  33. }
  34. // 给原型添加新属性
  35. Person.prototype.getName = function () {
  36. return this.name;
  37. };
  38. // 继承
  39. var kid = object(Person.prototype);
  40. console.log(typeof kid.getName); // "function",因为是在原型里定义的
  41. console.log(typeof kid.name); // "undefined", 因为只继承了原型

同时,ECMAScript5也提供了类似的一个方法叫做Object.create用于继承对象,用法如下:

  1. /* 使用新版的ECMAScript 5提供的功能 */
  2. var child = Object.create(parent);
  3. var child = Object.create(parent, {
  4. age: { value: 2} // ECMA5 descriptor
  5. });
  6. console.log(child.hasOwnProperty("age")); // true

而且,也可以更细粒度地在第二个参数上定义属性:

  1. // 首先,定义一个新对象man
  2. var man = Object.create(null);
  3. // 接着,创建包含属性的配置设置
  4. // 属性设置为可写,可枚举,可配置
  5. var config = {
  6. writable: true,
  7. enumerable: true,
  8. configurable: true
  9. };
  10. // 通常使用Object.defineProperty()来添加新属性(ECMAScript5支持)
  11. // 现在,为了方便,我们自定义一个封装函数
  12. var defineProp = function (obj, key, value) {
  13. config.value = value;
  14. Object.defineProperty(obj, key, config);
  15. }
  16. defineProp(man, 'car', 'Delorean');
  17. defineProp(man, 'dob', '1981');
  18. defineProp(man, 'beard', false);

所以,继承就这么可以做了:

  1. var driver = Object.create( man );
  2. defineProp (driver, 'topSpeed', '100mph');
  3. driver.topSpeed // 100mph

但是有个地方需要注意,就是Object.create(null)创建的对象的原型为undefined,也就是没有toString和valueOf方法,所以alert(man);的时候会出错,但alert(man.car);是没问题的。

模式2:复制所有属性进行继承

这种方式的继承就是将父对象里所有的属性都复制到子对象上,一般子对象可以使用父对象的数据。

先来看一个浅拷贝的例子:

  1. /* 浅拷贝 */
  2. function extend(parent, child) {
  3. var i;
  4. child = child || {};
  5. for (i in parent) {
  6. if (parent.hasOwnProperty(i)) {
  7. child[i] = parent[i];
  8. }
  9. }
  10. return child;
  11. }
  12. var dad = { name: "Adam" };
  13. var kid = extend(dad);
  14. console.log(kid.name); // "Adam"
  15. var dad = {
  16. counts: [1, 2, 3],
  17. reads: { paper: true }
  18. };
  19. var kid = extend(dad);
  20. kid.counts.push(4);
  21. console.log(dad.counts.toString()); // "1,2,3,4"
  22. console.log(dad.reads === kid.reads); // true

代码的最后一行,你可以发现dad和kid的reads是一样的,也就是他们使用的是同一个引用,这也就是浅拷贝带来的问题。

我们再来看一下深拷贝:

  1. /* 深拷贝 */
  2. function extendDeep(parent, child) {
  3. var i,
  4. toStr = Object.prototype.toString,
  5. astr = "[object Array]";
  6. child = child || {};
  7. for (i in parent) {
  8. if (parent.hasOwnProperty(i)) {
  9. if (typeof parent[i] === 'object') {
  10. child[i] = (toStr.call(parent[i]) === astr) ? [] : {};
  11. extendDeep(parent[i], child[i]);
  12. } else {
  13. child[i] = parent[i];
  14. }
  15. }
  16. }
  17. return child;
  18. }
  19. var dad = {
  20. counts: [1, 2, 3],
  21. reads: { paper: true }
  22. };
  23. var kid = extendDeep(dad);
  24. kid.counts.push(4);
  25. console.log(kid.counts.toString()); // "1,2,3,4"
  26. console.log(dad.counts.toString()); // "1,2,3"
  27. console.log(dad.reads === kid.reads); // false
  28. kid.reads.paper = false;

深拷贝以后,两个值就不相等了,bingo!

模式3:混合(mix-in)

混入就是将一个对象的一个或多个(或全部)属性(或方法)复制到另外一个对象,我们举一个例子:

  1. function mix() {
  2. var arg, prop, child = {};
  3. for (arg = 0; arg < arguments.length; arg += 1) {
  4. for (prop in arguments[arg]) {
  5. if (arguments[arg].hasOwnProperty(prop)) {
  6. child[prop] = arguments[arg][prop];
  7. }
  8. }
  9. }
  10. return child;
  11. }
  12. var cake = mix(
  13. { eggs: 2, large: true },
  14. { butter: 1, salted: true },
  15. { flour: '3 cups' },
  16. { sugar: 'sure!' }
  17. );
  18. console.dir(cake);

mix函数将所传入的所有参数的子属性都复制到child对象里,以便产生一个新对象。

那如何我们只想混入部分属性呢?该个如何做?其实我们可以使用多余的参数来定义需要混入的属性,例如mix(child,parent,method1,method2)这样就可以只将parent里的method1和method2混入到child里。上代码:

  1. // Car
  2. var Car = function (settings) {
  3. this.model = settings.model || 'no model provided';
  4. this.colour = settings.colour || 'no colour provided';
  5. };
  6. // Mixin
  7. var Mixin = function () { };
  8. Mixin.prototype = {
  9. driveForward: function () {
  10. console.log('drive forward');
  11. },
  12. driveBackward: function () {
  13. console.log('drive backward');
  14. }
  15. };
  16. // 定义的2个参数分别是被混入的对象(reciving)和从哪里混入的对象(giving)
  17. function augment(receivingObj, givingObj) {
  18. // 如果提供了指定的方法名称的话,也就是参数多余3个
  19. if (arguments[2]) {
  20. for (var i = 2, len = arguments.length; i < len; i++) {
  21. receivingObj.prototype[arguments[i]] = givingObj.prototype[arguments[i]];
  22. }
  23. }
  24. // 如果不指定第3个参数,或者更多参数,就混入所有的方法
  25. else {
  26. for (var methodName in givingObj.prototype) {
  27. // 检查receiving对象内部不包含要混入的名字,如何包含就不混入了
  28. if (!receivingObj.prototype[methodName]) {
  29. receivingObj.prototype[methodName] = givingObj.prototype[methodName];
  30. }
  31. }
  32. }
  33. }
  34. // 给Car混入属性,但是值混入'driveForward' 和 'driveBackward'*/
  35. augment(Car, Mixin, 'driveForward', 'driveBackward');
  36. // 创建新对象Car
  37. var vehicle = new Car({ model: 'Ford Escort', colour: 'blue' });
  38. // 测试是否成功得到混入的方法
  39. vehicle.driveForward();
  40. vehicle.driveBackward();

该方法使用起来就比较灵活了。

模式4:借用方法

一个对象借用另外一个对象的一个或两个方法,而这两个对象之间不会有什么直接联系。不用多解释,直接用代码解释吧:

  1. var one = {
  2. name: 'object',
  3. say: function (greet) {
  4. return greet + ', ' + this.name;
  5. }
  6. };
  7. // 测试
  8. console.log(one.say('hi')); // "hi, object"
  9. var two = {
  10. name: 'another object'
  11. };
  12. console.log(one.say.apply(two, ['hello'])); // "hello, another object"
  13. // 将say赋值给一个变量,this将指向到全局变量
  14. var say = one.say;
  15. console.log(say('hoho')); // "hoho, undefined"
  16. // 传入一个回调函数callback
  17. var yetanother = {
  18. name: 'Yet another object',
  19. method: function (callback) {
  20. return callback('Hola');
  21. }
  22. };
  23. console.log(yetanother.method(one.say)); // "Holla, undefined"
  24. function bind(o, m) {
  25. return function () {
  26. return m.apply(o, [].slice.call(arguments));
  27. };
  28. }
  29. var twosay = bind(two, one.say);
  30. console.log(twosay('yo')); // "yo, another object"
  31. // ECMAScript 5给Function.prototype添加了一个bind()方法,以便很容易使用apply()和call()。
  32. if (typeof Function.prototype.bind === 'undefined') {
  33. Function.prototype.bind = function (thisArg) {
  34. var fn = this,
  35. slice = Array.prototype.slice,
  36. args = slice.call(arguments, 1);
  37. return function () {
  38. return fn.apply(thisArg, args.concat(slice.call(arguments)));
  39. };
  40. };
  41. }
  42. var twosay2 = one.say.bind(two);
  43. console.log(twosay2('Bonjour')); // "Bonjour, another object"
  44. var twosay3 = one.say.bind(two, 'Enchanté');
  45. console.log(twosay3()); // "Enchanté, another object"

总结

就不用总结了吧。


还没有评论.