资讯专栏INFORMATION COLUMN

JS代码复用模式

nanfeiyan / 3265人阅读

摘要:那么在代码复用方面都有哪些方法构造模式构造函数与普通函数的唯一区别在于调用方式不同构造函数首字母大写只是惯例,任何函数都可以用关键字来作为构造函数调用构造函数普通函数。

复用是一项非常重要的生活技能,因为生命是有限的,无意义的重复等于浪费生命。作为一个程序开发者,代码的复用既是一种能力,也是对积极生活的一种态度。那么JS 在代码复用方面都有哪些方法?
...................................................................................................
构造模式

构造函数与普通函数的唯一区别在于调用方式不同(构造函数首字母大写只是惯例),任何函数都可以用new关键字来作为构造函数调用(构造函数 = new + 普通函数)。

function Parent() {
  this.name = "jim";
  this.say = function() {
    console.log(this.name);
  };
  console.log(this.name);
}
Parent(); // 输出 jim
console.log(Parent); // 输出 Parent (){/* 函数体-略 */}

var child1 = new Parent(); // 输出 jim 构造函数创建 child1 对象(解析执行)
var child2 = new Parent(); // 输出 jim 构造函数创建 child2 对象(解析执行)

console.log(child1); // 输出 Parent {name: "jim", say: ƒ ()}
console.log(child1.say); // 输出 ƒ () {/* 函数体-略 */}

child1.say(); // 输出 jim
child2.say(); // 输出 jim

console.log(child1.name); // 输出 jim (child1 继承了 Parent name)
console.log(child2.name); // 输出 jim (child2 继承了 Parent name)

child1.name = "tom1"; // 修改 child 的 name 属性
child2.name = "tom2"; // 修改 child 的 name 属性

child1.say(); // 输出 tom1(说明 child 本地实例化了name属性 )
child2.say(); // 输出 tom2(说明 child 本地实例化了name属性 )
console.log(child1.name); // 输出 tom1(说明 child 本地实例化了name属性 )
console.log(child2.name); // 输出 tom2(说明 child 本地实例化了name属性 )

delete child1.name; // 删除 child1 的 name 属性
delete child2.name; // 删除 child2 的 name 属性

console.log(child1.name); // 输出 undefined(说明 child1 本地实例化name属性已删除 )
console.log(child2.name); // 输出 undefined(说明 child2 本地实例化name属性已删除 )

Parent(); // 输出 jim (说明构造函数属性 和 构造对象属性 没有关系)

缺点:无法复用父对象属性方法,当子对象数量变多,反复使用 new 重新创建父对象.

原型模式

我们知道所有引用类型都是 Object,也就是说引用类型的原型是 Object,他们是一个继承的关系。另外,原型的属性可以自定义。

function fn() {
  this.keyThis = ["fnThisValue"];
}
// name: "fn" prototype: {constructor: fn()} __proto__: Object
// 函数名是 fn
// 函数 prototype 指向一个对象,该对象的属性constructor 指向函数自身
// 函数 __proto__ 指向 Object(重点 __proto__ 是一个原型引用指针,指向父级原型)
// 此时fn 未执行, this 虽然指向window , 但是 keyThis 并未声明和赋值

// 以上是 JS 内部已经实现好的,下面我们来自定义一个原型属性
fn.prototype.keyProto = ["fnProtoValue"];
console.log(fn.prototype);
// 输出 {keyProto: ["fnProtoValue"], constructor: fn(),__proto__: Object}

var foo = new fn(); // fn() 执行, this指向window,key1声明和赋值
console.log(foo);
// 输出
// fn{
//    keyThis:["fooThisValue"],
//    __proto__:{ keyProto: ["fnProtoValue"], constructor: fn(), __proto__: Object}
// }
// foo 仅仅是一个构造对象(重点对象没有原型属性),原型引用指针__proto__指向 fn 的原型
// 原型链 就是 __proto__:{__proto__:{···}}

console.log(foo.keyThis); // 输出 ["fooThisValue"]
console.log(foo.keyProto); // 输出 ["fnProtoValue"]

foo.keyThis.push("fooThis");
foo.keyProto.push("fooProto");

console.log(foo);
// 输出
// fn{
//    keyThis:["fooThisValue", "fooThis"],
//    __proto__:{ keyProto: ["fnProtoValue", "fooThis"], constructor: fn(), __proto__: Object}
// }
// foo 的原型属性竟然被修改了,这应该不是我们想要的(小本本记下来),所以父级常量最好用 this 来定义

console.log(fn.prototype);
// 输出{ keyProto: ["fnProtoValue", "fooThis"], constructor: fn(), __proto__: Object}

缺点:虽然复用父对象属性方法,当子对象数量变多,反复使用 new 重新创建父对象.

借用模式

在 JS 基础数据类型操作系列(四)函数 中,我们介绍了 call,apply 和 bind 的函数作用域借用操作,这也是一种代码复用的好方法。

function Parent() {
  this.keyThis = ["fnThisValue"];
}
Parent.prototype.keyProto = ["fnProtoValue"];
function Child() {
  Parent.call(this);
  console.log(this.keyThis); // 输出 ["fnThisValue"]
  console.log(this.keyProto); // 输出 undefined
}
Child();
// 这种借用只能够针对 this 绑定的属性方法起作用。

var jim = new Child();
console.log(jim.keyThis); // 输出 ["fnThisValue"]
console.log(jim.keyProto); // 输出 undefined
// 这种借用只能够针对 this 绑定的属性方法起作用。
代理模式
function inherit(parent, child) {
  var F = function() {};
  F.prototype = parent.prototype;
  child.prototype = new F();
  child.prototype.constructor = child;
}

function Parent() {
  this.keyThis = ["fnThisValue"];
}
Parent.prototype.keyProto = ["fnProtoValue"];

function Child() {}
inherit(Parent, Child);

var jim = new Child();
console.log(jim.keyThis); // 输出 undefined
console.log(jim.keyProto); // 输出 ["fnProtoValue"]

缺点:只是代理了原型

标准模式

在 ES 5 中,提供了Object.create()方法来实现原型构造继承(语法糖)。
Object.create()方法创建一个新对象,使用现有的对象来提供新创建的对象的__proto__

语法 :Object.create(proto, [propertiesObject]) 。

第二个可选参数是 null 或一个对象,添加到新创建对象的自定义可枚举属性,对应 Object.defineProperties()的第二个参数。

function Parent() {}
Parent.prototype.keyProto = ["fnProtoValue"];

var jim = Object.create(Parent, {
  key: { value: "val" }
});

console.log(jim); // 输出 Function {key: "val",__proto__: Parent()}
jim.hasOwnProperty("key");


var Fn = {
    key:"value"
}
Object.create(Fn)
// {__proto__:{ key:"value"}}
克隆模式

通过复制属性来实现继承

浅克隆

简单对象,单层克隆

function extend(parent, child) {
  var i;
  child = child || {};
  for (i in parent) {
    if (parent.hasOwnProperty(i)) {
      child[i] = parent[i]; // 这里只是引用, 并非实例化
    }
  }
  return child;
}

var Parent = {
    key:"value",
    arr:[1,2,3,4],
    obj:{
        key:"value",
        arr:[1,2,3,4],
    }
}
var kid = extend(Parent)
kid.arr.push(4);
console.log(Parent.arr)  // 输出 [1,2,3,4,4]
深克隆

复杂对象,递归克隆

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 ? [] : {};
        arguments.callee(parent[i], child[i]);
      } else {
        child[i] = parent[i];
      }
    }
  }
  return child;
}
var Parent = {
    key:"value",
    arr:[1,2,3,4],
    obj:{
        key:"value",
        arr:[1,2,3,4],
    }
}
var kid = extendDeep(Parent)
kid.arr.push(4);
console.log(Parent.arr)  // 输出 [1,2,3,4]

缺点:针对的是对象,不是函数,当然对象用这个是最好的

总结

综上了解,我们想要一个既可以继承this属性,又可以继承prototype属性的方法。继承this属性最好用的是借用模式,继承prototype属性最好用的是Object.create()标准模式。

function parent() {
  this.money = 1000;
}
parent.prototype.say = function(money) {
  console.log("I have " + (this.money + money));
}

function inherit(parent,childParams){
    function Child() {
        parent.call(this);      // 借用 父级 this 属性
    }
    childParams = childParams || {}; // 定义额外参数
    Child.prototype = Object.create(parent.prototype,childParams);
    // parent.prototype 指向原型对象parent Prototype
    // Object.create(parent.prototype)
    // 输出 {__proto__:{ say:ƒ (money),constructor:ƒ parent(), __proto__:Object}}
    Child.prototype.constructor = Child; // 原型的构造函数应该永远指向自身
    return new Child()
}

var jim = inherit(parent);
var tom = inherit(parent,{key:{value:500}});
jim.say(100);   //输出 I have 1100
tom.say(500);   //输出 I have 1100
tom.key         //输出 500

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/94480.html

相关文章

  • JavaScript代码复用模式

    摘要:如下代码所示,可以使用构造函数来创建父对象,这样做的话,自身的属性和构造函数的原型的属性都将被继承。方法继承自对象这是中构造函数链的一个示例。 代码复用及其原则 代码复用,顾名思义就是对曾经编写过的代码的一部分甚至全部重新加以利用,从而构建新的程序。在谈及代码复用的时候,我们首先可以想到的是继承性。代码复用的原则是: 优先使用对象组合,而不是类继承 在js中,由于没有类的概念,因此实例...

    bergwhite 评论0 收藏0
  • 《Node.js设计模式》欢迎来到Node.js平台

    摘要:事件多路复用器收集资源的事件并且把这些事件放入队列中,直到事件被处理时都是阻塞状态。最后,处理事件多路复用器返回的每个事件,此时,与系统资源相关联的事件将被读并且在整个操作中都是非阻塞的。 本系列文章为《Node.js Design Patterns Second Edition》的原文翻译和读书笔记,在GitHub连载更新,同步翻译版链接。 欢迎关注我的专栏,之后的博文将在专栏同步:...

    Paul_King 评论0 收藏0
  • JS学习笔记 - 代码复用

    摘要:本文章记录本人在学习中看书理解到的一些东西,加深记忆和并且整理记录下来,方便之后的复习。但是在开发的过程中,并不是所有的代码复用都会使用到继承。而且整个代码都无法按照预期来运行。为了修复绑定对象与方法之间的关系。 本文章记录本人在学习 JavaScript 中看书理解到的一些东西,加深记忆和并且整理记录下来,方便之后的复习。 js 中复用代码 说道代码复用,一般都会涉及到对...

    cheng10 评论0 收藏0
  • MVC MVP MVVM

    摘要:,的事件回调函数中调用的操作方法。以为例调用关系模式实际就是将中的改名为,调用过程基本一致,最大的改良是间的双向绑定。和间,有一个对象,可以操作修改,使用。 参考:MVC,MVP 和 MVVM 的图示 - 阮一峰http://www.ruanyifeng.com/blo...Web开发的MVVM模式http://www.cnblogs.com/dxy198...界面之下:还原真实的MV...

    wushuiyong 评论0 收藏0
  • MVC MVP MVVM

    摘要:,的事件回调函数中调用的操作方法。以为例调用关系模式实际就是将中的改名为,调用过程基本一致,最大的改良是间的双向绑定。和间,有一个对象,可以操作修改,使用。 参考:MVC,MVP 和 MVVM 的图示 - 阮一峰http://www.ruanyifeng.com/blo...Web开发的MVVM模式http://www.cnblogs.com/dxy198...界面之下:还原真实的MV...

    Tangpj 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<