javascript — this的指向

我心飞翔 分类:javascript

谈起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() // 
 
javascript — this的指向
图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()
 
javascript — this的指向
图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下的值

javascript — 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'
 
javascript — this的指向
图4 实例化后的bind
  • bind的原有的this指向失效了
  • new fun返回的是demo原生构造器的新对象
  • 包含了new的操作符号的内容

手写实现

通过上面的实例进行分析bind的功能

  1. 创建了一个全新的对象。
  2. 这个对象会被执行[[Prototype]](也就是__proto__)链接。
  3. 生成的新对象会绑定到函数调用的this。
  4. 通过new创建的每个对象将最终被[[Prototype]]链接到这个函数的prototype对象上。
  5. 如果函数没有返回对象类型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指向问题》
  • 一篇公众号文章,找了许久未找到源文章

作者:掘金-javascript — this的指向

回复

我来回复
  • 暂无回复内容