07月18, 2016

js记录之对象拷贝

今天在用angularjs的时候,有一个数组对象需要考虑深度拷贝。可angular.extend方法只支持浅拷贝。

既然说起来了拷贝的问题,那么我们来看一下主流的库关于继承的实现

  • jQuery
//https://github.com/jquery/jquery/blob/master/src/core.js
jQuery.extend = jQuery.fn.extend = function() {
    var options, name, src, copy, copyIsArray, clone,
        target = arguments[ 0 ] || {},
        i = 1,
        length = arguments.length,
        deep = false;

    // Handle a deep copy situation
    if ( typeof target === "boolean" ) {
        deep = target;

        // Skip the boolean and the target
        target = arguments[ i ] || {};
        i++;
    }

    // Handle case when target is a string or something (possible in deep copy)
    if ( typeof target !== "object" && !jQuery.isFunction( target ) ) {
        target = {};
    }

    // Extend jQuery itself if only one argument is passed
    if ( i === length ) {
        target = this;
        i--;
    }

    for ( ; i < length; i++ ) {

        // Only deal with non-null/undefined values
        if ( ( options = arguments[ i ] ) != null ) {

            // Extend the base object
            for ( name in options ) {
                src = target[ name ];
                copy = options[ name ];

                // Prevent never-ending loop
                if ( target === copy ) {
                    continue;
                }

                // Recurse if we"re merging plain objects or arrays
                if ( deep && copy && ( jQuery.isPlainObject( copy ) ||
                    ( copyIsArray = jQuery.isArray( copy ) ) ) ) {

                    if ( copyIsArray ) {
                        copyIsArray = false;
                        clone = src && jQuery.isArray( src ) ? src : [];

                    } else {
                        clone = src && jQuery.isPlainObject( src ) ? src : {};
                    }

                    // Never move original objects, clone them
                    target[ name ] = jQuery.extend( deep, clone, copy );

                // Don"t bring in undefined values
                } else if ( copy !== undefined ) {
                    target[ name ] = copy;
                }
            }
        }
    }

    // Return the modified object
    return target;
};
  • undersore
var _ = {};

_.isObject = function(obj) {
    var type = typeof obj;
    return type === "function" || type === "object" && !!obj;
};

_.allKeys = function(obj) {
    if (!_.isObject(obj)) return [];
    var keys = [];
    for (var key in obj) keys.push(key);
    return keys;
};


// An internal function for creating assigner functions.
var createAssigner = function(keysFunc, defaults) {
    return function(obj) {
        var length = arguments.length;
        if (defaults) obj = Object(obj);
        if (length < 2 || obj == null) return 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];
                if (!defaults || obj[key] === void 0) obj[key] = source[key];
            }
        }
        return obj;
    };
};

// Extend a given object with all the properties in passed-in object(s).
_.extend = createAssigner(_.allKeys);
_.defaults = createAssigner(_.allKeys, true);

个人感觉在underscore里面,高阶函数用法比较多,但它的这个实现只是做了浅复制。

  • angularjs
function baseExtend(dst, objs, deep) {
    var h = dst.$$hashKey;

    for (var i = 0, ii = objs.length; i < ii; ++i) {
        var obj = objs[i];
        if (!isObject(obj) && !isFunction(obj)) continue;
        var keys = Object.keys(obj);
        for (var j = 0, jj = keys.length; j < jj; j++) {
            var key = keys[j];
            var src = obj[key];

            if (deep && isObject(src)) {
                if (isDate(src)) {
                    dst[key] = new Date(src.valueOf());
                } else if (isRegExp(src)) {
                    dst[key] = new RegExp(src);
                } else if (src.nodeName) {
                    dst[key] = src.cloneNode(true);
                } else if (isElement(src)) {
                    dst[key] = src.clone();
                } else {
                    if (!isObject(dst[key])) dst[key] = isArray(src) ? [] : {};
                    baseExtend(dst[key], [src], true);
                }
            } else {
                dst[key] = src;
            }
        }
    }

    setHashKey(dst, h);
    return dst;
}


function extend(dst) {
    return baseExtend(dst, slice.call(arguments, 1), false);
}

function merge(dst) {
    return baseExtend(dst, slice.call(arguments, 1), true);
}

function publishExternalAPI(angular) {
  extend(angular, {
    "bootstrap": bootstrap,
    "copy": copy,
    "extend": extend,
    "merge": merge,
    "equals": equals,
    "element": jqLite,
    "forEach": forEach,
    "injector": createInjector,
    "noop": noop,
    "bind": bind,
    "toJson": toJson,
    "fromJson": fromJson,
    "identity": identity,
    "isUndefined": isUndefined,
    "isDefined": isDefined,
    "isString": isString,
    "isFunction": isFunction,
    "isObject": isObject,
    "isNumber": isNumber,
    "isElement": isElement,
    "isArray": isArray,
    "version": version,
    "isDate": isDate,
    "lowercase": lowercase,
    "uppercase": uppercase,
    "callbacks": {counter: 0},
    "getTestability": getTestability,
    "$$minErr": minErr,
    "$$csp": csp,
    "reloadWithDebugInfo": reloadWithDebugInfo
  });

publishExternalAPI(angular);

看起来是问题解决了,extend方法是浅复制,但是merge是深复制。

感觉有空的时候还是得多翻一下源码,才能知道有哪些好东西。

另类的实现

var arr1 = [{a: 1}, {b: 2}]
var arr2 = JSON.parse(JSON.stringify(arr1));

看到这个用法,瞬间感觉吊爆了。。不过它的写法只有支持JSON的浏览器才行。

补充于2016年7月21号

但上面的话,有一个问题是这样的:

var obj = {
    a: 1,    
    b: function(){
          alert(1)
    }
}

不标准的json,用上面的方法得到的新对象,会过滤掉function那一条属性键值对。

本文链接:www.my-fe.pub/post/js-record-copy.html

-- EOF --

Comments

评论加载中...

注:如果长时间无法加载,请针对 disq.us | disquscdn.com | disqus.com 启用代理。