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