老年人的码农梦

导航

浅拷贝与深拷贝

浅拷贝

function extend(target, options) {
    for(name in options) {
        target[name] = options[name];
    }
}

如果对象是引用,则两者互通,改一个,另一个也跟着变:

var target = {};

var options = {
    a: {a: 1, b: 2, c: 3},
    b: 2,
    c: 3
};

extend(target, options);

target.a.a = 12;

console.log(options);

深拷贝

function extend(target, options) {
    for(name in options) {
        if(isPlainObject(options[name]))
            target[name] = extend(target[name], options[name]);
        else
            target[name] = options[name];
    }
    return target;
}

判断其是不是一个纯粹的对象(通过 {} 或者 new Object 创建的),如果是,则递归拷贝。

纯粹对象

通用的判断数据类型的方式 Object.prototype.toString.call() 判断是否是 [object Object],调用原型方法最简单的方式就是实例化后再调用:

var object = new Object();
object.toString.call({});

new Object(){} 又算是同一回事儿,下面这个代码也应该是正确的:

{}.toString.call({});

实际上,这个跑不通,js 把行首的 {} 当作空语句块来处理了,必须得这样:

({}).toString.call({});

最简单的能识别出的就是没有原型的对象,单纯的只是存储数据,没有原型链,而Object.getPrototypeOf(object) 可以返回对象的原型:

var object1 = Object.create(null);
console.log(Object.getPrototypeOf(object1));

var object2 = {};
console.log(Object.getPrototypeOf(object2));

而我们自定义的构造函数,也会同样返回 [object Object],这并不是我们想要的纯粹对象:

function A(){}

var a = new A();

({}).toString.call(a);

要把这一类的都排除出去:

Object.toString();

Object.toString.call(function(){});

可以看出两者生成的字符串并不一致,所以只要比较下字符串就可以看出是由 Object 直接实例化的对象还是通过其它自定义行为实例化的,而通过实例化对象找出构造函数可以通过 constructor

Object.toString.call(Object.getPrototypeOf({}).constructor);

实际在 jQuery 源码中用了一种非常绕的表达方式:

Object.prototype.hasOwnProperty.toString.call(Object);

综上,判断纯粹的对象:

var getProto = Object.getPrototypeOf;

var class2type = {};

var toString = class2type.toString;

var hasOwn = class2type.hasOwnProperty;

var fnToString = hasOwn.toString;

var ObjectFunctionString = fnToString.call( Object );

function isPlainObject(obj) {
    var proto, Ctor;
    
    if(!obj || toString.call(obj) !== "[object Object]") {
        return false;
    }

    proto = getProto(obj);

    if(!proto) {
        return true;
    }

    Ctor = hasOwn.call(proto, "constructor") && proto.constructor;
    return typeof Ctor === "function" && fnToString.call(Ctor) === ObjectFunctionString;
}

当然,还有一个小的错误,extend(target[name], options[name]) 递归调用的时候 target[name] 并一定就存在,修改如下:

function extend(target, options) {
    for(name in options) {
        target[name] = target[name]? target[name] : {};
        if(isPlainObject(options[name]))
            target[name] = extend(target[name], options[name]);
        else
            target[name] = options[name];
    }
    return target;
}

jQuery 中的类型判断

jQuery 中使用了一个对象 class2type 来存储各种类型的字符串表达形式。

var class2type = {};

var arrType = "Boolean Number String Function Array Date RegExp Object Error Symbol".split(" ");

for(var i = 0; i < arrType.length; i++) {
    class2type["[object " + arrType[i] + "]"] = arrType[i].toLowerCase();
}

有了这个类型字符串存储对象,判断类型就很简单了:

function type(obj) {
    if(obj == null) {
        return obj + "";    
    }

    return typeof obj === "object" || typeof obj === "function" ?
        class2type[({}).toString.call(obj)] || "object" :
        typeof obj;
}

类数组

可以直接通过 [] 访问就可以称之为类数组:

  1. 本身就是数组;

  2. 存在 length 属性,且其为 0

  3. length 是数字,且 length - 1(最后一个元素) 存在。

   function isArrayLike(obj) {
            var length = !!obj && "length" in obj && obj.length,
                type = type(obj);
        
            if (type === "function" || (obj != null && obj === obj.window)) {
                return false;
            }
        
            return type === "array" || length === 0 ||
                typeof length === "number" && length > 0 && (length - 1) in obj;
        }

each 方法

遍历,而后挨个执行传入的函数,分了类数组与非类数组,不过内部处理逻辑是一致的:

function each(obj, callback) {
    var length, i = 0;
	
    if (isArrayLike(obj)) {
        length = obj.length;
        for(; i < length; i++) {
            if (callback.call(obj[i], i, obj[i] ) === false) {
	        break;
	    }
        }
    } else {
        for(i in obj) {
            if(callback.call(obj[i], i, obj[i] ) === false) {
                break;
            }
        }
    }
	
    return obj;
}

组合起来的 extend

有了上面那些大大小小的拆分开的小方法,就能组合成 jQuery 中的 extend 方法了:

(function(window) {
	
	"use strict";

	var getProto = Object.getPrototypeOf;

	var class2type = {};

	var toString = class2type.toString;

	var hasOwn = class2type.hasOwnProperty;

	var fnToString = hasOwn.toString;

	var ObjectFunctionString = fnToString.call( Object );

	var jQuery = function() {
		return new jQuery.fn.init();
	};

	jQuery.fn = jQuery.prototype;

	jQuery.extend = jQuery.fn.extend = function() {
		var options, name, src, copy, copyIsArray, clone,
			target = arguments[0] || {},
			i = 1,
			length = arguments.length,
			deep = false;
	
		if(typeof target === "boolean") {
			deep = target;
	
			target = arguments[i] || {};
			i++;
		}
	
		if(typeof target !== "object" && !jQuery.isFunction(target)) {
			target = {};
		}
	
		if(i === length) {
			target = this;
			i--;
		}
	
		for(; i < length; i++) {
			if((options = arguments[i]) != null) {
				for(name in options) {
					src = target[name];
					copy = options[name];
	
					if(target === copy) {
						continue;
					}
	
					if(deep && copy && (jQuery.isPlainObject(copy) ||
						(copyIsArray = Array.isArray(copy)))) {
	
						if (copyIsArray) {
							copyIsArray = false;
							clone = src && Array.isArray(src) ? src : [];
	
						} else {
							clone = src && jQuery.isPlainObject(src) ? src : {};
						}
	
						target[name] = jQuery.extend(deep, clone, copy);
	
					} else if (copy !== undefined) {
						target[name] = copy;
					}
				}
			}
		}
	
		return target;
	};

	jQuery.extend({
		isFunction: function(obj) {
			return jQuery.type(obj) === "function";
		},

		isWindow: function(obj) {
			return obj != null && obj === obj.window;
		},

		isPlainObject: function(obj) {
			var proto, Ctor;
	
			if(!obj || toString.call(obj) !== "[object Object]") {
				return false;
			}
	
			proto = getProto(obj);
	
			if (!proto) {
				return true;
			}
	
			Ctor = hasOwn.call(proto, "constructor") && proto.constructor;
			return typeof Ctor === "function" && fnToString.call(Ctor) === ObjectFunctionString;
		},

		type: function(obj) {
			if (obj == null) {
				return obj + "";
			}

			return typeof obj === "object" || typeof obj === "function" ?
				class2type[toString.call(obj)] || "object" :
				typeof obj;
		},

		each: function(obj, callback) {
			var length, i = 0;
	
			if (isArrayLike(obj)) {
				length = obj.length;
				for(; i < length; i++) {
					if (callback.call(obj[i], i, obj[i] ) === false) {
						break;
					}
				}
			} else {
				for(i in obj) {
					if(callback.call(obj[i], i, obj[i] ) === false) {
						break;
					}
				}
			}
	
			return obj;
		}
	});

	jQuery.each("Boolean Number String Function Array Date RegExp Object Error Symbol".split(" "), function(i, name) {
		class2type[ "[object " + name + "]" ] = name.toLowerCase();
	});

	function isArrayLike(obj) {
		var length = !!obj && "length" in obj && obj.length,
			type = jQuery.type(obj);
	
		if (type === "function" || jQuery.isWindow(obj)) {
			return false;
		}
	
		return type === "array" || length === 0 ||
			typeof length === "number" && length > 0 && (length - 1) in obj;
	}

	var init = jQuery.fn.init = function() {};

	init.prototype = jQuery.fn;

	
	window.jQuery = window.$ = jQuery;

	
	return jQuery;
})(window);

小小的吐槽

jQuery 扩展并没有防覆盖措施,很轻松就能通过外部扩展来覆盖原先定义好的方法,可能这就是前端最大的无奈,一切都放在明面上,反正我的东西你都能看到,源码都能直接改,何必费劲多写东西来防止你覆盖我原先的方法呢?

代码地址

https://github.com/oldmanscode/jq_step_by_step/blob/master/step3.js

posted on 2017-12-29 16:27  老年人的码农梦  阅读(160)  评论(0)    收藏  举报