函数式调用
_.reverse(‘text’)
面向对象式调用
_(‘text’).reverse()
可以看到二者的区别是函数式调用是直接使用underscore
调用方法,而面向对象调用的方法是先创建underscore
对象,然后调用方法(不传参)
从上面可以看出,underscore 对象不是一个简单的对象,除了能调用方法外,还可以调用自身,在 JS 中函数也是一种对象,且符合我们的要求。
underscore 构造函数
const _ = function(obj) {
if(!(this instanceof _)) return new _(obj)
this._wrapped = obj
}
上面的代码,调用时先判断调用者是否是 underscore 的实例,如果不是就以 obj 为参数创建 underscore 实例,该实例包含一个 _wrapped
属性用来保存 obj。
例子:
_('text')
// 调用过程分为如下几步:
// 1. 因为 this === window(假设在浏览器环境),不是 _ 实例,所以创建 _ 实例
// 2. new _(obj) 再次调用 _,此时 this 是 _ 的实例,所以运行 this._wrapped = obj
挂载静态方法到 underscore
const _ = function(obj) {
if(!(this instanceof _)) return new _(obj)
this._wrapped = obj
}
_.reverse = function(string) {
return string.split('').reverse().join('')
}
// 调用
_.reverse('hello') // olleh
把静态方法挂到 prototype 上
前面用 _('text').reverse()
的方法调用,实际是创建一个 underscore 实例,然后调用这个实例的 reverse
方法,显然我们需要把方法挂到 prototype 上,而不是给这个实例添加方法。
具体步骤分为如下几步:
- 找到所有静态方法
- 遍历静态方法并包装执行函数
// ... 代码同上
// 找到 obj 的所有静态方法并返回方法名数组
_.functions = function(obj) {
const names = []
for(const key in obj) {
if(obj[key] instanceof Function) {
names.push(key)
}
}
return names
}
// 把实例方法挂载到 prototype
_.mixin = function(obj) {
for(const name of _.functions(obj)) {
// 注意这行的 _[name] = obj[name]
// 有了 _[name] = obj[name],我们可以扩展 _ 的方法
const fn = _[name] = obj[name]
obj.prototype[name] = function(...args) {
const arg = this._wrapped // 获取创建对象时传入的参数
return fn.call(_, arg, ...args)
}
}
}
_.mixin(_)
完整代码
(function () {
// 只考虑最简单的情况:非严格模式且this存在
const root = this;
const _ = function (obj) {
if (!(this instanceof _)) return new _(obj)
this._wrapped = obj
}
root._ = _
_.log = function () {
console.log(this)
}
_.each = function (obj, cb) {
if (obj instanceof Array) {
for (let i = 0; i < obj.length; i++) {
if (cb.call(obj, obj[i], i) === false) {
break
}
}
} else {
for (const key in obj) {
if (cb.call(obj, obj[key], key) === false) {
break
}
}
}
return obj
}
_.functions = function (obj) {
const names = []
for (const key in obj) {
if (typeof obj[key] === 'function') {
names.push(key)
}
}
return names
}
_.reverse = function (string) {
return string.split('').reverse().join('')
}
// 挂载对象方法到 prototype
_.mixin = function (obj) {
_.each(_.functions(obj), (name) => {
const func = _[name] = obj[name]
_.prototype[name] = function (...args) {
const arg = this._wrapped
return func.call(_, arg, ...args)
}
})
}
_.mixin({
..._,
addOne(num) {
return num + 1
}
})
})()
参考资料
https://github.com/mqyqingfeng/Blog/issues/56