谈起this的指向,是实际应用中比较常见的使用,同时也是面试中最常见的问题;在实际的应用中,我们通过Vue的this去调用方法,获得属性,在React的生命周期写法中,通过将函数先bind内部this,然后通过this.函数名调用函数;
this指向
普通函数
this是在调用的时候才被动态创建的this的指向取决于当前被调用的上下文;- 全局函数的内部
this指向window; - 对象内部函数
this指向当前对象所在的this; - 如果在调用的过程中,更改了
this的指向,则指向被更改的对象;
- 全局函数的内部
var a = '333'
function g_fun(){
console.log(this) // Window
console.log("定义在全局的函数",this.a) ;// '333'
}
g_fun() ;
var obj={
a:'对象内部的a',
g_fun:function(){
console.log(this.a) //'对象内部的a'
}
}
obj.g_fun() //
图1 this的指向问题
注意点
⚠️:如果全局函数是用let进行定义的变量,在函数中是没有办法通过this.变量访问的,let定义在全局的变量,没有办法挂载到this中,但是可以通过变量名的方式进行访问;
- 全局通过
var定义的变量挂载到this/window中 - 全局通过
let定义的变量不会挂载到this/window中
let b = 'mfy'
var b1 = 'mfy'
function g_funb(){
console.log(this.b) //underfined
console.log(b);//mfy
console.log(this.b1) //mfy
}
console.log(this)
g_funb()
图2 var、let 声明的变量
箭头函数
箭头函数无this,而内部的继承父执行上下文里面的this;
let arrowFnn = ()=>{
console.log(this) // window
}
var objfn = {
name:'ee',
arrowFnn:()=>{
console.log(this) //window
}
}
箭头函数找this 的指向,只需要按照层级一层一层向上查找,找到第一个非箭头函数,如果无则this指向全局;
常见面试题
var name = 2;
let funn = {
name:'mfy',
printName:()=>{
var name = 4;
console.log(name) // 4
console.log(this.name) //2
},
printName2:function(){
let name = 'fff'
console.log(this) // funn
console.log(this.name) //mfy
}
}
funn.printName();
funn.printName2();
分别执行printName函数,查看打印的内容;
- 首先分析
funn.printName- 箭头函数 外部无其他的具名函数
this指向window - 变量
name在函数内部局部作用域和全局都存在 - 查找局部作用域,找到name 打印值 停止查找
- 查找当前
this,打印window
- 箭头函数 外部无其他的具名函数
funn.printName2()-
具名函数,
this指向当前调用者obj, 就是obj -
变量
this.name直接获取当前this下的值
-
图3 常见面试题
根据此面试题还会衍生出其他的面试题目
- 将全局的
var使用let定义 - 将
funn.printName内部的this进行更改
更改内部this指向
在一些场景中,我们需要更改函数的内部this,去实现我们的相关需求;更改this方式最多的就是call、bind、apply
函数操作中通常用来更改this指向
- 箭头函数没有this,箭头函数this只取决于包裹的第一个非箭头函数的this,
call、apply、bind都可以更改this,或者执行当前函数;call、apply都是改变this的指向,作用相同,只是传值的参数不同;
call
使用
var obj ={
value:22,
}
function list(name,age){
console.log(this.value)
}
list.call(obj,'33',33)
手写实现
绑定this,并执行当前函数
Function.prototype.myCall=function(context){
//context是当前传入的对象或者其他想要绑定的this
var context = context || window;
context.fn = this;
//取出当前的this
var args =[...arguments].slice(1);
//调用当前的函数
var result = context.fn(...args);
//删除挂在实例上的方法
delete context.fn;
//返回调用的结果值
return result;
}
apply
使用
var obj ={
value:22,
}
function list(name,age){
console.log(this.value)
}
list.call(obj,'33',33)
list.apply(obj,['33',33])
手写实现
Function.prototype.myApply=function(context){
var context = context || window;
context.fn = this;
var result = null;
//判断是否有参数传入
if(arguments[1]){
// 将参数进行分割开
result = context.fn(...arguments[1])
}else{
result = context.fn()
}
delete context.fn;
return result;
}
bind
使用
bind 和 call、apply 不相同点在于,可以绑定函数,但是不会立即执行
- 将当前函数通过传递参数的形式,更改
this的指向 - 只会将当前函数挂载到函数中,不会立即执行 首先是进行bind的使用分析
var obj = {
a:2,
b:4
}
function demo(a,b){
console.log(...arguments)
console.log(a,b)
return false
}
let fun = demo.bind(obj,5)
console.log(fun.name); // 'bound demo'
console.log(fun.bind.name); // 'bind'
console.log(fun.bind); // 'bind'
console.log((function(){}).bind().name); // 'bound '
console.log((function(){}).bind().length); // 0
- 通过
bind进行绑定的this的值返回一个bound的函数 - 除第一个参数外,其他的参数都当作形参传入到demo函数中
- bind的函数打印出来为 bound demo ,匿名函数的话为bound+空格
- bind后的返回值函数,执行后返回值是原函数(demo)的返回值。
如果返回的fun在进行实例化函数呢?
var obj = {
a:2,
b:4
}
function demo(a,b){
console.log(...arguments)
console.log(a,b)
return false
}
let fun = demo.bind(obj,5)
var demom = new fun(6);
console.log(demom,'demom') //demo {} 'demom'
图4 实例化后的bind
- bind的原有的this指向失效了
new fun返回的是demo原生构造器的新对象- 包含了new的操作符号的内容
手写实现
通过上面的实例进行分析bind的功能
- 创建了一个全新的对象。
- 这个对象会被执行
[[Prototype]](也就是__proto__)链接。 - 生成的新对象会绑定到函数调用的this。
- 通过new创建的每个对象将最终被
[[Prototype]]链接到这个函数的prototype对象上。 - 如果函数没有返回对象类型
Object(包含Functoin, Array, Date, RegExg, Error),那么new表达式中的函数调用会自动返回这个新的对象。 6.返回当前的bound
Function.prototype.myBindDemo = function (context) {
//1. 获取调用的函数
let fn = this;
//2. 分离参数bind时候传入的参数
let args = [].slice.call(arguments, 1);
//3.定义一个bound函数
function bound() {
//3.1 合并调用的参数和当前传入的参数
let currArgs = args.concat([].slice.call(arguments,0)) ;//合并所有的参数
// 3.2 判断是否是new的操作
if(this instanceof bound){
//3.3 创建一个全新的对象 ->并且执行[[Prototype]]__proto__链接->链接到这个函数的`prototype`对象上。断开当前的原型链
if(fn.prototype){
function Empty() {} //创建一个函数
Empty.prototype = fn.prototype; //该函数指向原来函数的this
bound.prototype = new Empty(); //脱离当前的原型链,将该bound指向其他
}
//3.4 生成的新对象会绑定到函数调用里面的this
let result = fn.call(this,...currArgs)
var isObject = typeof result === 'object' && result !== null;
var isFunction = typeof result === 'function';
if(isObject || isFunction){
return result;
}
// 返回当前call的函数
return this;
}else{
// apply修改this指向,把两个函数的参数合并传给self函数,并执行self函数,返回执行结果
return fn.apply(context, currArgs);
}
}
return bound;
}
bind 总结
- bind是Function原型链中的
Function.prototype的一个属性,它是一个函数,修改this指向,合并参数传递给原函数,返回值是一个新的函数。 - bind返回的函数可以通过new调用,这时提供的this的参数被忽略,指向了new生成的全新对象。内部模拟实现了new操作符。
面试常见问题总结
- this指向问题
- 全局函数this指向
- 箭头函数 this指向
this指向应用题判断
- 更改
this指向的方式 - 三种方式区别
- 三种方式手写实现
参考文档
- 《javascript 高级程序设计第四班》
- 《彻底搞懂JavaScript中的this指向问题》
- 一篇公众号文章,找了许久未找到源文章