JavaScript > 继续与拷贝

JavaScript 继续与拷贝

Date: 7th of Aug, 2015

Author: HaoyCn

本文议论JavaScript中怎样完成继续关联,以及怎样拷贝对象。下面,我们离别讨论4种继续要领。谈完继续要领后,再议论对象的拷贝。

须要提早申明的是,后文须要继续的组织器函数是:

var Animal = function(name){
    this.name = name;
};
Animal.prototype.jump = function(){
    console.log('jumped');
};

原型链继续法

本要领的特征在于,能够把能够重用的部份迁移到原型链中,而不可重用的则设置为对象的自身属性。

var Human = function(name){
    this.name = name;
};
// 这一步会使得
// Human.prototype.constructor = Animal;
// 所以须要从新手动重定向
Human.prototype = new Animal;
// 假如不变动
// 经由历程`Human`组织器组织出来的对象的组织器会变成`Animal`而不是`Human`
Human.prototype.constructor = Human;

var man = new Human('HaoyCn');
man.jump();

如今,对象 man 能够应用 Animal.prototype.jump 要领,查找历程是:

  1. man 自身没有jump要领
  2. 查找 man.constructor.prototype,即Human.prototype,可Human.prototype自身也没有 jump 要领,而它又是一个由 Animal 组织的对象,所以
  3. 查找 Animal.prototype,在其中找到了 jump 要领,实行之

仅从原型继续法

和“原型链继续法”比拟,本要领的长处在于,提高了运行时的效力,没有建立新对象出来。

var Human = function(name){
    this.name = name;
};
Human.prototype = Animal.prototype;
var man = new Human('HaoyCn');
man.jump();

这时候的查找历程是:

  1. man 自身没有jump要领
  2. 查找 man.constructor.prototype,即Human.prototypeHuman.prototype是对 Animal.prototype 的援用,在其中找到了 jump 要领,实行之

减少了一步。然则价值则是:对 Human.prototype 的修改都邑影响到 Animal.prototype,由于前者是对后者的援用。

一个致命瑕玷就是,没法修改子类组织的对象的 constructor

测试一下:

man.constructor === Animal;//true

我们来回忆一下 new 的历程:

var newProcess = function(){
    var ret;
    // 组织一个新对象
    var obj = {};
    // 组织函数
    var Constructor = Array.prototype.shift.call(arguments);
    // 纪录原型
    obj.__proto__ = Constructor.prototype;
    // 应用组织函数给新对象设置属性
    ret = Constructor.apply(obj,arguments);
    // 一直返回一个对象
    return 'object' === typeof ret ? ret : obj;
};

我们以此来回忆下“仅从原型继续法”是怎样建立出 man 的。

// var man = newProcess(Human,'HaoyCn');
// 复原以下
var ret;
var man = {};
// var Constructor = Array.prototype.shift.call(arguments);
// 等于
//var Constructor = Human;
man.__proto__ = Human.prototype;
// ret = Human.apply(obj,arguments);
// `Human`组织器实行的是
man.name = 'HaoyCn';
// `Human`组织器返回的是 undefined,即 ret = undefined;
// 所以末了`newProcess`返回`man`

因而,就不难理解了:

man.constructor === 
    man.__proto__.constructor === 
    Human.prototype.constructor ===
    Animal.prototype.constructor ===
Animal

暂时组织器继续法

“仅从原型继续法”的题目暴露出来了:Animal.prototype 会因对 Human.prototype 的修改而转变。假如被转变了,由 Animal 组织出来的对象也会发作转变。我们来举个例子:

var monkey = new Animal('monkey');
var woman = new Human('woman');
monkey.jump();// jumped
woman.jump();// jumped
// 下面的修改会影响`Animal.prototype`
Human.prototype.jump = function(){
    console.log('I refuse');
};
// 底本组织好的对象也会被影响
monkey.jump();// I refuse
woman.jump();// I refuse

那末,我们怎样躲避这个题目呢?

“暂时组织器继续法”应用一个中介函数,以下

var F = function(){};
F.prototype = Animal.prototype;
var Human = function(name){
    this.name = name;
};
Human.prototype = new F;
Human.prototype.constructor = Human;
Human.prototype.sing = function(){
    console.log('Mayday');
};
var man = new Human('HaoyCn');
man.jump();
man.sing();

我们对 Human.prototype 的任何转变都变成了对一个由中介组织器建立的对象的属性的修改。jump 查找历程是:

  1. man 自身没有jump要领
  2. 查找 man.constructor.prototype,即Human.prototype,可Human.prototype自身也没有 jump 要领,而它又是一个由 F 组织的对象,所以
  3. 查找 F.prototype,即 Animal.prototype,在其中找到了 jump 要领,实行之

那这个要领同最最先的“仅从原型继续法”比拟,又有什么提高呢?

先看“仅从原型继续法”中的操纵:

Human.prototype = new Animal;
// 这将形成:
// Human.prototype.name = undefined;// 没有给`Animal`传入参数之故

也就是说,Human.prototype 会多出不必要的属性来,而中介器则避免了这类不必要的属性。

组织器借用法

以上继续法共通的一个瑕玷在于,Human 组织器组织的对象虽然能够共用 Animal.prototype,但关于 name 属性而言,Human 组织器只能本身再写一遍组织 name 属性,为何不把初始化属性的要领也共(借)用呢?

组织器借用法应运而生。如今我们把 name 属性的建立照样交给 Animal,然后再为 Human 增添 country 属性。我们在“暂时组织器法”基础上进一步完美之。

var F = function(){};
F.prototype = Animal.prototype;
var Human = function(){
    Animal.apply(this,arguments);
    this.country = arguments[1];
}
Human.prototype = new F;
Human.prototype.constructor = Human;
var man = new Human('HaoyCn','China');
console.log(man.country);// China

如许,我们就轻轻松松地完成了偷懒。这让我想到了PHP中掩盖组织函数的方法,以下

// PHP
class Human{
    public $name;
    public $country;
    function __construct($name,$country){
        parent::__construct($name);
        $this->country = $country;
    }
}

关于继续的话题到此结束。接下来谈拷贝。

原型属性拷贝法

利用了原型机制。在高等浏览器中,有 Object.create 要领来完成对对象的拷贝,我们如今就简朴地复原之:

Object.create = Object.create || function(obj){
    var F = function(){};
    F.prototype = obj;
    return new F;
}

能够看到,这是一种浅拷贝。假如我们对被拷贝对象举行修改,也会影响到新对象。举例以下:

var man = {
    name: 'HaoyCn',
    jump: function(){
        console.log('jumped');
    }
};
var monkey = Object.create(man);
monkey.jump();// jumped
man.jump = function(){
    console.log('I refuse');
};
monkey.jump();// I refuse

浅拷贝与深拷贝

题目摆在面前,怎样深拷贝?

我们拷贝对象除了“原型属性拷贝法”以外,还能够经由历程遍向来完成。如浅拷贝遍历:

var man = {
    name: 'HaoyCn',
    jump: function(){
        console.log('jumped');
    }
};
var monkey = {};
for(var i in man){
    monkey[i] = man[i]; 
}
monkey.jump();// jumped

而深拷贝要做的就是,假如属性照样个对象,就递归拷贝。

function deepCopy(origin,copy){
    copy = copy || {};
    for(var i in origin){
        if('object' === typeof origin[i]){
            // 推断是不是为数组另有更好方法,这里从简
            copy[i] = ('Array' === origin[i].constructor) ? [] : {};
            deepCopy(origin[i],copy[i]);
        }else{
            copy[i] = origin[i];
        }
    }
}

以上是深拷贝的一个简要道理代码。更庞杂的磨练历程,能够参考 jQuery.extend。然则,如许的拷贝(包含jQuery.extend的深拷贝)只能完成对地道对象的深拷贝,而函数、RegExp、Date等都没法深拷贝。

以上。关于对非地道对象的深拷贝的要领我还在探究中,比方挪用 toString() 后再组织对象的体式格局,但都不够完美,假如您在此方面有心得,敬请指教!

    原文作者:残阳映枫红
    原文地址: https://segmentfault.com/a/1190000003065140
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞