javascript — this的指向

谈起this的指向,是实际应用中比较常见的使用,同时也是面试中最多见的问题;在实际的应用中,咱们经过Vue的this去调用方法,得到属性,在React的生命周期写法中,经过将函数先bind内部this,而后经过this.函数名调用函数;javascript

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中,可是能够经过变量名的方式进行访问;java

  • 全局经过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指向全局;markdown

常见面试题

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函数,查看打印的内容;app

  • 首先分析 funn.printName
    • 箭头函数 外部无其余的具名函数 this指向window
    • 变量name 在函数内部局部做用域和全局都存在
    • 查找局部做用域,找到name 打印值 中止查找
    • 查找当前this,打印window
  • funn.printName2()
    • 具名函数,this指向当前调用者obj, 就是obj函数

    • 变量this.name 直接获取当前this下的值oop

图3 常见面试题

根据此面试题还会衍生出其余的面试题目ui

  • 将全局的var使用let定义
  • funn.printName 内部的this进行更改

更改内部this指向

在一些场景中,咱们须要更改函数的内部this,去实现咱们的相关需求;更改this方式最多的就是call、bind、apply
函数操做中一般用来更改this指向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,并执行当前函数spa

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的功能

  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指向的方式
  • 三种方式区别
  • 三种方式手写实现

参考文档

相关文章
相关标签/搜索