-
Notifications
You must be signed in to change notification settings - Fork 4.7k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
underscore 系列之如何写自己的 underscore #56
Comments
虽然我看完了 underscore 源码,但是并没有这么深入,真是惭愧 |
我一直认为underscore作为一个工具库,支持面向对象风格很奇怪。不知道博主怎么看这件事。 |
@dongliang1993 可以看这个系列的文章查漏补缺,相互交流呀~ |
@jiangshanmeta 我倒没有奇怪的感觉……支持面向对象风格应该是方便实现链式调用吧 |
博主,你好,在这段代码中 var _ = function(obj) {
if (obj instanceof _) return obj;
if (!(this instanceof _)) return new _(obj);
this._wrapped = obj;
}; 你说 执行 this instanceof _,this 指向 window ,可是我打印this 并不等于window啊 而是 _ 的实例,这个地方博主能不能解释到底是怎么回事? |
var _ = function(obj) {
console.log(this)
if (!(this instanceof _)) return new _(obj);
this._wrapped = obj;
};
_([1, 2, 3]) 查看打印结果为: 第一次 this 指向 window |
谢谢博主解疑! |
希望博主写完underscore之后写es6 再react循序渐进 加油 |
@zhangruinian 一路走来,深感其中不易,非常感谢鼓励~ |
@zhangruinian 一定要感谢下大家对我的支持~ |
@mqyqingfeng 其实你可以探索一下 |
@liuliangsir 这是什么黑科技?能举个例子不? |
看的真的很舒服,思维都开阔了 |
@liuxinqiong 好久不见<( ̄︶ ̄)> |
@mqyqingfeng 一直在默默学习中,等哪天知识吸收了,且有自己的观点了,我要和博主华山论剑 |
@liuxinqiong 哈哈,好啊,期待这一天早日来临,不过我也不是止步不前哦~ 在学习的路上与你共勉 []~( ̄▽ ̄)~* |
@MuYunyun 感谢分享哈~ 我补充下第二点,exports 是 module.exports 的引用,但是如果我要导出一个函数,其实我只要 module.exports = func 就可以了,为什么还要 exports = module.exports 呢?我当时困惑的是这一点, 这个在官方文档其实也有说明啦:
|
楼主,麻烦问下,这里为什么要这么写呢 if (callback.call(obj[i], obj[i], i) === false) {
break;
} |
@dowinweb 这个可以参考 JavaScript专题之jQuery通用遍历方法each的实现,主要原因是 ES5 的 forEach 函数,在遍历中是不会停止遍历,也就是说,有的时候,我已经找到合适的值了,不想在接着往下遍历其他的数据了,但是如果用 forEach ,依然会接着遍历,所以如果使用 for 循环,我可以判断函数返回的值是否是 false,如果是 false ,我就 break,就不会接着遍历了~ |
厉害了,看懂基本结构再看源码逻辑清晰很多。 |
@jiangshanmeta 其实 jQuery 全程都在 OOP 的( |
@mqyqingfeng 博主~你说的 forEach 循环无法跳出的问题我看有的源码里直接抛出一个空的 Error 也可以达到目的,当然使用 for 循环更好,仅提供一个意见 |
@mqyqingfeng 博主可以参考你的文章写博客么? |
exports 是 module.exports 的一个引用,当你使用了 module.exports = function(){},实际上覆盖了 module.exports,但是 exports 并未发生改变。 |
var _ = function(obj) { 为什么不利用这种方式来创建 _ ,这样还能省一个mix的逻辑。 |
写的真棒,看完之后收获颇丰 |
感谢感谢,十分感谢!!! |
没明白小程序里root = {}如和实现挂载到全局的 |
就光环境的判断 学到很多 思维也开阔不少 |
楼主,
|
为什么是 (typeof self == 'object' && self.self == self && self) ,而不是 (typeof self == 'object' && self && self.self == self) ,前者的话,万一self定义成了null呢? |
这个self不是定义的而是window自带的属性,window.self==self,self = window
…------------------ 原始邮件 ------------------
发件人: "lazybonee"<[email protected]>;
发送时间: 2019年11月14日(星期四) 下午5:35
收件人: "mqyqingfeng/Blog"<[email protected]>;
抄送: "random"<[email protected]>; "Comment"<[email protected]>;
主题: Re: [mqyqingfeng/Blog] underscore 系列之如何写自己的 underscore (#56)
为什么是 (typeof self == 'object' && self.self == self && self) ,而不是 (typeof self == 'object' && self && self.self == self) ,前者的话,万一self定义成了null呢?
—
You are receiving this because you commented.
Reply to this email directly, view it on GitHub, or unsubscribe.
|
@rujptw 我知道,但是不应该做容错处理吗? 如果我就是在引入underscore之前,将self定义成null了呢? |
从编码的规范角度,不应该是写成self && self.self更可取吗,源码写成self.self && self是出自什么考虑呢? 如果判断了self.self为真,那么后面的&&self不是显得多余吗? |
@lazybonee 如果self.self === self为真,后面的&&self就是将self对象赋值给root。 |
@btea 啊对哦...一时疏忽了..感谢指点 ~ |
有个疑问,不用做单例检测吗? |
好问题,有没有大佬能够解答一下 |
直接把方法定义到原型上有什么问题吗,为什么要多一步 mix 呢,大佬们指导下 |
类似于 |
这类文章真的很好啊,持续学习,有能力了也会把所思所想分享出来,一来督促自己,二来造福更多学习者。共勉 |
我开始也有这个困惑,就是为什么不能直接prototype[name]等于func,后来发现同一个func既需要支持函数直接调用,又需要支持从原型上调用,但两种调用方式,函数所能拿到的参数是不一样的。函数调用时候是可以直接拿到参数的,但是原型式调用不成,此时参数相当于已经在实例中的wrapped中了,因此原型上的方法需要在func的基础上做处理,把wrapped作为参数传递过去 |
前言
在 《JavaScript 专题系列》 中,我们写了很多的功能函数,比如防抖、节流、去重、类型判断、扁平数组、深浅拷贝、查找数组元素、通用遍历、柯里化、函数组合、函数记忆、乱序等,可以我们该如何组织这些函数,形成自己的一个工具函数库呢?这个时候,我们就要借鉴 underscore 是怎么做的了。
自己实现
如果是我们自己去组织这些函数,我们该怎么做呢?我想我会这样做:
我们将所有的方法添加到一个名为
_
的对象上,然后将该对象挂载到全局对象上。之所以不直接
window._ = _
是因为我们写的是一个工具函数库,不仅要求可以运行在浏览器端,还可以运行在诸如 Node 等环境中。root
然而 underscore 可不会写得如此简单,我们从
var root = this
开始说起。之所以写这一句,是因为我们要通过 this 获得全局对象,然后将
_
对象,挂载上去。然而在严格模式下,this 返回 undefined,而不是指向 Window,幸运的是 underscore 并没有采用严格模式,可是即便如此,也不能避免,因为在 ES6 中模块脚本自动采用严格模式,不管有没有声明
use strict
。如果 this 返回 undefined,代码就会报错,所以我们的思路是对环境进行检测,然后挂载到正确的对象上。我们修改一下代码:
在这段代码中,我们判断了浏览器和 Node 环境,可是只有这两个环境吗?那我们来看看 Web Worker。
Web Worker
Web Worker 属于 HTML5 中的内容,引用《JavaScript权威指南》中的话就是:
为了演示 Web Worker 的效果,我写了一个 demo,查看代码。
在 Web Worker 中,是无法访问 Window 对象的,所以
typeof window
和typeof global
的结果都是undefined
,所以最终 root 的值为 false,将一个基本类型的值像对象一样添加属性和方法,自然是会报错的。那么我们该怎么办呢?
虽然在 Web Worker 中不能访问到 Window 对象,但是我们却能通过 self 访问到 Worker 环境中的全局对象。我们只是要找全局变量挂载而已,所以完全可以挂到 self 中嘛。
而且在浏览器中,除了 window 属性,我们也可以通过 self 属性直接访问到 Winow 对象。
考虑到使用 self 还可以额外支持 Web Worker,我们直接将代码改成 self:
node vm
到了这里,依然没完,让你想不到的是,在 node 的 vm 模块中,也就是沙盒模块,runInContext 方法中,是不存在 window,也不存在 global 变量的,查看代码。
但是我们却可以通过 this 访问到全局对象,所以就有人发起了一个 PR,代码改成了:
微信小程序
到了这里,还是没完,轮到微信小程序登场了。
因为在微信小程序中,window 和 global 都是 undefined,加上又强制使用严格模式,this 为 undefined,挂载就会发生错误,所以就有人又发了一个 PR,代码变成了:
这就是现在 v1.8.3 的样子。
虽然作者可以直接讲解最终的代码,但是作者更希望带着大家看看这看似普通的代码是如何一步步演变成这样的,也希望告诉大家,代码的健壮性,并非一蹴而就,而是汇集了很多人的经验,考虑到了很多我们意想不到的地方,这也是开源项目的好处吧。
函数对象
现在我们讲第二句
var _ = {};
。如果仅仅设置 _ 为一个空对象,我们调用方法的时候,只能使用
_.reverse('hello')
的方式,实际上,underscore 也支持类似面向对象的方式调用,即:再举个例子比较下两种调用方式:
可是该如何实现呢?
既然以
_([1, 2, 3])
的形式可以执行,就表明_
不是一个字面量对象,而是一个函数!幸运的是,在 JavaScript 中,函数也是一种对象,我们举个例子:
我们完全可以将自定义的函数定义在
_
函数上!目前的写法为:
如何做到
_([1, 2, 3]).each(...)
呢?即 _ 函数返回一个对象,这个对象,如何调用挂在 _ 函数上的方法呢?我们看看 underscore 是如何实现的:
我们分析下
_([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示意图如下:
然后问题来了,我们是将方法挂载到 _ 函数对象上,并没有挂到函数的原型上呐,所以返回了的实例,其实是无法调用 _ 函数对象上的方法的!
我们写个例子:
确实有这个问题,所以我们还需要一个方法将 _ 上的方法复制到
_.prototype
上,这个方法就是_.mixin
。_.functions
为了将 _ 上的方法复制到原型上,首先我们要获得 _ 上的方法,所以我们先写个
_.functions
方法。isFunction 函数可以参考 《JavaScript专题之类型判断(下)》
mixin
现在我们可以写 mixin 方法了。
each 方法可以参考 《JavaScript专题jQuery通用遍历方法each的实现》
值得注意的是:因为
_[name] = obj[name]
的缘故,我们可以给 underscore 拓展自定义的方法:至此,我们算是实现了同时支持面向对象风格和函数风格。
导出
终于到了讲最后一步
root._ = _
,我们直接看源码:为了支持模块化,我们需要将 _ 在合适的环境中作为模块导出,但是 nodejs 模块的 API 曾经发生过改变,比如在早期版本中:
在新版本中:
所以我们根据 exports 和 module 是否存在来选择不同的导出方式,那为什么在新版本中,我们还要使用
exports = module.exports = _
呢?这是因为在 nodejs 中,exports 是 module.exports 的一个引用,当你使用了 module.exports = function(){},实际上覆盖了 module.exports,但是 exports 并未发生改变,为了避免后面再修改 exports 而导致不能正确输出,就写成这样,将两者保持统一。
写个 demo 吧:
最后为什么要进行一个 exports.nodeType 判断呢?这是因为如果你在 HTML 页面中加入一个 id 为 exports 的元素,比如
就会生成一个 window.exports 全局变量,你可以直接在浏览器命令行中打印该变量。
此时在浏览器中,
typeof exports != 'undefined'
的判断就会生效,然后exports._ = _
,然而在浏览器中,我们需要将 _ 挂载到全局变量上呐,所以在这里,我们还需要进行一个是否是 DOM 节点的判断。源码
最终的代码如下,有了这个基本结构,你可以自由添加你需要使用到的函数了:
相关链接
《JavaScript专题之类型判断(下)》
《JavaScript专题jQuery通用遍历方法each的实现》
underscore 系列
underscore 系列目录地址:https://github.com/mqyqingfeng/Blog。
underscore 系列预计写八篇左右,重点介绍 underscore 中的代码架构、链式调用、内部函数、模板引擎等内容,旨在帮助大家阅读源码,以及写出自己的 undercore。
如果有错误或者不严谨的地方,请务必给予指正,十分感谢。如果喜欢或者有所启发,欢迎star,对作者也是一种鼓励。
The text was updated successfully, but these errors were encountered: