這篇文章是手寫實現(xiàn)xxx功能部分的第一篇,后續(xù)會陸續(xù)更新其他的啥纸。
一号杏、call方法
考察知識點: call方法基礎用法,F(xiàn)unction原型對象斯棒,解構用法盾致, JS方法中this的指向, arguments等
function sayName(p) {
console.log(this.name)
console.log(p)
return `${p}${this.name}`
}
// ES6版荣暮,比較簡單
Function.prototype.selfCall = function(ctx, ...args) {
if (typeof this !== 'function') {
throw new Error('you must use call with function')
}
// ctx = ctx || window || global // 這個寫法是為了在NodeJS中運行庭惜,不過這個并非這個問題的關鍵
ctx = ctx || window
// 強綁定上ctx為執(zhí)行上下文,這里為了為了防止方法名稱沖突穗酥,我定義這個方法名稱為_fn护赊,不過這個并不是這個問題的關鍵考察點
ctx._fn = this
const res = ctx._fn(...args)
// 刪除這個,防止影響外部的對象正常運行
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 由于無法通過解構的形式傳入?yún)?shù)砾跃,只能通過字符串拼接然后再通過eval來執(zhí)行
var res = eval('ctx._fn(' + argstr + ')')
delete ctx._fn
return res
}
var obj = {name: 'wyh'}
// 驗證結果
console.log(sayName.call(obj, 'xxx'))
console.log(sayName.selfCall(obj, 'xxx'))
console.log(sayName.es5call(obj, 'xxx'))
二骏啰、apply方法
考察知識點: 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方法
考察知識點: bind方法的用法
bind方法是不同于call方法和apply方法的抽高,bind方法會返回一個新的函數(shù)判耕。這個方法的接收參數(shù)形式和意義與call方法一樣,不同的是翘骂,bind會將接收的第一個參數(shù)作為返回的函數(shù)的this對象壁熄。bind方法是函數(shù)珂里化的一個典型例子。
對于bind雏胃,我會特殊對待,由淺到深完成實現(xiàn)它的過程
// 首先定義一些基礎的數(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
這個只是為了更方便大家去理解bind函數(shù)的工作模式
2瞭亮、使用apply/call 實現(xiàn)bind函數(shù) 這個也是很簡單的
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、不使用強綁定形式
// 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 = []
// 做一層緩存固棚,因為下面返回的function中的arguments會和這個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
踩坑
在寫call和apply的時候统翩,一時迷了寫了下面這種實現(xiàn)方法:
// 錯誤示范
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]);
}
// 因為這里newArr.join(',') 返回的是一個字符串,也就是不管這個call方法傳入幾個參數(shù)此洲,真正執(zhí)行的就只會有一個參數(shù)了
var res = ctx._fn(newArr.join(','));
delete ctx._fn;
return res
}
結語
內置的call厂汗,apply,bind方法要比我寫的要復雜且健壯的多呜师。不過自己手動實現(xiàn)這三個方法主要是為了考察這三個函數(shù)是否掌握以及函數(shù)內部this的指向問題娶桦。