jQuery.extend扩展利器

来源:互联网 发布:知乎 东风 编辑:程序博客网 时间:2024/05/29 18:42

js面向对象

js是一种弱语言,对变量类型的校验是由变量的值确定。js对面向对象的封装特性由函数来实现,它充当编译语言中的class,当然在es6.0以上就开始使用class来定义类。那么js中的function所构造出的obj对象,任然可以动态扩展。这种不确定性、动态扩展性和封装性是js灵活、面向对象的体现。

var var1 = 10;console.log(typeof var1); // "number"var1 = true;console.log(typeof var1); // "boolean"var1 = {};console.log(typeof var1); // "object"var1 = function(){};console.log(typeof var1); // "function"//封装一个类function myClass(n, a) {    this.name = n;    this.age = a;};//每个函数都带了一个原型prototype,下面这种会覆盖了prototypeMyClass.prototype = {    constructor: MyClass, //修正myClass.constructor的指向,防止this.constructor()构造出错    getName: function() {        return this.name;    },    getAge: function() {        return this.age;    }    ... ... }//我们都知道编译语言中class有静态方法,那么js中class的静态方法呢?myClass.getClassName = function() {    return "myClass";};console.log(myClass.getClassName()); // "myClass"//此外,我们都知道,静态方法是class所有,对象不应该具有访问权限。var myObj = new MyClass("vzou", 18);console.log(myObj.getClassName()); // Uncaught TypeError: myObj.getClassName is not a functionconsole.log(myObj.getName()); // "vzou"//同理,有MyClass不具有myObj的方法的访问权限console.log(myClass.getName()); //Uncaught TypeError: myClass.getName is not a function

动态扩展

在java、C#和C++等编译语言中,一个类创建后便不能进行扩展(当然除了子类扩展、接口扩展和反射扩展),但js可以动态的进行扩展。js中给class扩展包括对象属性扩展和类属性扩展,分别对应与function.prototype和function。此外,class对象实例obj还可以扩展本obj专属属性。

//如何给MyClass扩展对象方法?通过添加extend方法,来将aruments参数扩展到prototype中//1、将简单obj扩展到MyClass.prototype上,简单obj是指obj的属性的值不是object    MyClass.prototype.extend = function(obj) {        //处理下obj,如果传进来的不是object时,直接return null;        if(typeof obj !== "object") return null;        //foreach可以遍历object对象属性, this指代当前对象,即this = MyClass.prototype,将obj中的所有属性都扩展到this中        for(var k in obj)            this[k] = obj[k];    };    MyClass.prototype.extend({        sayHaha: function() {            alert("haha");        },        sayHehe: function() {            alert("hehe");        }    });     var obj1 = new MyClass();    obj1.sayHaha(); //haha//工具方法,合并对象MyClass.extend(arg1, arg2, ....)    /**     *根据传入参数个数,来决定是扩展MyClass还是扩展第一个参数     */    MyClass.extend = function() {        //i从1开始, target表示被合并的对象arguments[0]        var i = 1, length = arguments.length, target = arguments[0] || {},        //name属性名,options=arguments[i], src=target[name], copyt=options[name]            name, options, copy;        //如果传入的参数个数为1,那么表示扩展this        if(i == arguments.length) {            target = this;        }        //这里要区分出arguments[i]中的属性是object还是简单属性(string,boolean,number,function)        for(; i < arguments.length; i++) {            options = arguments[i];            //当扩展对象是null或undefined时,直接过滤掉               if(!options) continue;              for(var name in options) {                copy = options[name];                //当扩展对象的属性为undefined时,直接过滤掉,为什么这么做呢?                //因为copy为undefined意味着options中未定义该属性。比如var obj = {}, obj["name"]的值为undefined                if(copy !== undefined) {                    target[name] = copy;                }            }           }        //将扩展后的对象返回        return target;    };

上面是将一个简单object对象扩展到MyClass对象属性。复杂的object对象:

var obj = {    person: {        name: "v_vzou",        age: 19,        getName: function() {            return this.name;        }        otherObj:{... ...}    }   }

怎么做?方案1:要是this[k]也有一个extend就可以解决。但是这种想法太烂;方案2:有个工具可以将this[k]和obj[k]进行合并,这不就是MyClass.extend的功能吗?将MyClass.prototype.extend和MyClass.extend结合使用来扩展复杂对象。

//2、将复杂obj扩展到MyClass.prototype    MyClass.prototype.extend = function(obj) {        //标识obj的属性是否为object        var isObj = false;        if(typeof obj !== "object") return false;        for(var k in obj) {            isObj = typeof obj[k] === "object";            if(!isObj) {                this[k] = obj[k];            } else {            //怎么做?            //方案1:要是this[k]也有一个extend就可以解决。但是这种想法太烂            //方案2:有个工具可以将this[k]和obj[k]进行合并,这不就是extend的功能吗?            MyClass.extend(this[k], obj[k]);            }           }    };//如果MyClass.prototype中已有一个属性person,它是一个object    MyClass.prototype.person = {        grade: 2,        hometown: "jiangxi"    };//给MyClass.prototype扩展复杂对象MyClass.prototype.extend({    sayHello: function() {        alert(this.person.hometown);    },    person: {        hometown: "fuzhou"    }});     obj = new MyClass();obj.sayHello(); //fuzhou    

简单对象和复杂对象通过extend进行扩展是否可以控制,对于extend方法中进行参数判断便可区分。

//用一个开关来控制复制对象的方式MyClass.extend = MyClass.prototype.extend = function() {    //i从1开始, target表示被合并的对象arguments[0]            var i = 1, length = arguments.length, target = arguments[0] || {},                //name属性名,options=arguments[i], src=target[name], copy=options[name], deep=false                name, options, src, copy, deep = false;            //如果第一个参数为深度复制开关,那么将target右移            if(typeof target === "boolean") {                deep = target;                target = arguments[1];                //此时target需要扩展是下标为2的参数,也就是跳过boolean、                i = 2;            }               //这里处理递归MyClass.extend(deep, target[name], copy)中target[name]是非object对象时问题            //extend(false, {...})、extend(true, 1, {...})、extend(false, "string", {...})            //obj为string、boolean、number时都不能添加属性。当参数不是object时,则人为的将target改变成object            if(typeof target !== "object" && !isFunction(target)) {                target = {};            }            //如果传入的参数个数为1,那么表示扩展this            if(i == arguments.length) {                target = this;                //此时需要减1,为啥呢?                //如果i == 1,那么此时extend({...}),length == 1,此时要扩展this,那么下边for循环从arguments第一个元素开始                //如果i == 2,那么此时extend(true, {...}), length == 2, 此时要扩展this,那么下边for循环从arguments第二个元素开始                i--;            }            //这里要区分出arguments[i]中的属性是object还是简单属性(string,boolean,number,function)            for(; i < arguments.length; i++) {                options = arguments[i];                //当扩展对象是null或undefined时,直接过滤掉                   if(!options) continue;                  for(var name in options) {                    copy = options[name];                    src = target[name];                    //如果是deep为true,表明需要进行复杂对象扩展,此时也要判断copy和src是否为object                    //为什么只判断copy呢?因为如果target为非object,此时全覆盖方式target[name]=copy;                    ////if条件成立:deep=true, isNotNullOrEmpty(copy)和isPlainObj(copy)                    if(deep && copy && isPlainObj(copy)) {                        //这里为什么要接收返回值?这里extend是三个参数,说明是将copy扩展到target[name]上                        //如果target[name]不是object,此时在extend里面必须处理,否则报错                        target[name] = MyClass.extend(deep, src, copy);                    } else if(copy !== undefined) {                        //当扩展对象的属性为undefined时,直接过滤掉,为什么这么做呢?                        //因为copy为undefined意味着options中未定义该属性。比如var obj = {}, obj["name"]的值为undefined                        target[name] = copy;                    }                }               }            //将扩展后的对象返回            return target;    }    //判断一个对象是否为Object构造对象,如{},new Object(),而 new MyClass()则不是        function isPlainObj(obj) {            //typeof obj为"object"时,obj可能会哪几种情况:            //1、{}和new Object(); 2、new 函数; 3、DOM对象(Document、HTMLElement); 4、Window。当为1时,返回true,其他的返回false;            //非object、obj.nodeType(1~12)说明为HTMLElement和window都不是plain Object            if(typeof obj !== "object" || obj.nodeType || isWindow(obj)) return false;            //new 函数也不是plain Object,此时obj肯定有contructor属性                //函数对象必有constructor构造器和prototype原型,但是有constructor和prototype是否就一定是函数对象呢?            if(obj.contsructor) return false;            return true;        }        //判断obj是否为window对象        function isWindow(obj) {            //剔除null异常,window.window这个属性成为判断window对象标识            return obj != null && obj === obj.window;        }        //判断传入对象是否为函数        function isFunction(obj) {            return type(obj) === "function";        }        //判断obj是否为数组        function isArray(obj) {            if(Array.isArray)                return Array.isArray(obj);            return type(obj) == "array";        }        //返回对象obj类型字符, undefined == null 为true        function type(obj) {            //这里用String包装obj是因为在IE8以下中,            //Object.prototype.toString.call(null)返回"[object Object]"            //Object.prototype.toString.call(undefined)返回"[object Object]"            return obj == null ? String(obj) :                 class2type[Object.prototype.toString.call(obj)];        }        //先将class的toString返回结果建立一个Map,然后有toString检索出该class的简写type        class2type = {            //这里绑定Undefined、Null没什么意义,在type函数中检测不完全            //"[object Undefined]": "undefined",            //"[object Null]": "null",             "[object Boolean]": "boolean",            "[object Number]": "number",            "[object String]": "string",            "[object Function]": "function",            "[object Array]": "array",            "[object Date]": "date",            "[object RegExp]": "regexp",            "[object Object]": "object"        };

js数组

数组是一种特殊的对象,通过int类型下标进行值访问,typeof [] === “object”。js中数组是线性可变长度、类型不定的有序列表,区别于编译语言java、c++和C#等数组是定长、类型一直的有限列表。那么对于数组这种特殊的对象,需要在extend进行特殊处理。

var arr = []; //arr = new Array() 或 Array()//往数组中添加元素,此时数组是一个容器arr[0] = 0; arr[1] = 1; ... ...//往数组中添加属性arr.name = "NumberArray";arr.countOne = function() {    return this[0]; };for(var name in arr)    console.log(name);//01...、name、countOne

extend

extend可以扩展框架本身,也可以作为对象扩展工具,但它内部实现包含诸多的规则,其中个关于数组Array和Plain Object就是jQuery中特殊处理的对象。

//特殊处理数组对象,当为深度扩展时,extend(true, obj1, obj2, ...)MyClass.extend = MyClass.prototype.extend = function() {    var i = 1, length = arguments.length, target = arguments[0] || {},    //copyIsArray用于标识target[name]是否为数组,clone用于src是否为object的判断结果值    name, options, src, copy, deep = false, copyIsArray = false, clone;    if(typeof target === "boolean") {        deep = target;        target = arguments[1];        i = 2;    }       if(typeof target !== "object" && !isFunction(target)) {        target = {};    }    if(i == arguments.length) {        target = this;        i--;    }    for(; i < arguments.length; i++) {        options = arguments[i];        if(!options) continue;          for(var name in options) {            copy = options[name];            src = target[name];            //if条件成立:deep=true, isNotNullOrEmpty(copy)和isPlainObj(copy)            //数组对象也可以拥有复杂属性,此时isPlainObj返回false;所以必须检测obj是否为数组            if(deep && copy && (isPlainObj(copy)) ||             //copyIsArray放到这里进行赋值好处是懒解析,因为只有deep && copy为true、isPlainObj为false才会解析copyIsArray                 (copyIsArray = isArray(copy))) {                //isPlainObj、isArray这两者要区分开来,所以有if...else...                if(copyIsArray) {                    copyIsArray = false; //置回初始状态                    //这样为了避免copy是数组,而src不是,就是只能扩展json格式                    clone = src && isArray(src) ? src : {};                } else {                //这里杜绝src为非object,而是string、boolean、number,Function构造来的object、HTMLElement、window、RegExp、Date                //这里是为了避免copy是plain Object,而src不是,就是只能扩展json格式                clone = src && isPlainObj(src) ? src : {};            }                target[name] = MyClass.extend(deep, src, copy);            } else if(copy !== undefined) {                        target[name] = copy;            }        }       }    return target;}

this引用

对象是引用类型,对象可以具有属性,而js中属性可以是基本类型、引用类型,具体类型是在解析器中进行判断。js动态扩展固然带来灵活性,但是也带来了无限循环的潜在性。

//简单对象person刚开始定义了两个属性name、agevar person = {    name: "vzou",    age: 18};//... ... 中间穿插了很多的逻辑代码;然后动态添加了属性me,此时指向本身person.me = person; //或者this//那么MyClass.extend(true, person, person.me),则会造成无限循环。这也是在编译语言中可以智能检测到的循环引用问题。//处理自身属性引用自身,然后进行扩展自身的无限循环问题,为了避免循环引用,则在extend中进行剔除MyClass.extend = MyClass.prototype.extend = function() {    var i = 1, length = arguments.length, target = arguments[0] || {},    //copyIsArray用于标识target[name]是否为数组,clone用于src是否为object的判断结果值    name, options, src, copy, deep = false, copyIsArray = false, clone;    if(typeof target === "boolean") {        deep = target;        target = arguments[1];        i = 2;    }       if(typeof target !== "object" && !isFunction(target)) {        target = {};    }    if(i == arguments.length) {        target = this;        i--;    }    for(; i < arguments.length; i++) {        options = arguments[i];        if(!options) continue;          for(var name in options) {            copy = options[name];            src = target[name];            if(copy === target) continue;            if(deep && copy && (isPlainObj(copy)) || (copyIsArray = isArray(copy))) {                if(copyIsArray) {                    copyIsArray = false;                     clone = src && isArray(src) ? src : {};                } else {                    clone = src && isPlainObj(src) ? src : {};                }                target[name] = MyClass.extend(deep, src, copy);            } else if(copy !== undefined) {                target[name] = copy;            }        }       }    return target;}

结论

jQuery.extend = jQuery.fn.extend扩展函数可以给jQuery框架扩展功能,也可以扩展某个对象,这给jQuery框架带来了巨大的灵活性。那么extend函数内部的扩展过程,其实根据js动态扩展特性而来。翻看源码带来的感受是,plain Object、函数对象、HTMLElement、Window和数组等js对象都分类进行处理,深度扩展不只是简单的对象属性的覆盖,而是有plain object和Array对象进行区分扩展。