this是能改的,call和apply都可以修改this,ES5里面还新增了一个bind函数
this指向相关笔记后续补 TODO:
call、apply、bind用法
建议先看
function test(year) {
console.log(this.name, year)
}
const obj1 = { name: 'obj1' }
test.call(obj1, year)👆 在test上并没有定义call,但是我们可以用test.call来调用,证明call是原型链上的函数
注意在非严格模式下,目标对象是null或者undefined,this将指向window,而严格模式将会是undefined
原型链相关笔记后续补 TODO:
实现call
上面例子,我们想要让test的this指向obj1,只需要用obj1.test()的形式调用即可,前提是obj1上要有test
所以修改this指向工具方法的思路就有了
function call(fn, target) {
target.fn = fn // 把函数挂到目标对象的临时变量上
const res = target.fn() // 通过目标对象执行函数即可让this指向目标对象
delete target.fn // 清除为了修改this指向而挂上对象的函数
return res // 需要返回运行结果
}执行
call(test, obj1)👇 如mdn文档说的,这种情况非严格模式会是window
function call(fn, target) {
if (target === null || taget === undefined) {
target = window
}
target.fn = fn // 把函数挂到目标对象的临时变量上
const res = target.fn() // 通过目标对象执行函数即可让this指向目标对象
delete target.fn // 清除为了修改this指向而挂上对象的函数
return res // 需要返回运行结果
}为了减少实现的复杂度,我们以下的实现都忽略target是null和undefined的情况
我们把参数也补上
function call(fn, target, ...arg) {
target.fn = fn // 把函数挂到目标对象的临时变量上
const res = target.fn(arg) // 通过目标对象执行函数即可让this指向目标对象
delete target.fn // 清除为了修改this指向而挂上对象的函数
return res // 需要返回运行结果
}👇 挂在Function原型上
Function.prototype.myCall = function(target, ...arg) {
target.fn = this // 如下调用 this就是test函数
const res = target.fn(arg) // 通过目标对象执行函数即可让this指向目标对象
delete target.fn // 清除为了修改this指向而挂上对象的函数
return res // 需要返回运行结果
}
test.myCall()实现apply
apply和call基本一样,只是让参数通过数组传入可能是想方便封装工具的时候,参数未知,用
call就不太方便,可以直接传入arguments但是参数未知的时候,我们传入解构
arguments传入不就行了吗...个人觉得是相同功能的东西设计2个API是多余的
function apply(fn, target, params) {
target.fn = fn // 把函数挂到目标对象的临时变量上
const res = target.fn(...params) // 通过目标对象执行函数即可让this指向目标对象
delete target.fn // 清除为了修改this指向而挂上对象的函数
return res // 需要返回运行结果
}👇 我们复用一下call
function apply(fn, target, params) {
return call(fn, target, ...params)
}👇 挂在Function原型上
Function.prototype.myApply = function(target, params) {
target.fn = this // 如下调用 this就是test函数
const res = target.fn(...params) // 通过目标对象执行函数即可让this指向目标对象
delete target.fn // 清除为了修改this指向而挂上对象的函数
return res // 需要返回运行结果
}
test.myApply()实现bind
bind不会立即执行,而是返回一个this指向修改后的函数
function bind(fn, target, ...arg) {
return function(...arg2) {
target.fn = fn // 把函数挂到目标对象的临时变量上
const res = target.fn(...arg,...arg2) // 通过目标对象执行函数即可让this指向目标对象
delete target.fn // 清除为了修改this指向而挂上对象的函数
return res // 需要返回运行结果
}
}new的问题
由于bind返回的是函数,而这个函数被怎么调用就不是我们可以控制的了 而且bind是修改this指向,如果返回的函数被调用的时候又是另一种this指向的诉求就gg 而恰恰new一个函数就是这种情况
简单来说new的this指向优先级最高
function test() {
console.log(this.name)
}
const obj1 = { name: 'obj1' }
const newTest = bind(test)
new newTest() // 不是直接执行newTest 而是new👆 此时的test虽然被修改了this指向,但是在new面前,this会是new出来的对象
关于new的原理,手写系列-new原理
👇 我们处理一下优先级的问题 通过执行时的this是不是函数自身来判断是new执行还是直接执行做不同的处理
function bind(fn, target, ...arg) {
return function F(...arg2) {
// 通过执行时this是不是函数自身来判断是new
if(this instanceof F) {
return new fn(...arg,...arg2)
}
target.fn = fn // 把函数挂到目标对象的临时变量上
const res = target.fn(...arg,...arg2) // 通过目标对象执行函数即可让this指向目标对象
delete target.fn // 清除为了修改this指向而挂上对象的函数
return res // 需要返回运行结果
}
}👇 我们复用一下call
function bind(fn, target, ...arg) {
return function(...arg2) {
// 通过执行时this是不是函数自身来判断是new
if(this instanceof F) {
return new fn(...arg,...arg2)
}
return call(fn, target, ...arg, ...arg2)
}
}👇 挂在Function原型上
Function.prototype.myBind = function(target, ...arg) {
const fn = this // 如下调用 this就是test函数
return function(...arg2) {
// 通过执行时this是不是函数自身来判断是new
if(this instanceof F) {
return new fn(...arg,...arg2)
}
target.fn = fn // 把函数挂到目标对象的临时变量上
const res = target.fn(...arg,...arg2) // 通过目标对象执行函数即可让this指向目标对象
delete target.fn // 清除为了修改this指向而挂上对象的函数
return res // 需要返回运行结果
}
}
const newtest = test.myBind(obj1)
newtest()拓展优化
我们把函数用一个临时变量挂在目标对象上,调用后就删除
这个临时变量我们可以利用 Symbol 数据类型实现
好处是这个临时变量做的属性名是唯一的,不会被业务代码覆盖
如👇 call改造
Function.prototype.myCall = function(target, ...arg) {
const fnKey = Symbol('fn') // 用Symbol做临时变量属性名
target[fnKey] = this // 如下调用 this就是test函数
const res = target[fnKey](arg) // 通过目标对象执行函数即可让this指向目标对象
delete target[fnKey] // 清除为了修改this指向而挂上对象的函数
return res // 需要返回运行结果
}关于 Symbol - mdn文档 数据类型,后面补充
代码地址
TODO: 实际上是复用apply实现,而不是call的吗?有什么区别?