成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

JavaScript專題之從零實(shí)現(xiàn)jQuery的extend

wangtdgoodluck / 1534人閱讀

摘要:不過(guò)的實(shí)現(xiàn)中,多了很多細(xì)節(jié)上的判斷,比如第一個(gè)參數(shù)是否是布爾值,是否是一個(gè)對(duì)象,不傳參數(shù)時(shí)的默認(rèn)值等。

JavaScritp 專題系列第七篇,講解如何從零實(shí)現(xiàn)一個(gè) jQuery 的 extend 函數(shù)

前言

jQuery 的 extend 是 jQuery 中應(yīng)用非常多的一個(gè)函數(shù),今天我們一邊看 jQuery 的 extend 的特性,一邊實(shí)現(xiàn)一個(gè) extend!

extend 基本用法

先來(lái)看看 extend 的功能,引用 jQuery 官網(wǎng):

Merge the contents of two or more objects together into the first object.

翻譯過(guò)來(lái)就是,合并兩個(gè)或者更多的對(duì)象的內(nèi)容到第一個(gè)對(duì)象中。

讓我們看看 extend 的用法:

jQuery.extend( target [, object1 ] [, objectN ] )

第一個(gè)參數(shù) target,表示要拓展的目標(biāo),我們就稱它為目標(biāo)對(duì)象吧。

后面的參數(shù),都傳入對(duì)象,內(nèi)容都會(huì)復(fù)制到目標(biāo)對(duì)象中,我們就稱它們?yōu)榇龔?fù)制對(duì)象吧。

舉個(gè)例子:

var obj1 = {
    a: 1,
    b: { b1: 1, b2: 2 }
};

var obj2 = {
    b: { b1: 3, b3: 4 },
    c: 3
};

var obj3 = {
    d: 4
}

console.log($.extend(obj1, obj2, obj3));

// {
//    a: 1,
//    b: { b1: 3, b3: 4 },
//    c: 3,
//    d: 4
// }

當(dāng)兩個(gè)對(duì)象出現(xiàn)相同字段的時(shí)候,后者會(huì)覆蓋前者,而不會(huì)進(jìn)行深層次的覆蓋。

extend 第一版

結(jié)合著上篇寫(xiě)得 《JavaScript專題之深淺拷貝》,我們嘗試著自己寫(xiě)一個(gè) extend 函數(shù):

// 第一版
function extend() {
    var name, options, src, copy;
    var length = arguments.length;
    var i = 1;
    var target = arguments[0];

    for (; i < length; i++) {
        options = arguments[i];
        if (options != null) {
            for (name in options) {
                src = target[name];
                copy = options[name];
                if (copy !== undefined){
                    target[name] = copy;
                }
            }
        }
    }

    return target;
};
extend 深拷貝

那如何進(jìn)行深層次的復(fù)制呢?jQuery v1.1.4 加入了一個(gè)新的用法:

jQuery.extend( [deep], target, object1 [, objectN ] )

也就是說(shuō),函數(shù)的第一個(gè)參數(shù)可以傳一個(gè)布爾值,如果為 true,我們就會(huì)進(jìn)行深拷貝,false 依然當(dāng)做淺拷貝,這個(gè)時(shí)候,target 就往后移動(dòng)到第二個(gè)參數(shù)。

還是舉這個(gè)例子:

var obj1 = {
    a: 1,
    b: { b1: 1, b2: 2 }
};

var obj2 = {
    b: { b1: 3, b3: 4 },
    c: 3
};

var obj3 = {
    d: 4
}

console.log($.extend(true, obj1, obj2, obj3));

// {
//    a: 1,
//    b: { b1: 3, b2: 2, b3: 4 },
//    c: 3,
//    d: 4
// }

因?yàn)椴捎昧松羁截?,?huì)遍歷到更深的層次進(jìn)行添加和覆蓋。

extend 第二版

我們來(lái)實(shí)現(xiàn)深拷貝的功能,值得注意的是:

需要根據(jù)第一個(gè)參數(shù)的類型,確定 target 和要合并的對(duì)象的下標(biāo)起始值。

如果是深拷貝,根據(jù) copy 的類型遞歸 extend。

// 第二版
function extend() {
    // 默認(rèn)不進(jìn)行深拷貝
    var deep = false;
    var name, options, src, copy;
    var length = arguments.length;
    // 記錄要復(fù)制的對(duì)象的下標(biāo)
    var i = 1;
    // 第一個(gè)參數(shù)不傳布爾值的情況下,target默認(rèn)是第一個(gè)參數(shù)
    var target = arguments[0] || {};
    // 如果第一個(gè)參數(shù)是布爾值,第二個(gè)參數(shù)是才是target
    if (typeof target == "boolean") {
        deep = target;
        target = arguments[i] || {};
        i++;
    }
    // 如果target不是對(duì)象,我們是無(wú)法進(jìn)行復(fù)制的,所以設(shè)為{}
    if (typeof target !== "object") {
        target = {}
    }

    // 循環(huán)遍歷要復(fù)制的對(duì)象們
    for (; i < length; i++) {
        // 獲取當(dāng)前對(duì)象
        options = arguments[i];
        // 要求不能為空 避免extend(a,,b)這種情況
        if (options != null) {
            for (name in options) {
                // 目標(biāo)屬性值
                src = target[name];
                // 要復(fù)制的對(duì)象的屬性值
                copy = options[name];

                if (deep && copy && typeof copy == "object") {
                    // 遞歸調(diào)用
                    target[name] = extend(deep, src, copy);
                }
                else if (copy !== undefined){
                    target[name] = copy;
                }
            }
        }
    }

    return target;
};

在實(shí)現(xiàn)上,核心的部分還是跟上篇實(shí)現(xiàn)的深淺拷貝函數(shù)一致,如果要復(fù)制的對(duì)象的屬性值是一個(gè)對(duì)象,就遞歸調(diào)用 extend。不過(guò) extend 的實(shí)現(xiàn)中,多了很多細(xì)節(jié)上的判斷,比如第一個(gè)參數(shù)是否是布爾值,target 是否是一個(gè)對(duì)象,不傳參數(shù)時(shí)的默認(rèn)值等。

接下來(lái),我們看幾個(gè) jQuery 的 extend 使用效果:

target 是函數(shù)

在我們的實(shí)現(xiàn)中,typeof target 必須等于 object,我們才會(huì)在這個(gè) target 基礎(chǔ)上進(jìn)行拓展,然而我們用 typeof 判斷一個(gè)函數(shù)時(shí),會(huì)返回function,也就是說(shuō),我們無(wú)法在一個(gè)函數(shù)上進(jìn)行拓展!

什么,我們還能在一個(gè)函數(shù)上進(jìn)行拓展!!

當(dāng)然啦,畢竟函數(shù)也是一種對(duì)象嘛,讓我們看個(gè)例子:

function a() {}

a.target = "b";

console.log(a.target); // b

實(shí)際上,在 underscore 的實(shí)現(xiàn)中,underscore 的各種方法便是掛在了函數(shù)上!

所以在這里我們還要判斷是不是函數(shù),這時(shí)候我們便可以使用《JavaScript專題之類型判斷(上)》中寫(xiě)得 isFunction 函數(shù)

我們這樣修改:

if (typeof target !== "object" && !isFunction(target)) {
    target = {};
}
類型不一致

其實(shí)我們實(shí)現(xiàn)的方法有個(gè)小 bug ,不信我們寫(xiě)個(gè) demo:

var obj1 = {
    a: 1,
    b: {
        c: 2
    }
}

var obj2 = {
    b: {
        c: [5],

    }
}

var d = extend(true, obj1, obj2)
console.log(d);

我們預(yù)期會(huì)返回這樣一個(gè)對(duì)象:

{
    a: 1,
    b: {
        c: [5]
    }
}

然而返回了這樣一個(gè)對(duì)象:

{
    a: 1,
    b: {
        c: {
            0: 5
        }
    }
}

讓我們細(xì)細(xì)分析為什么會(huì)導(dǎo)致這種情況:

首先我們?cè)诤瘮?shù)的開(kāi)始寫(xiě)一個(gè) console 函數(shù)比如:console.log(1),然后以上面這個(gè) demo 為例,執(zhí)行一下,我們會(huì)發(fā)現(xiàn) 1 打印了三次,這就是說(shuō) extend 函數(shù)執(zhí)行了三遍,讓我們捋一捋這三遍傳入的參數(shù):

第一遍執(zhí)行到遞歸調(diào)用時(shí):

var src = { c: 2 };
var copy = { c: [5]};

target[name] = extend(true, src, copy);

第二遍執(zhí)行到遞歸調(diào)用時(shí):

var src = 2;
var copy = [5];

target[name] = extend(true, src, copy);

第三遍進(jìn)行最終的賦值,因?yàn)?src 是一個(gè)基本類型,我們默認(rèn)使用一個(gè)空對(duì)象作為目標(biāo)值,所以最終的結(jié)果就變成了對(duì)象的屬性!

為了解決這個(gè)問(wèn)題,我們需要對(duì)目標(biāo)屬性值和待復(fù)制對(duì)象的屬性值進(jìn)行判斷:

判斷目標(biāo)屬性值跟要復(fù)制的對(duì)象的屬性值類型是否一致:

如果待復(fù)制對(duì)象屬性值類型為數(shù)組,目標(biāo)屬性值類型不為數(shù)組的話,目標(biāo)屬性值就設(shè)為 []

如果待復(fù)制對(duì)象屬性值類型為對(duì)象,目標(biāo)屬性值類型不為對(duì)象的話,目標(biāo)屬性值就設(shè)為 {}

結(jié)合著《JavaScript專題之類型判斷(下)》中的 isPlainObject 函數(shù),我們可以對(duì)類型進(jìn)行更細(xì)致的劃分:

var clone, copyIsArray;

...

if (deep && copy && (isPlainObject(copy) ||
        (copyIsArray = Array.isArray(copy)))) {

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

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

    target[name] = extend(deep, clone, copy);

} else if (copy !== undefined) {
    target[name] = copy;
}
循環(huán)引用

實(shí)際上,我們還可能遇到一個(gè)循環(huán)引用的問(wèn)題,舉個(gè)例子:

var a = {name : b};
var b = {name : a}
var c = extend(a, b);
console.log(c);

我們會(huì)得到一個(gè)可以無(wú)限展開(kāi)的對(duì)象,類似于這樣:

為了避免這個(gè)問(wèn)題,我們需要判斷要復(fù)制的對(duì)象屬性是否等于 target,如果等于,我們就跳過(guò):

...
src = target[name];
copy = options[name];

if (target === copy) {
    continue;
}
...

如果加上這句,結(jié)果就會(huì)是:

{name: undefined}
最終代碼
function extend() {
    // 默認(rèn)不進(jìn)行深拷貝
    var deep = false;
    var name, options, src, copy, clone, copyIsArray;
    var length = arguments.length;
    // 記錄要復(fù)制的對(duì)象的下標(biāo)
    var i = 1;
    // 第一個(gè)參數(shù)不傳布爾值的情況下,target 默認(rèn)是第一個(gè)參數(shù)
    var target = arguments[0] || {};
    // 如果第一個(gè)參數(shù)是布爾值,第二個(gè)參數(shù)是 target
    if (typeof target == "boolean") {
        deep = target;
        target = arguments[i] || {};
        i++;
    }
    // 如果target不是對(duì)象,我們是無(wú)法進(jìn)行復(fù)制的,所以設(shè)為 {}
    if (typeof target !== "object" && !isFunction(target)) {
        target = {};
    }

    // 循環(huán)遍歷要復(fù)制的對(duì)象們
    for (; i < length; i++) {
        // 獲取當(dāng)前對(duì)象
        options = arguments[i];
        // 要求不能為空 避免 extend(a,,b) 這種情況
        if (options != null) {
            for (name in options) {
                // 目標(biāo)屬性值
                src = target[name];
                // 要復(fù)制的對(duì)象的屬性值
                copy = options[name];

                // 解決循環(huán)引用
                if (target === copy) {
                    continue;
                }

                // 要遞歸的對(duì)象必須是 plainObject 或者數(shù)組
                if (deep && copy && (isPlainObject(copy) ||
                        (copyIsArray = Array.isArray(copy)))) {
                    // 要復(fù)制的對(duì)象屬性值類型需要與目標(biāo)屬性值相同
                    if (copyIsArray) {
                        copyIsArray = false;
                        clone = src && Array.isArray(src) ? src : [];

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

                    target[name] = extend(deep, clone, copy);

                } else if (copy !== undefined) {
                    target[name] = copy;
                }
            }
        }
    }

    return target;
};
思考題

如果覺(jué)得看明白了上面的代碼,想想下面兩個(gè) demo 的結(jié)果:

var a = extend(true, [4, 5, 6, 7, 8, 9], [1, 2, 3]);
console.log(a) // ???
var obj1 = {
    value: {
        3: 1
    }
}

var obj2 = {
    value: [5, 6, 7],

}

var b = extend(true, obj1, obj2) // ???
var c = extend(true, obj2, obj1) // ???
專題系列

JavaScript專題系列目錄地址:https://github.com/mqyqingfeng/Blog。

JavaScript專題系列預(yù)計(jì)寫(xiě)二十篇左右,主要研究日常開(kāi)發(fā)中一些功能點(diǎn)的實(shí)現(xiàn),比如防抖、節(jié)流、去重、類型判斷、拷貝、最值、扁平、柯里、遞歸、亂序、排序等,特點(diǎn)是研(chao)究(xi) underscore 和 jQuery 的實(shí)現(xiàn)方式。

如果有錯(cuò)誤或者不嚴(yán)謹(jǐn)?shù)牡胤剑?qǐng)務(wù)必給予指正,十分感謝。如果喜歡或者有所啟發(fā),歡迎 star,對(duì)作者也是一種鼓勵(lì)。

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/83989.html

相關(guān)文章

  • JavaScript專題系列文章

    摘要:專題系列共計(jì)篇,主要研究日常開(kāi)發(fā)中一些功能點(diǎn)的實(shí)現(xiàn),比如防抖節(jié)流去重類型判斷拷貝最值扁平柯里遞歸亂序排序等,特點(diǎn)是研究專題之函數(shù)組合專題系列第十六篇,講解函數(shù)組合,并且使用柯里化和函數(shù)組合實(shí)現(xiàn)模式需求我們需要寫(xiě)一個(gè)函數(shù),輸入,返回。 JavaScript 專題之從零實(shí)現(xiàn) jQuery 的 extend JavaScritp 專題系列第七篇,講解如何從零實(shí)現(xiàn)一個(gè) jQuery 的 ext...

    Maxiye 評(píng)論0 收藏0
  • JavaScript專題系列20篇正式完結(jié)!

    摘要:寫(xiě)在前面專題系列是我寫(xiě)的第二個(gè)系列,第一個(gè)系列是深入系列。專題系列自月日發(fā)布第一篇文章,到月日發(fā)布最后一篇,感謝各位朋友的收藏點(diǎn)贊,鼓勵(lì)指正。 寫(xiě)在前面 JavaScript 專題系列是我寫(xiě)的第二個(gè)系列,第一個(gè)系列是 JavaScript 深入系列。 JavaScript 專題系列共計(jì) 20 篇,主要研究日常開(kāi)發(fā)中一些功能點(diǎn)的實(shí)現(xiàn),比如防抖、節(jié)流、去重、類型判斷、拷貝、最值、扁平、柯里...

    sixleaves 評(píng)論0 收藏0
  • JavaScript專題之遞歸

    摘要:專題系列第十八篇,講解遞歸和尾遞歸定義程序調(diào)用自身的編程技巧稱為遞歸。然而非尾調(diào)用函數(shù),就會(huì)創(chuàng)建多個(gè)執(zhí)行上下文壓入執(zhí)行上下文棧。所以我們只用把階乘函數(shù)改造成一個(gè)尾遞歸形式,就可以避免創(chuàng)建那么多的執(zhí)行上下文。 JavaScript 專題系列第十八篇,講解遞歸和尾遞歸 定義 程序調(diào)用自身的編程技巧稱為遞歸(recursion)。 階乘 以階乘為例: function factorial(n...

    asoren 評(píng)論0 收藏0
  • 2017-07-15 前端日?qǐng)?bào)

    摘要:前端日?qǐng)?bào)精選從設(shè)計(jì)到源碼用強(qiáng)類型語(yǔ)言增強(qiáng)通過(guò)一個(gè)場(chǎng)景實(shí)例了解前端處理大數(shù)據(jù)的無(wú)限可能專題之從零實(shí)現(xiàn)的表單驗(yàn)證第一部分使用和技巧對(duì)表單進(jìn)行約束驗(yàn)證中文譯即將到來(lái)的正則表達(dá)式新特性掘金個(gè)快速編程技巧個(gè)人文章周刊第期相信控制像一樣使用 2017-07-15 前端日?qǐng)?bào) 精選 Redux從設(shè)計(jì)到源碼用強(qiáng)類型語(yǔ)言GraphQL增強(qiáng)React通過(guò)一個(gè)場(chǎng)景實(shí)例 了解前端處理大數(shù)據(jù)的無(wú)限可能JavaSc...

    Vicky 評(píng)論0 收藏0
  • JavaScript深入系列15篇正式完結(jié)!

    摘要:寫(xiě)在前面深入系列共計(jì)篇已經(jīng)正式完結(jié),這是一個(gè)旨在幫助大家,其實(shí)也是幫助自己捋順底層知識(shí)的系列。深入系列自月日發(fā)布第一篇文章,到月日發(fā)布最后一篇,感謝各位朋友的收藏點(diǎn)贊,鼓勵(lì)指正。 寫(xiě)在前面 JavaScript 深入系列共計(jì) 15 篇已經(jīng)正式完結(jié),這是一個(gè)旨在幫助大家,其實(shí)也是幫助自己捋順 JavaScript 底層知識(shí)的系列。重點(diǎn)講解了如原型、作用域、執(zhí)行上下文、變量對(duì)象、this、...

    fxp 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

最新活動(dòng)
閱讀需要支付1元查看
<