成人无码视频,亚洲精品久久久久av无码,午夜精品久久久久久毛片,亚洲 中文字幕 日韩 无码

資訊專欄INFORMATION COLUMN

underscore 系列之如何寫自己的 underscore

Invoker / 1517人閱讀

摘要:因?yàn)樵谖⑿判〕绦蛑校投际?,加上又?qiáng)制使用嚴(yán)格模式,為,掛載就會(huì)發(fā)生錯(cuò)誤,所以就有人又發(fā)了一個(gè),代碼變成了這就是現(xiàn)在的樣子。

前言

在 《JavaScript 專題系列》 中,我們寫了很多的功能函數(shù),比如防抖、節(jié)流、去重、類型判斷、扁平數(shù)組、深淺拷貝、查找數(shù)組元素、通用遍歷、柯里化、函數(shù)組合、函數(shù)記憶、亂序等,可以我們?cè)撊绾谓M織這些函數(shù),形成自己的一個(gè)工具函數(shù)庫(kù)呢?這個(gè)時(shí)候,我們就要借鑒 underscore 是怎么做的了。

自己實(shí)現(xiàn)

如果是我們自己去組織這些函數(shù),我們?cè)撛趺醋瞿??我想我?huì)這樣做:

(function(){
    var root = this;

    var _ = {};

    root._ = _;

    // 在這里添加自己的方法
    _.reverse = function(string){
        return string.split("").reverse().join("");
    }

})()

_.reverse("hello");
=> "olleh"

我們將所有的方法添加到一個(gè)名為 _ 的對(duì)象上,然后將該對(duì)象掛載到全局對(duì)象上。

之所以不直接 window._ = _ 是因?yàn)槲覀儗懙氖且粋€(gè)工具函數(shù)庫(kù),不僅要求可以運(yùn)行在瀏覽器端,還可以運(yùn)行在諸如 Node 等環(huán)境中。

root

然而 underscore 可不會(huì)寫得如此簡(jiǎn)單,我們從 var root = this 開(kāi)始說(shuō)起。

之所以寫這一句,是因?yàn)槲覀円ㄟ^(guò) this 獲得全局對(duì)象,然后將 _ 對(duì)象,掛載上去。

然而在嚴(yán)格模式下,this 返回 undefined,而不是指向 Window,幸運(yùn)的是 underscore 并沒(méi)有采用嚴(yán)格模式,可是即便如此,也不能避免,因?yàn)樵?ES6 中模塊腳本自動(dòng)采用嚴(yán)格模式,不管有沒(méi)有聲明 use strict

如果 this 返回 undefined,代碼就會(huì)報(bào)錯(cuò),所以我們的思路是對(duì)環(huán)境進(jìn)行檢測(cè),然后掛載到正確的對(duì)象上。我們修改一下代碼:

var root = (typeof window == "object" && window.window == window && window) ||
           (typeof global == "object" && global.global == global && global);

在這段代碼中,我們判斷了瀏覽器和 Node 環(huán)境,可是只有這兩個(gè)環(huán)境嗎?那我們來(lái)看看 Web Worker。

Web Worker

Web Worker 屬于 HTML5 中的內(nèi)容,引用《JavaScript權(quán)威指南》中的話就是:

在 Web Worker 標(biāo)準(zhǔn)中,定義了解決客戶端 JavaScript 無(wú)法多線程的問(wèn)題。其中定義的 “worker” 是指執(zhí)行代碼的并行過(guò)程。不過(guò),Web Worker 處在一個(gè)自包含的執(zhí)行環(huán)境中,無(wú)法訪問(wèn) Window 對(duì)象和 Document 對(duì)象,和主線程之間的通信業(yè)只能通過(guò)異步消息傳遞機(jī)制來(lái)實(shí)現(xiàn)。

為了演示 Web Worker 的效果,我寫了一個(gè) demo,查看代碼。

在 Web Worker 中,是無(wú)法訪問(wèn) Window 對(duì)象的,所以 typeof windowtypeof global 的結(jié)果都是 undefined,所以最終 root 的值為 false,將一個(gè)基本類型的值像對(duì)象一樣添加屬性和方法,自然是會(huì)報(bào)錯(cuò)的。

那么我們?cè)撛趺崔k呢?

雖然在 Web Worker 中不能訪問(wèn)到 Window 對(duì)象,但是我們卻能通過(guò) self 訪問(wèn)到 Worker 環(huán)境中的全局對(duì)象。我們只是要找全局變量掛載而已,所以完全可以掛到 self 中嘛。

而且在瀏覽器中,除了 window 屬性,我們也可以通過(guò) self 屬性直接訪問(wèn)到 Winow 對(duì)象。

console.log(window.window === window); // true
console.log(window.self === window); // true

考慮到使用 self 還可以額外支持 Web Worker,我們直接將代碼改成 self:

var root = (typeof self == "object" && self.self == self && self) ||
           (typeof global == "object" && global.global == global && global);
node vm

到了這里,依然沒(méi)完,讓你想不到的是,在 node 的 vm 模塊中,也就是沙盒模塊,runInContext 方法中,是不存在 window,也不存在 global 變量的,查看代碼。

但是我們卻可以通過(guò) this 訪問(wèn)到全局對(duì)象,所以就有人發(fā)起了一個(gè) PR,代碼改成了:

var root = (typeof self == "object" && self.self == self && self) ||
           (typeof global == "object" && global.global == global && global) ||
           this;
微信小程序

到了這里,還是沒(méi)完,輪到微信小程序登場(chǎng)了。

因?yàn)樵谖⑿判〕绦蛑?,window 和 global 都是 undefined,加上又強(qiáng)制使用嚴(yán)格模式,this 為 undefined,掛載就會(huì)發(fā)生錯(cuò)誤,所以就有人又發(fā)了一個(gè) PR,代碼變成了:

var root = (typeof self == "object" && self.self == self && self) ||
           (typeof global == "object" && global.global == global && global) ||
           this ||
           {};

這就是現(xiàn)在 v1.8.3 的樣子。

雖然作者可以直接講解最終的代碼,但是作者更希望帶著大家看看這看似普通的代碼是如何一步步演變成這樣的,也希望告訴大家,代碼的健壯性,并非一蹴而就,而是匯集了很多人的經(jīng)驗(yàn),考慮到了很多我們意想不到的地方,這也是開(kāi)源項(xiàng)目的好處吧。

函數(shù)對(duì)象

現(xiàn)在我們講第二句 var _ = {};

如果僅僅設(shè)置 _ 為一個(gè)空對(duì)象,我們調(diào)用方法的時(shí)候,只能使用 _.reverse("hello") 的方式,實(shí)際上,underscore 也支持類似面向?qū)ο蟮姆绞秸{(diào)用,即:

_("hello").reverse(); // "olleh"

再舉個(gè)例子比較下兩種調(diào)用方式:

// 函數(shù)式風(fēng)格
_.each([1, 2, 3], function(item){
    console.log(item)
});

// 面向?qū)ο箫L(fēng)格
_([1, 2, 3]).each(function(item){
    console.log(item)
});

可是該如何實(shí)現(xiàn)呢?

既然以 _([1, 2, 3]) 的形式可以執(zhí)行,就表明 _ 不是一個(gè)字面量對(duì)象,而是一個(gè)函數(shù)!

幸運(yùn)的是,在 JavaScript 中,函數(shù)也是一種對(duì)象,我們舉個(gè)例子:

var _ = function() {};
_.value = 1;
_.log = function() { return this.value + 1 };

console.log(_.value); // 1
console.log(_.log()); // 2

我們完全可以將自定義的函數(shù)定義在 _ 函數(shù)上!

目前的寫法為:

var root = (typeof self == "object" && self.self == self && self) ||
           (typeof global == "object" && global.global == global && global) ||
           this ||
           {};

var _ = function() {}

root._ = _;

如何做到 _([1, 2, 3]).each(...)呢?即 函數(shù)返回一個(gè)對(duì)象,這個(gè)對(duì)象,如何調(diào)用掛在 函數(shù)上的方法呢?

我們看看 underscore 是如何實(shí)現(xiàn)的:

var _ = function(obj) {
    if (!(this instanceof _)) return new _(obj);
    this._wrapped = obj;
};

_([1, 2, 3]);

我們分析下 _([1, 2, 3]) 的執(zhí)行過(guò)程:

執(zhí)行 this instanceof _,this 指向 window ,window instanceof _ 為 false,!操作符取反,所以執(zhí)行 new _(obj)。

new _(obj) 中,this 指向?qū)嵗龑?duì)象,this instanceof _ 為 true,取反后,代碼接著執(zhí)行

執(zhí)行 this._wrapped = obj, 函數(shù)執(zhí)行結(jié)束

總結(jié),_([1, 2, 3]) 返回一個(gè)對(duì)象,為 {_wrapped: [1, 2, 3]},該對(duì)象的原型指向 _.prototype

示意圖如下:

然后問(wèn)題來(lái)了,我們是將方法掛載到 函數(shù)對(duì)象上,并沒(méi)有掛到函數(shù)的原型上吶,所以返回了的實(shí)例,其實(shí)是無(wú)法調(diào)用 函數(shù)對(duì)象上的方法的!

我們寫個(gè)例子:

(function(){
    var root = (typeof self == "object" && self.self == self && self) ||
               (typeof global == "object" && global.global == global && global) ||
               this ||
               {};

    var _ = function(obj) {
        if (!(this instanceof _)) return new _(obj);
        this._wrapped = obj;
    }

    root._ = _;

    _.log = function(){
        console.log(1)
    }

})()

_().log(); // _(...).log is not a function

確實(shí)有這個(gè)問(wèn)題,所以我們還需要一個(gè)方法將 _ 上的方法復(fù)制到 _.prototype 上,這個(gè)方法就是 _.mixin。

_.functions

為了將 上的方法復(fù)制到原型上,首先我們要獲得 上的方法,所以我們先寫個(gè) _.functions 方法。

_.functions = function(obj) {
    var names = [];
    for (var key in obj) {
        if (_.isFunction(obj[key])) names.push(key);
    }
    return names.sort();
};

isFunction 函數(shù)可以參考 《JavaScript專題之類型判斷(下)》

mixin

現(xiàn)在我們可以寫 mixin 方法了。

var ArrayProto = Array.prototype;
var push = ArrayProto.push;

_.mixin = function(obj) {
    _.each(_.functions(obj), function(name) {
        var func = _[name] = obj[name];
        _.prototype[name] = function() {
            var args = [this._wrapped];
            push.apply(args, arguments);
            return func.apply(_, args);
        };
    });
    return _;
};

_.mixin(_);

each 方法可以參考 《JavaScript專題jQuery通用遍歷方法each的實(shí)現(xiàn)》

值得注意的是:因?yàn)?_[name] = obj[name] 的緣故,我們可以給 underscore 拓展自定義的方法:

_.mixin({
  addOne: function(num) {
    return num + 1;
  }
});

_(2).addOne(); // 3

至此,我們算是實(shí)現(xiàn)了同時(shí)支持面向?qū)ο箫L(fēng)格和函數(shù)風(fēng)格。

導(dǎo)出

終于到了講最后一步 root._ = _,我們直接看源碼:

if (typeof exports != "undefined" && !exports.nodeType) {
    if (typeof module != "undefined" && !module.nodeType && module.exports) {
        exports = module.exports = _;
    }
    exports._ = _;
} else {
    root._ = _;
}

為了支持模塊化,我們需要將 _ 在合適的環(huán)境中作為模塊導(dǎo)出,但是 nodejs 模塊的 API 曾經(jīng)發(fā)生過(guò)改變,比如在早期版本中:

// add.js
exports.addOne = function(num) {
  return num + 1
}

// index.js
var add = require("./add");
add.addOne(2);

在新版本中:

// add.js
module.exports = function(1){
    return num + 1
}

// index.js
var addOne = require("./add.js")
addOne(2)

所以我們根據(jù) exports 和 module 是否存在來(lái)選擇不同的導(dǎo)出方式,那為什么在新版本中,我們還要使用 exports = module.exports = _ 呢?

這是因?yàn)樵?nodejs 中,exports 是 module.exports 的一個(gè)引用,當(dāng)你使用了 module.exports = function(){},實(shí)際上覆蓋了 module.exports,但是 exports 并未發(fā)生改變,為了避免后面再修改 exports 而導(dǎo)致不能正確輸出,就寫成這樣,將兩者保持統(tǒng)一。

寫個(gè) demo 吧:

// exports 是 module.exports 的一個(gè)引用
module.exports.num = "1"

console.log(exports.num) // 1

exports.num = "2"

console.log(module.exports.num) // 2
// addOne.js
module.exports = function(num){
    return num + 1
}

exports.num = "3"

// result.js 中引入 addOne.js
var addOne = require("./addOne.js");

console.log(addOne(1)) // 2
console.log(addOne.num) // undefined
// addOne.js
exports = module.exports = function(num){
    return num + 1
}

exports.num = "3"

// result.js 中引入 addOne.js
var addOne = require("./addOne.js");

console.log(addOne(1)) // 2
console.log(addOne.num) // 3

最后為什么要進(jìn)行一個(gè) exports.nodeType 判斷呢?這是因?yàn)槿绻阍?HTML 頁(yè)面中加入一個(gè) id 為 exports 的元素,比如

就會(huì)生成一個(gè) window.exports 全局變量,你可以直接在瀏覽器命令行中打印該變量。

此時(shí)在瀏覽器中,typeof exports != "undefined" 的判斷就會(huì)生效,然后 exports._ = _,然而在瀏覽器中,我們需要將 _ 掛載到全局變量上吶,所以在這里,我們還需要進(jìn)行一個(gè)是否是 DOM 節(jié)點(diǎn)的判斷。

源碼

最終的代碼如下,有了這個(gè)基本結(jié)構(gòu),你可以自由添加你需要使用到的函數(shù)了:

(function() {

    var root = (typeof self == "object" && self.self == self && self) ||
        (typeof global == "object" && global.global == global && global) ||
        this || {};

    var ArrayProto = Array.prototype;

    var push = ArrayProto.push;

    var _ = function(obj) {
        if (obj instanceof _) return obj;
        if (!(this instanceof _)) return new _(obj);
        this._wrapped = obj;
    };

    if (typeof exports != "undefined" && !exports.nodeType) {
        if (typeof module != "undefined" && !module.nodeType && module.exports) {
            exports = module.exports = _;
        }
        exports._ = _;
    } else {
        root._ = _;
    }

    _.VERSION = "0.1";

    var MAX_ARRAY_INDEX = Math.pow(2, 53) - 1;

    var isArrayLike = function(collection) {
        var length = collection.length;
        return typeof length == "number" && length >= 0 && length <= MAX_ARRAY_INDEX;
    };

    _.each = function(obj, callback) {
        var length, i = 0;

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

        return obj;
    }

    _.isFunction = function(obj) {
        return typeof obj == "function" || false;
    };

    _.functions = function(obj) {
        var names = [];
        for (var key in obj) {
            if (_.isFunction(obj[key])) names.push(key);
        }
        return names.sort();
    };

    /**
     * 在 _.mixin(_) 前添加自己定義的方法
     */
    _.reverse = function(string){
        return string.split("").reverse().join("");
    }

    _.mixin = function(obj) {
        _.each(_.functions(obj), function(name) {
            var func = _[name] = obj[name];
            _.prototype[name] = function() {
                var args = [this._wrapped];

                push.apply(args, arguments);

                return func.apply(_, args);
            };
        });
        return _;
    };

    _.mixin(_);

})()
相關(guān)鏈接

《JavaScript專題之類型判斷(下)》

《JavaScript專題jQuery通用遍歷方法each的實(shí)現(xiàn)》

underscore 系列

underscore 系列目錄地址:https://github.com/mqyqingfeng/Blog。

underscore 系列預(yù)計(jì)寫八篇左右,重點(diǎn)介紹 underscore 中的代碼架構(gòu)、鏈?zhǔn)秸{(diào)用、內(nèi)部函數(shù)、模板引擎等內(nèi)容,旨在幫助大家閱讀源碼,以及寫出自己的 undercore。

如果有錯(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://m.hztianpu.com/yun/89802.html

相關(guān)文章

  • underscore 源碼該如何閱讀?

    摘要:所以它與其他系列的文章并不沖突,完全可以在閱讀完這個(gè)系列后,再跟著其他系列的文章接著學(xué)習(xí)。如何閱讀我在寫系列的時(shí)候,被問(wèn)的最多的問(wèn)題就是該怎么閱讀源碼我想簡(jiǎn)單聊一下自己的思路。感謝大家的閱讀和支持,我是冴羽,下個(gè)系列再見(jiàn)啦 前言 別名:《underscore 系列 8 篇正式完結(jié)!》 介紹 underscore 系列是我寫的第三個(gè)系列,前兩個(gè)系列分別是 JavaScript 深入系列、...

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

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

    sixleaves 評(píng)論0 收藏0
  • underscore 系列防沖突與 Utility Functions

    摘要:你可以輕松為你的函數(shù)庫(kù)添加防沖突功能。系列系列目錄地址。如果有錯(cuò)誤或者不嚴(yán)謹(jǐn)?shù)牡胤?,?qǐng)務(wù)必給予指正,十分感謝。 防沖突 underscore 使用 _ 作為函數(shù)的掛載對(duì)象,如果頁(yè)面中已經(jīng)存在了 _ 對(duì)象,underscore 就會(huì)覆蓋該對(duì)象,舉個(gè)例子: var _ = {value: 1 } // 引入 underscore 后 console.log(_.value); // un...

    qiangdada 評(píng)論0 收藏0
  • underscore 系列鏈?zhǔn)秸{(diào)用

    摘要:我們都知道可以鏈?zhǔn)秸{(diào)用,比如我們寫個(gè)簡(jiǎn)單的模擬鏈?zhǔn)秸{(diào)用之所以能實(shí)現(xiàn)鏈?zhǔn)秸{(diào)用,關(guān)鍵就在于通過(guò),返回調(diào)用對(duì)象。系列預(yù)計(jì)寫八篇左右,重點(diǎn)介紹中的代碼架構(gòu)鏈?zhǔn)秸{(diào)用內(nèi)部函數(shù)模板引擎等內(nèi)容,旨在幫助大家閱讀源碼,以及寫出自己的。 前言 本文接著上篇《underscore 系列之如何寫自己的 underscore》,閱讀本篇前,希望你已經(jīng)閱讀了上一篇。 jQuery 我們都知道 jQuery 可以鏈...

    zhangrxiang 評(píng)論0 收藏0
  • JavaScript專題系列文章

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

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

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

0條評(píng)論

閱讀需要支付1元查看
<