前言
在方法原型链中有3个能改变方法调用中的this,分别是apply、call、bind,三个都是Function原型链中的方法,可以在我们定义的方法中调用,下面就开始详细的介绍
目录
- 方法内的this是什么
- call、apply方法的原理
- bind方法的原理
- 总结
方法内的this是什么
我们在调用方法是通常在方法体内使用到this这个关键字,主要分以下5种情况
- 自然调用this都指向window(浏览器环境下)
// 声明方法后,自然调用
function a() {
console.log(this);
}
a() // window
// 无论是在方法体嵌套,只要是自然调用都属于window(浏览器环境下)
function b() {
a() // window
}
b()
- 在引用类型中的调用函数中,this属于调用该方法的调用者(通俗来说就是"."之前的那个)
// 在对象体内定义方法
const a = {
b: function () {
console.log(this);
}
}
a.b() // a
// 在嵌套对象体内定义方法
const c = {
d: {
e: function () {
console.log(this);
}
}
}
const f = c.d
c.d.e() // d
- new构造方法其内部的this是该构造方法
// 定义一个构造方法
function Person() {
console.log(this);
}
const a = new Person() // Person
Ps: new构造方法的内部实现
function custom_new(constructor, ...args) {
// 保存构造方法的prototype
const originPrototype = constructor.prototype
// 利用构造方法的prototype创建一个对象
const obj = Object.create(originPrototype)
// 挂载构造方法、并调用
const special = Symbol('special')
obj.__proto__[special] = constructor
const res = obj[special](...args)
delete obj.__proto__[special]
// 构造方法调用后的返回值是一个引用类型,则会使用返回值
return res && res instanceof Object ? res : obj
}
- 方法原型链上提供了一个bind方法用于改变方法的this指向
- 经过bind返回的方法,其this通常就是bind方法的第一个参数(bind的详细介绍在下方)
- 但是如果bind方法在用new调用构造方法那就是bind之前的方法本身
function a() {
console.log(this);
}
const b = { a: 'hello' }
const c = a.bind(b)
c() // b
const d = new c() // a
- 箭头函数,其this是声明函数时所在的作用域this(无法被bind改变this指向,只取决于声明时的作用域)
// 声明时的作用域
const a = () => { console.log(this) }
const obj = { a }
obj.a() // window
const b = { o: 'hello' }
const c = a.bind(b)
c() // window
call、apply方法的原理
- call和apply都是Function原型链上的方法
- 用于改变方法调用内部的this指向
- 返回调用方法后返回的结果
function a(){
console.log(this)
return this.a + ' world'
}
const b = { a: 'hello'}
const c = a.call(b) // 方法a里的console.log 打印出 {a: 'hello'}
const d = a.apply(b) // 方法a里的console.log 打印出 {a: 'hello'}
console.log(c) // hello world
console.log(d) // hello world
不同点: 参数传递不一样
- apply传递参数是把参数以数组的形式传递到方法的第二个参数
- call传递参数则把参数接到方法的第二个参数往后
function a(paramA, paramB, paramC){
console.log(this, paramA, paramB, paramC)
}
const b = { a: 'hello'}
const paramA = 'A'
const paramB = 'B'
const paramC = 'C'
a.call(b, paramA, paramB, paramC)
a.apply(b, [paramA, paramB, paramC])
call、apply的原理(手写call以及apply方法)
- 类型处理,call、apply方法下都会将基本类型进行转换成基本类型的引用实例(如,3转化成 Number {3})
- 方法挂载到隐式原型下
- 方法调用
- 删除隐式原型下的方法
Function.prototype.call = function (initThis, ...args){ // apply和call的不同点在这,参数传递不通
if (typeof this !== 'function'){
throw new Error('this must be a function')
}
let handleThis;
//类型处理
switch (typeof initThis){
case 'string':
handleThis = new String(initThis)
break
case 'number':
handleThis = new Number(initThis)
break
case 'boolean':
handleThis = new Boolean(initThis)
break
default:
// 浏览器环境下才有window
handleThis = initThis || window // null undefined (转化成window) Symbol Object Array function
break
}
// 方法挂载、方法调用、方法销毁
const special = Symbol('special')
initThis.__proto__[special] = this // this指向的是当前的方法 如:a.call() 引用类型调用'.'的前一个为this
const res = initThis[special](...args)
delete initThis.__proto__[special]
return res
}
apply唯一不同点就是参数传递
Function.prototype.apply = function (initThis, args){ // apply和call的不同点在这,参数传递不通
if (typeof this !== 'function'){
throw new Error('this must be a function')
}
let handleThis;
//类型处理
switch (typeof initThis){
case 'string':
handleThis = new String(initThis)
break
case 'number':
handleThis = new Number(initThis)
break
case 'boolean':
handleThis = new Boolean(initThis)
break
default:
handleThis = initThis || window // null undefined (转化成window) Symbol Object Array function
break
}
// 方法挂载、方法调用、方法销毁
const special = Symbol('special')
initThis.__proto__[special] = this // this指向的是当前的方法 如:a.call() 引用类型调用'.'的前一个为this
const res = initThis[special](...args)
delete initThis.__proto__[special]
return res
}
bind方法的原理
- bind也是Function原型链上的方法
- 用于改变方法调用内部的this指向
- 返回一个新的方法(不调用原方法)
function a(){
console.log(this)
}
const b = { a: 'hello'}
const c = a.bind(b)
c() // console.log { a: 'hello'}
bind的原理(手写bind方法)
- 类型处理,原生bind方法下都会将基本类型进行转换成原基本类型的引用实例(如,3转化成 Number {3})
- 定义返回方法(判断处理,如果是new关键字调用构造方法的,则要使用当前的this)
- new构造函数调用:使用当前的this进行方法挂载、方法调用、方法删除
- 普通函数调用:使用处理好的引用类型进行方法挂载、方法调用、方法删除
- 将返回的方法的prototype关联this.prototype(为了new构造方法时调用的是原构造方法)
- 返回方法
Function.prototype.bind = function (initThis, ...args){
if (typeof this !== 'function'){
throw new Error('this must be a function')
}
let handleThis;
//类型处理
switch (typeof initThis){
case 'string':
handleThis = new String(initThis)
break
case 'number':
handleThis = new Number(initThis)
break
case 'boolean':
handleThis = new Boolean(initThis)
break
default:
handleThis = initThis || window // null undefined (转化成window) Symbol Object Array function
break
}
const special = Symbol('special')
const _this = this
// 返回值
const returnFn = function (...returnArgs){
// 判断是否是new关键字
// new关键字是其构造函数本身,_this指向的就是原方法
// 普通调用则使用处理好的引用类型
const callThis = this instanceof _this ? this : handleThis
//方法挂载、调用、删除
callThis.__proto__[special] = this
callThis[special](...[...args, ...returnArgs])
delete callThis[special](...[...args, ...returnArgs])
}
// 关联当前的prototype
if(this.prototype){
returnFn.prototype = this.prototype
}
return returnFn
}
总结
this判断
- 先判断是否是自然调用,自然调用即window(浏览器下)
- "引用类型.方法名()“调用则就是该引用类型,如果引用类型依旧是方法则需要通过”."的链向上查找判断
- 经过bind改变的this的方法this指向改变值,但不会影响原型链,所以new出来还是原函数
- new构造函数this是构造函数本身,但是new箭头函数则会报错
apply、call实现步骤
- 类型处理,处理成引用类型
- 在引用类型上,方法挂载、方法调用、方法删除
- 返回方法调用的结果
bind实现步骤
- 类型处理,处理成引用类型
- 定义返回方法(new构造函数调用、普通函数调用)
- new:使用当前的this进行方法挂载、方法调用、方法删除
- 普通:使用处理好的引用类型进行方法挂载、方法调用、方法删除
- 关联原型
- 返回方法