摘要:因為在微信小程序中,和都是,加上又強(qiáng)制使用嚴(yán)格模式,為,掛載就會發(fā)生錯誤,所以就有人又發(fā)了一個,代碼變成了這就是現(xiàn)在的樣子。
前言
在 《JavaScript 專題系列》 中,我們寫了很多的功能函數(shù),比如防抖、節(jié)流、去重、類型判斷、扁平數(shù)組、深淺拷貝、查找數(shù)組元素、通用遍歷、柯里化、函數(shù)組合、函數(shù)記憶、亂序等,可以我們該如何組織這些函數(shù),形成自己的一個工具函數(shù)庫呢?這個時候,我們就要借鑒 underscore 是怎么做的了。
自己實現(xiàn)如果是我們自己去組織這些函數(shù),我們該怎么做呢?我想我會這樣做:
(function(){ var root = this; var _ = {}; root._ = _; // 在這里添加自己的方法 _.reverse = function(string){ return string.split("").reverse().join(""); } })() _.reverse("hello"); => "olleh"
我們將所有的方法添加到一個名為 _ 的對象上,然后將該對象掛載到全局對象上。
之所以不直接 window._ = _ 是因為我們寫的是一個工具函數(shù)庫,不僅要求可以運(yùn)行在瀏覽器端,還可以運(yùn)行在諸如 Node 等環(huán)境中。
root然而 underscore 可不會寫得如此簡單,我們從 var root = this 開始說起。
之所以寫這一句,是因為我們要通過 this 獲得全局對象,然后將 _ 對象,掛載上去。
然而在嚴(yán)格模式下,this 返回 undefined,而不是指向 Window,幸運(yùn)的是 underscore 并沒有采用嚴(yán)格模式,可是即便如此,也不能避免,因為在 ES6 中模塊腳本自動采用嚴(yán)格模式,不管有沒有聲明 use strict。
如果 this 返回 undefined,代碼就會報錯,所以我們的思路是對環(huán)境進(jìn)行檢測,然后掛載到正確的對象上。我們修改一下代碼:
var root = (typeof window == "object" && window.window == window && window) || (typeof global == "object" && global.global == global && global);
在這段代碼中,我們判斷了瀏覽器和 Node 環(huán)境,可是只有這兩個環(huán)境嗎?那我們來看看 Web Worker。
Web WorkerWeb Worker 屬于 HTML5 中的內(nèi)容,引用《JavaScript權(quán)威指南》中的話就是:
在 Web Worker 標(biāo)準(zhǔn)中,定義了解決客戶端 JavaScript 無法多線程的問題。其中定義的 “worker” 是指執(zhí)行代碼的并行過程。不過,Web Worker 處在一個自包含的執(zhí)行環(huán)境中,無法訪問 Window 對象和 Document 對象,和主線程之間的通信業(yè)只能通過異步消息傳遞機(jī)制來實現(xiàn)。
為了演示 Web Worker 的效果,我寫了一個 demo,查看代碼。
在 Web Worker 中,是無法訪問 Window 對象的,所以 typeof window 和 typeof global 的結(jié)果都是 undefined,所以最終 root 的值為 false,將一個基本類型的值像對象一樣添加屬性和方法,自然是會報錯的。
那么我們該怎么辦呢?
雖然在 Web Worker 中不能訪問到 Window 對象,但是我們卻能通過 self 訪問到 Worker 環(huán)境中的全局對象。我們只是要找全局變量掛載而已,所以完全可以掛到 self 中嘛。
而且在瀏覽器中,除了 window 屬性,我們也可以通過 self 屬性直接訪問到 Winow 對象。
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
到了這里,依然沒完,讓你想不到的是,在 node 的 vm 模塊中,也就是沙盒模塊,runInContext 方法中,是不存在 window,也不存在 global 變量的,查看代碼。
但是我們卻可以通過 this 訪問到全局對象,所以就有人發(fā)起了一個 PR,代碼改成了:
var root = (typeof self == "object" && self.self == self && self) || (typeof global == "object" && global.global == global && global) || this;微信小程序
到了這里,還是沒完,輪到微信小程序登場了。
因為在微信小程序中,window 和 global 都是 undefined,加上又強(qiáng)制使用嚴(yán)格模式,this 為 undefined,掛載就會發(fā)生錯誤,所以就有人又發(fā)了一個 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)驗,考慮到了很多我們意想不到的地方,這也是開源項目的好處吧。
函數(shù)對象現(xiàn)在我們講第二句 var _ = {};。
如果僅僅設(shè)置 _ 為一個空對象,我們調(diào)用方法的時候,只能使用 _.reverse("hello") 的方式,實際上,underscore 也支持類似面向?qū)ο蟮姆绞秸{(diào)用,即:
_("hello").reverse(); // "olleh"
再舉個例子比較下兩種調(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) });
可是該如何實現(xiàn)呢?
既然以 _([1, 2, 3]) 的形式可以執(zhí)行,就表明 _ 不是一個字面量對象,而是一個函數(shù)!
幸運(yùn)的是,在 JavaScript 中,函數(shù)也是一種對象,我們舉個例子:
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ù)返回一個對象,這個對象,如何調(diào)用掛在 函數(shù)上的方法呢?
我們看看 underscore 是如何實現(xiàn)的:
var _ = function(obj) { if (!(this instanceof _)) return new _(obj); this._wrapped = obj; }; _([1, 2, 3]);
我們分析下 _([1, 2, 3]) 的執(zhí)行過程:
執(zhí)行 this instanceof _,this 指向 window ,window instanceof _ 為 false,!操作符取反,所以執(zhí)行 new _(obj)。
new _(obj) 中,this 指向?qū)嵗龑ο螅?b>this instanceof _ 為 true,取反后,代碼接著執(zhí)行
執(zhí)行 this._wrapped = obj, 函數(shù)執(zhí)行結(jié)束
總結(jié),_([1, 2, 3]) 返回一個對象,為 {_wrapped: [1, 2, 3]},該對象的原型指向 _.prototype
示意圖如下:
然后問題來了,我們是將方法掛載到 函數(shù)對象上,并沒有掛到函數(shù)的原型上吶,所以返回了的實例,其實是無法調(diào)用 函數(shù)對象上的方法的!
我們寫個例子:
(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
確實有這個問題,所以我們還需要一個方法將 _ 上的方法復(fù)制到 _.prototype 上,這個方法就是 _.mixin。
_.functions為了將 上的方法復(fù)制到原型上,首先我們要獲得 上的方法,所以我們先寫個 _.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的實現(xiàn)》
值得注意的是:因為 _[name] = obj[name] 的緣故,我們可以給 underscore 拓展自定義的方法:
_.mixin({ addOne: function(num) { return num + 1; } }); _(2).addOne(); // 3
至此,我們算是實現(xiàn)了同時支持面向?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ā)生過改變,比如在早期版本中:
// 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 是否存在來選擇不同的導(dǎo)出方式,那為什么在新版本中,我們還要使用 exports = module.exports = _ 呢?
這是因為在 nodejs 中,exports 是 module.exports 的一個引用,當(dāng)你使用了 module.exports = function(){},實際上覆蓋了 module.exports,但是 exports 并未發(fā)生改變,為了避免后面再修改 exports 而導(dǎo)致不能正確輸出,就寫成這樣,將兩者保持統(tǒng)一。
寫個 demo 吧:
// exports 是 module.exports 的一個引用 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)行一個 exports.nodeType 判斷呢?這是因為如果你在 HTML 頁面中加入一個 id 為 exports 的元素,比如
就會生成一個 window.exports 全局變量,你可以直接在瀏覽器命令行中打印該變量。
此時在瀏覽器中,typeof exports != "undefined" 的判斷就會生效,然后 exports._ = _,然而在瀏覽器中,我們需要將 _ 掛載到全局變量上吶,所以在這里,我們還需要進(jìn)行一個是否是 DOM 節(jié)點(diǎn)的判斷。
源碼最終的代碼如下,有了這個基本結(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的實現(xiàn)》
underscore 系列underscore 系列目錄地址:https://github.com/mqyqingfeng/Blog。
underscore 系列預(yù)計寫八篇左右,重點(diǎn)介紹 underscore 中的代碼架構(gòu)、鏈?zhǔn)秸{(diào)用、內(nèi)部函數(shù)、模板引擎等內(nèi)容,旨在幫助大家閱讀源碼,以及寫出自己的 undercore。
如果有錯誤或者不嚴(yán)謹(jǐn)?shù)牡胤?,請?wù)必給予指正,十分感謝。如果喜歡或者有所啟發(fā),歡迎star,對作者也是一種鼓勵。
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/89802.html
摘要:所以它與其他系列的文章并不沖突,完全可以在閱讀完這個系列后,再跟著其他系列的文章接著學(xué)習(xí)。如何閱讀我在寫系列的時候,被問的最多的問題就是該怎么閱讀源碼我想簡單聊一下自己的思路。感謝大家的閱讀和支持,我是冴羽,下個系列再見啦 前言 別名:《underscore 系列 8 篇正式完結(jié)!》 介紹 underscore 系列是我寫的第三個系列,前兩個系列分別是 JavaScript 深入系列、...
摘要:寫在前面專題系列是我寫的第二個系列,第一個系列是深入系列。專題系列自月日發(fā)布第一篇文章,到月日發(fā)布最后一篇,感謝各位朋友的收藏點(diǎn)贊,鼓勵指正。 寫在前面 JavaScript 專題系列是我寫的第二個系列,第一個系列是 JavaScript 深入系列。 JavaScript 專題系列共計 20 篇,主要研究日常開發(fā)中一些功能點(diǎn)的實現(xiàn),比如防抖、節(jié)流、去重、類型判斷、拷貝、最值、扁平、柯里...
摘要:你可以輕松為你的函數(shù)庫添加防沖突功能。系列系列目錄地址。如果有錯誤或者不嚴(yán)謹(jǐn)?shù)牡胤剑垊?wù)必給予指正,十分感謝。 防沖突 underscore 使用 _ 作為函數(shù)的掛載對象,如果頁面中已經(jīng)存在了 _ 對象,underscore 就會覆蓋該對象,舉個例子: var _ = {value: 1 } // 引入 underscore 后 console.log(_.value); // un...
摘要:我們都知道可以鏈?zhǔn)秸{(diào)用,比如我們寫個簡單的模擬鏈?zhǔn)秸{(diào)用之所以能實現(xiàn)鏈?zhǔn)秸{(diào)用,關(guān)鍵就在于通過,返回調(diào)用對象。系列預(yù)計寫八篇左右,重點(diǎn)介紹中的代碼架構(gòu)鏈?zhǔn)秸{(diào)用內(nèi)部函數(shù)模板引擎等內(nèi)容,旨在幫助大家閱讀源碼,以及寫出自己的。 前言 本文接著上篇《underscore 系列之如何寫自己的 underscore》,閱讀本篇前,希望你已經(jīng)閱讀了上一篇。 jQuery 我們都知道 jQuery 可以鏈...
摘要:專題系列共計篇,主要研究日常開發(fā)中一些功能點(diǎn)的實現(xiàn),比如防抖節(jié)流去重類型判斷拷貝最值扁平柯里遞歸亂序排序等,特點(diǎn)是研究專題之函數(shù)組合專題系列第十六篇,講解函數(shù)組合,并且使用柯里化和函數(shù)組合實現(xiàn)模式需求我們需要寫一個函數(shù),輸入,返回。 JavaScript 專題之從零實現(xiàn) jQuery 的 extend JavaScritp 專題系列第七篇,講解如何從零實現(xiàn)一個 jQuery 的 ext...
閱讀 1763·2023-04-25 16:28
閱讀 697·2021-11-23 09:51
閱讀 1478·2019-08-30 15:54
閱讀 1164·2019-08-30 15:53
閱讀 2838·2019-08-30 15:53
閱讀 3431·2019-08-30 15:43
閱讀 3269·2019-08-30 11:18
閱讀 3292·2019-08-26 10:25