克隆与扩展

扩展

underscore 中提供了两个方法用于对象属性的扩展:

  • _.extend
  • _.extendOwn

这两个函数都是由内部函数 createAssigner 进行创建的。

createAssigner

createAssigner 接受 2 个参数:

  • keysFunc:获得对象属性的方式
  • defaults: 来声明是否要覆盖属性

返回一个属性分配器。

  var createAssigner = function (keysFunc, defaults) {
    return function (obj) {
        // 参数的长度反映了传入对象的个数
        var length = arguments.length;
        if (defaults) obj = Object(obj);
        if (length < 2 || obj == null) return obj;
        // 遍历每个对象,从中不断获得属性,赋给obj
        for (var index = 1; index < length; index++) {
            var source = arguments[index],
                keys = keysFunc(source),
                l = keys.length;
            for (var i = 0; i < l; i++) {
                var key = keys[i];
                // 如果不是defaults模式,则会覆盖原来key的value
                // 如果是defaults模式,则只会设置原来为undefined的属性
                if (!defaults || obj[key] === void 0) obj[key] = source[key];
            }
        }
        return obj;
    };
  };

._extends

._extends(obj, ...objs): 用 ...objs 扩展 obj

源码

// _.allKeys会获得对象自身及其原型链上的所有属性
_.extends = createAssigner(_.allKeys)

用例

var student = {
    name: 'yoyoyohamapi',
    age: 13,
    sex: 'girl'
};
function Hobby(){
    this.hobby = "soccer";
}
Hobby.prototype.hate = "volleyball";
_.extend(student, new Hobby(),{sex:'boy'});
// => student: {
//    name: "yoyoyohamapi",
//    age: 13,
//    hobby: "soccer",
//    hate: "volleyball", // 原型链上的属性也被继承了
//    sex: "boy" // 会覆盖属性
//}

._extendOwn = _.assign

._extendOwn(obj, ...objs): 用 ...objs 扩展 obj,不会继承原型链上的属性。

源码

// _.keys只会获得自身属性
_.extendOwn = _.assign = createAssigner(_.keys);

用例

var student = {
    name: "yoyoyohamapi",
    age: 13,
    sex: 'girl'
};
function Hobby(){
    this.hobby = "soccer";
}
Hobby.prototype.hate = "volleyball";
_.extend(student, new Hobby(),{sex:'boy'});
// => student: {
//    name: "yoyoyohamapi",
//    age: 13,
//    hobby: "soccer",
//    sex: "boy" // 会存在属性覆盖
//}

_.defaults

_.defaults(obj, attrs): 设置 obj 的默认属性。

在创建一个库或者组价时,我们会为这个库或者组件定义一个基本配置,考虑到灵活性,也允许用户自定义一些配置项,最终,这个插件的配置将会有 用户配置默认配置 共同决定。underscore 中的 _.defaults 函数正是用来完成这个任务的。

源码

_.defaults = createAssigner(_.allKeys, true);

通过向 createAssigner 传递第二个参数 true,后续的对象都会填充 obj 中为 undefined 的属性。

用例

var defaultOptions = {
  method: 'GET',
  'content-type': 'application/json',
  accept: 'application/json'
};

var options = {
  method: 'POST'
};

var opts = _.defaults(options, defaultOptions);
// opts => {
//   method: 'POST',
//   'content-type': 'application/json',
//   accept: 'application/json'
//}

Object.assign

通过 MDN 给出的 polyfill 看出,Object.assign 在扩展对象属性的时候,不会拷贝原型链上的属性:

if (typeof Object.assign != 'function') {
  Object.assign = function(target) {
    'use strict';
    if (target == null) {
      throw new TypeError('Cannot convert undefined or null to object');
    }

    target = Object(target);
    for (var index = 1; index < arguments.length; index++) {
      var source = arguments[index];
      if (source != null) {
        for (var key in source) {
          if (Object.prototype.hasOwnProperty.call(source, key)) {
            target[key] = source[key];
          }
        }
      }
    }
    return target;
  };
}

用例:

var student = {
    name: 'yoyoyohamapi',
    age: 13,
    sex: 'girl'
};
function Hobby(){
    this.hobby = "soccer";
}
Hobby.prototype.hate = "volleyball";
Object.assign(student, new Hobby(),{sex:'boy'});
// => student: {
//    name: "yoyoyohamapi",
//    age: 13,
//    hobby: "soccer",
//    sex: "boy" // 会覆盖属性
//}

注意,无论是 underscore 提供的 extend 相关方法还是 Object.assign,对象属性的复制都是浅复制:

var son = {
  family: {
    dad: 'wxj'
  }
}

var daughter = _.extend({}, son);
daughter.family === son.family; // true

daughter = Object.assign({}, son);
daughter.family === son.family; // true

克隆

underscore 中提供了一个方法用于克隆一个对象:

  • _.clone

_.clone

_.clone(obj):克隆一个 obj 的对象副本。

underscore 提供的克隆是浅克隆,亦即,对象中的数组、对象属性引用不会发生变化:

_.clone = function (obj) {
    if (!_.isObject(obj)) return obj;
    // 可以看出,如果obj是一个对象,那么该函数基于_.extend实现
    return _.isArray(obj) ? obj.slice() : _.extend({}, obj);
};

用例

var family = {
  name: '304',
  dad: {
    name: 'wxj',
    age: 32
  }
}

var cloned = _.clone(family);

// 克隆是浅克隆
cloned.dad === family.dad; // => true
family.dad.name = 'john';
cloned.dad.name; // => 'john'

深度克隆

underscore 并没用提供深度克隆的方法,不过借助于递归,我们构造一个 深度克隆 的方法:

var deepClone = function(obj) {
  // 如果待克隆对象是数组
  if(_.isArray(obj)) {
      return _.map(obj, function(elem){
        return _.isArray(elem) || _.isObject(elem) ? deepClone(elem) : elem;
      });
  } else if(_.isObject(obj)){
      return _.reduce(obj, function(newObj, value, key){
        newObj[key] = _.isArray(value) || _.isObject(value) ? deepClone(value) : value;
        return newObj;
      }, {});
  } else {
    return obj;
  }
}

测试

var family = {
  name: '304',
  dad: {
    name: 'wxj',
    age: 32
  }
}

var cloned = _.clone(family);

// 克隆是浅克隆
cloned.dad === family.dad; // => false
family.dad.name = 'john';
cloned.dad.name; // => 'wxj'

深度克隆并非那么容易实现的,他包含有太多语义界定和边界用例,详情可以参看知乎上的这篇讨论:
JavaScript 如何完整实现深度克隆对象?

results matching ""

    No results matching ""