這篇文章是手寫(xiě)實(shí)現(xiàn)xxx功能部分的第一篇,后續(xù)會(huì)陸續(xù)更新其他的。
一、call方法
考察知識(shí)點(diǎn): call方法基礎(chǔ)用法,F(xiàn)unction原型對(duì)象,解構(gòu)用法, JS方法中this的指向, arguments等
function sayName(p) {
console.log(this.name)
console.log(p)
return `${p}${this.name}`
}
// ES6版,比較簡(jiǎn)單
Function.prototype.selfCall = function(ctx, ...args) {
if (typeof this !== 'function') {
throw new Error('you must use call with function')
}
// ctx = ctx || window || global // 這個(gè)寫(xiě)法是為了在NodeJS中運(yùn)行,不過(guò)這個(gè)并非這個(gè)問(wèn)題的關(guān)鍵
ctx = ctx || window
// 強(qiáng)綁定上ctx為執(zhí)行上下文,這里為了為了防止方法名稱沖突,我定義這個(gè)方法名稱為_(kāi)fn,不過(guò)這個(gè)并不是這個(gè)問(wèn)題的關(guān)鍵考察點(diǎn)
ctx._fn = this
const res = ctx._fn(...args)
// 刪除這個(gè),防止影響外部的對(duì)象正常運(yùn)行
delete ctx._fn
// 返回值也要注意
return res
}
// ES5版
Function.prototype.es5call = function(ctx) {
if (typeof this !== 'function') {
throw new Error('you must use call with function')
}
ctx = ctx || window
var args = []
// 收集參數(shù)
for (var i = 1; i < arguments.length; i++) {
args.push('arguments[' + i + ']')
}
var argstr = args.join(',')
ctx._fn = this
// ES5 由于無(wú)法通過(guò)解構(gòu)的形式傳入?yún)?shù),只能通過(guò)字符串拼接然后再通過(guò)eval來(lái)執(zhí)行
var res = eval('ctx._fn(' + argstr + ')')
delete ctx._fn
return res
}
var obj = {name: 'wyh'}
// 驗(yàn)證結(jié)果
console.log(sayName.call(obj, 'xxx'))
console.log(sayName.selfCall(obj, 'xxx'))
console.log(sayName.es5call(obj, 'xxx'))
二、apply方法
考察知識(shí)點(diǎn): apply方法用法
var obj = {
name: 'wyh'
}
function say() {
console.log(arguments[1] + arguments[2]) // expect 5
return this.name
}
Function.prototype.es6apply = function(ctx, args) {
if (typeof this !== 'function') {
throw new Error('you must use apply with function')
}
ctx = ctx || window
ctx._fn = this
const res = ctx._fn(...args)
delete ctx._fn
return res
}
Function.prototype.es5apply = function(ctx, args) {
if (typeof this !== 'function') {
throw new Error('you must use apply with function')
}
ctx = ctx || window
ctx._fn = this
var res
if (!args || !args.length) {
res = ctx._fn()
} else {
var argStr = args.reduce(function(prev, current, currentIndex) {
return prev + ",args[" + currentIndex + "]"
}, '')
argStr = argStr.substr(1)
res = eval("ctx._fn(" + argStr + ")")
}
delete ctx._fn
return res
}
console.log(say.apply(obj, [1,2,3,4])) // 5 , wyh
console.log(say.es6apply(obj, [1,2,3,4])) // 5 , wyh
console.log(say.es5apply(obj, [1,2,3,4])) // 5 , wyh
三、bind方法
考察知識(shí)點(diǎn): bind方法的用法
bind方法是不同于call方法和apply方法的,bind方法會(huì)返回一個(gè)新的函數(shù)。這個(gè)方法的接收參數(shù)形式和意義與call方法一樣,不同的是,bind會(huì)將接收的第一個(gè)參數(shù)作為返回的函數(shù)的this對(duì)象。bind方法是函數(shù)珂里化的一個(gè)典型例子。
對(duì)于bind,我會(huì)特殊對(duì)待,由淺到深完成實(shí)現(xiàn)它的過(guò)程
// 首先定義一些基礎(chǔ)的數(shù)據(jù)方便后面使用
var person = {
name: 'wyh',
sayName(n) {
return n + this.name
},
calc(a,b) {
console.log(this.name)
return a + b
}
}
var person2 = {
name: 'person2'
}
1、 bind 輔助函數(shù)形式
function _bind(fn, ctx, ...args) {
return function() {
return fn.apply(ctx, args)
}
}
console.log(_bind(person.sayName, person2, 'nihao')()) // nihaoperson2
這個(gè)只是為了更方便大家去理解bind函數(shù)的工作模式
2、使用apply/call 實(shí)現(xiàn)bind函數(shù) 這個(gè)也是很簡(jiǎn)單的
Function.prototype.applybind = function(ctx, ...args) {
if (typeof this !== 'function') {
throw new Error('you must use bind with function')
}
ctx = ctx || window
var self = this
return function() {
return self.apply(ctx, args)
}
}
3、不使用強(qiáng)綁定形式
// ES6版
Function.prototype.es6bind = function(ctx, ...args) {
if (typeof this !== 'function') {
throw new Error('you must use bind with function')
}
ctx = ctx || window
ctx._fn = this
return function() {
const res = ctx._fn(...args)
delete ctx._fn
return res
}
}
// ES5 版
Function.prototype.es5bind = function(ctx) {
if (typeof this !== 'function') {
throw new Error('you must use bind with function')
}
ctx = ctx || window
ctx._fn = this
var args = []
// 做一層緩存,因?yàn)橄旅娣祷氐膄unction中的arguments會(huì)和這個(gè)arguments沖突
var fArguments = arguments
for (var i = 1; i < fArguments.length; i++) {
args.push("fArguments[" + i + "]")
}
var argsStr = args.join(',')
return function() {
var res = eval("ctx._fn(" + argsStr + ")")
delete ctx._fn
return res
}
}
console.log(person.calc.es6bind(person2, 1, 2)()) // person2 3
console.log(person.calc.es5bind(person2, 1, 2)()) // person2 3
踩坑
在寫(xiě)call和apply的時(shí)候,一時(shí)迷了寫(xiě)了下面這種實(shí)現(xiàn)方法:
// 錯(cuò)誤示范
Function.prototype.callError = function(ctx) {
ctx = ctx || window
ctx._fn = this
var newArr = [];
for(var i=1; i<args.length; i++) {
newArr.push(args[i]);
}
// 因?yàn)檫@里newArr.join(',') 返回的是一個(gè)字符串,也就是不管這個(gè)call方法傳入幾個(gè)參數(shù),真正執(zhí)行的就只會(huì)有一個(gè)參數(shù)了
var res = ctx._fn(newArr.join(','));
delete ctx._fn;
return res
}
結(jié)語(yǔ)
內(nèi)置的call,apply,bind方法要比我寫(xiě)的要復(fù)雜且健壯的多。不過(guò)自己手動(dòng)實(shí)現(xiàn)這三個(gè)方法主要是為了考察這三個(gè)函數(shù)是否掌握以及函數(shù)內(nèi)部this的指向問(wèn)題。