this的绑定指向详细解答

1 .为什么需要this?

1. 1 this指针

  • JS是词法作用域
    • 每当我们执行到函数时,执行引擎会自动帮我们创建一个执行上下文,这个执行上下文中保存了执行栈(调用栈),this指针。
    • 如果不在人为干扰下,this指针绑定的就是调用栈的栈顶元素。相对于对象来说就是多层级对象下离属性最近的对象

1. 2 为啥需要this

  • 首先在JS中传入的对象不同,所处的地理环境不同,把这个地理环境抽象一下就是所处的上下文不同,得到结果也不同
  • 而在一个函数中这个上下文只要一个就行了,而且如果函数嵌套层级一多起来就容易乱,毕竟人不是机器。
  • 所以把这个极具功能性的内容抽象出来,形成了this指针。

1. 3 有this和没有this的区别

在这里插入图片描述
在这里插入图片描述

1. 4 this全局作用下的指向=>window

在这里插入图片描述

  • 但是,开发中很少直接在全局作用于下去使用this,通常都是在函数中使用。
    • 所有的函数在被调用时,都会创建一个执行上下文:
    • 这个上下文中记录着函数的调用栈、AO对象等;
    • this也是其中的一条记录;

1 .4 .1 node环境和全局下的指向

// 开发中如果没有this我们也有解决方案的
var obj={
	name:"why",
	eating(){
		console.log(obj.name+"睡觉");
	},
	eating1(){
		console.log(obj.name+"睡觉l");
	},
	eating2(){
		console.log(obj.name+"睡觉2");
		console.log(this);  //指向上一层的对象obj
	},
	
}
// this 指向的同一个作用域
// 在全局的情况下 this绑定的就是window
// 在node 环境下就是一个null对象
console.log(this.eating);
console.log(this);//window   <=>console.log(window)
obj.eating()
obj.eating1()
obj.eating2()

2 .关于this的指向到底和什么有关系

💛this指向跟函数所处的位置是没有关系的
💛 跟函数被调用的方式是有关系

function foo(){
	console.log(this);
}
// 调用方式1
foo()    //window
var obj={
	name:"why",
	foo:foo
}
obj.foo();  //obj对象
foo.call("abc") //String {"abc"}对象

3 .this的绑定规则

3 .1 默认绑定

  • 什么情况下使用默认绑定呢?独立函数调用
    • 独立的函数调用我们可以理解成函数没有被绑定到某个对象上进行调用
//独立函数的调用
function foo(){
	console.log(this);   //windows
}
foo()

// 案列二
function foo1(){
	console.log(this); //windows
} 
function foo2(){
	console.log(this); //windows
	foo1()
}
function foo3(){
	console.log(this);//windows
	foo2()
}
foo3()

// 案列三
var obj={
	name:"why",
	foo(){
		console.log(this); //windwos
	}

}
var bar=obj.foo;
bar()   //只和调用的方式有关系 与位置没有任何关系 


// 案列四
function foo(){
	console.log(this);  //window
}
var obj={
	name:"why",
	foo:foo
}
var bar=obj.foo
bar()

// 案列五
function foo(){
	function bar(){
		console.log(this); //window
	}
	return bar
}
var bar2=foo()
bar2();

3 .2隐式绑定

  • 另外一种比较常见的调用方式是通过某个对象进行调用的
    • 也就是它的调用位置中,是通过某个对象发起的函数调用。

语法 :隐式绑定: object.fn()

  • 隐式绑定说通俗一点就是把this调起来 , 谁调的指向谁
 // 1.案例一:
      var obj = {
        name: "why",
        foo: foo
      }
      obj.foo() // obj对象  
 // 3.案例三:
      var obj1 = {
        name: "obj1",
        foo: function () {
          console.log(this)   //Object
        }
      }
      var obj2 = {
        name: "obj2",
        bar: obj1.foo
      }
      obj2.bar()

3 .3显示绑定

  • 隐式绑定有一个前提条件:
    • 必须在调用的对象内部有一个对函数的引用(比如一个属性);
    • 如果没有这样的引用,在进行调用时,会报找不到该函数的错误;
    • 正是通过这个引用,间接的将this绑定到了这个对象上;
  • 如果我们不希望在对象内部包含这个函数的引用,同时又希望在这个对象上进行强制调用,该怎么做呢?
    • JavaScript所有的函数都可以使用call和apply方法(这个和Prototype有关)。

它们两个的区别

  • 其实非常简单,第一个参数是相同的,后面的参数,apply为数组,call为参数列表
    这两个函数的第一个参数都要求是一个对象,这个对象的作用是什么呢?就是给this准备的。
  • 在调用这个函数时,会将this绑定到这个传入的对象上。
  • 因为上面的过程,我们明确的绑定了this指向的对象,所以称之为显示绑定。

3 .3 .1 通过call或者apply绑定this对象

  • 显示绑定后,this就会明确的指向绑定的对象
    在这里插入图片描述
 //foo直接调用和call /apply调用不同的this绑定不同
 // foo直接调用指向的全局对象window
 var obj={
	name:"obj"
}
//call/apply 指定this的重新绑定
foo.call(obj)  
foo.apply(obj)
foo.apply("aaa")

3 .3 .2函数显示的绑定到一个对象上

在这里插入图片描述

function foo() {
  console.log(this)
}

// foo.call("aaa")
// foo.call("aaa")
// 默认绑定和显示绑定bind冲突: 优先级(显示绑定)

var newFoo = foo.bind("aaa")

newFoo()
var bar = foo
console.log(bar === foo)
console.log(newFoo === foo)

3 .3 .3 call和apply有什么区别?

function sum(num1, num2, num3) {
  console.log(num1 + num2 + num3, this)
}

sum.call("call", 20, 30, 40)   //参数列表
sum.apply("apply", [20, 30, 40]) //数组

3 .4new绑定

JavaScript中的函数可以当做一个类的构造函数来使用,也就是使用new关键字。

  • 使用new关键字来调用函数是,会执行如下的操作:
    1.创建一个全新的对象;
    2.这个新对象会被执行prototype连接;
    3.这个新对象会绑定到函数调用的this上(this的绑定在这个步骤完成);
    4.如果函数没有返回其他对象,表达式会返回这个新对象;
    在这里插入图片描述

4 .绑定规则优先级

❤🧡💛.默认规则的优先级最低

  • 毫无疑问,默认规则的优先级是最低的,因为存在其他规则时,就会通过其他规则的方式来绑定this

❤🧡💛显示绑定优先级高于隐式绑定

测试一

var obj = {
  name: "obj",
  foo: function() {
    console.log(this)
  }
}
obj.foo()
// 1.call/apply的显示绑定高于隐式绑定
obj.foo.apply('abc')
obj.foo.call('abc')

// 2.bind的优先级高于隐式绑定
var bar = obj.foo.bind("cba")
bar()

测试二

// 3.更明显的比较
function foo() {
  console.log(this)
}
var obj = {
  name: "obj",
  foo: foo.bind("aaa")
}
obj.foo()

❤🧡💛new绑定优先级高于隐式绑定
测试代码

var obj = {
  name: "obj",
  foo: function() {
    console.log(this)
  }
}

// new的优先级高于隐式绑定
var f = new obj.foo()

❤🧡💛new绑定优先级高于bind
注意

  • new绑定和call、apply是不允许同时使用的,所以不存在谁的优先级更高
  • new绑定可以和bind一起使用,new绑定优先级更高

测试代码

// 结论: new关键字不能和apply/call一起来使用

// new的优先级高于bind
function foo() {
  console.log(this)
}

var bar = foo.bind("aaa")

var obj = new bar()

// new绑定 > 显示绑定(apply/call/bind) > 隐式绑定(obj.foo()) > 默认绑定(独立函数调用)

4 .1特殊绑定忽略显示绑定

  • 如果在显示绑定中,我们传入一个null或者undefined,那么这个显示绑定会被忽略,使用默认规则:
    在这里插入图片描述
function foo() {
  console.log(this)
}
foo.apply("abc")
foo.apply({})
// apply/call/bind: 当传入null/undefined时, 自动将this绑定成全局对象
foo.apply(null)
foo.apply(undefined)
var bar = foo.bind(null)
bar()

4. 2间接函数引用

  • 另外一种情况,创建一个函数的间接引用,这种情况使用默认绑定规则。
    • 赋值(obj2.foo = obj1.foo)的结果是foo函数;
    • foo函数被直接调用,那么是默认绑定;

在这里插入图片描述

5 箭头函数

注意点

  • 箭头函数不会绑定this、arguments属性;
  • 箭头函数不能作为构造函数来使用(不能和new一起来使用,会抛出错误);
// 1.编写箭头函数
// 1> (): 参数
// 2> =>: 箭头
// 3> {}: 函数的执行体
var foo = (num1, num2, num3) => {
  console.log(num1, num2, num3)
  var result = num1 + num2 + num3
  console.log(result)
}

function bar(num1, num2, num3) {
}

// 高阶函数在使用时, 也可以传入箭头函数
var nums = [10, 20, 45, 78]
nums.forEach((item, index, arr) => {})

5 .1箭头函数的编写优化

  • 优化一: 如果只有一个参数()可以省略
nums.forEach(item => {
  console.log(item)
})
  • 简写二: 如果函数执行体只有一行代码, 那么{}也可以省略
  • 注意 : 并且它会默认将这行代码的执行结果作为返回值
nums.forEach(item => console.log(item))
var newNums = nums.filter(item => item % 2 === 0)
console.log(newNums)

filter/map/reduce

// filter/map/reduce
var result = nums.filter(item => item % 2 === 0)
                 .map(item => item * 100)
                 .reduce((preValue, item) => preValue + item)
console.log(result)
  • 如果函数执行体只有返回一个对象, 那么需要给这个对象加上()
// var bar = () => {
//   return { name: "why", age: 18 }
// }
var bar = () => ({ name: "why", age: 18 })

5 .2箭头函数的this

箭头函数不使用this的四种标准规则(也就是不绑定this),而是根据外层作用域来决定this。

  • 如下模拟网络请求的案例:
    • 这里我使用setTimeout来模拟网络请求,请求到数据后如何可以存放到data中呢?
    • 我们需要拿到obj对象,设置data;
    • 但是直接拿到的this是window,我们需要在外层定义:var _this = this
    • 在setTimeout的回调函数中使用_this就代表了obj对象
      在这里插入图片描述
      测试代码
// 2.应用场景
var obj = {
  data: [],
  getData: function() {
    // 发送网络请求, 将结果放到上面data属性中
    // 在箭头函数之前的解决方案
    // var _this = this    //这样this就绑定在了obj
    // setTimeout(function() {
    //   var result = ["abc", "cba", "nba"]
    //   _this.data = result

    // 如果这样写 this.data=result //这里的this是window
    // }, 2000);
    // 箭头函数之后
    setTimeout(() => {
      var result = ["abc", "cba", "nba"]
      this.data = result  //箭头函数不绑定this 
      // 这样写是因为obj里面绑定了this 
    }, 2000);
  }
}

obj.getData()

注意箭头函数并不绑定this对象,那么this引用就会从上层作用于中找到对应的this

//1.测试箭头函数中this指向
var name = "why"

var foo = () => {
  console.log(this)
}
foo()
var obj = {foo: foo}
obj.foo()
foo.call("abc")

6 分析this的指向=>面试题

代码一

var name = "window";

var person = {
  name: "person",
  sayName: function () {
    console.log(this.name);
  }
};

function sayName() {
  var sss = person.sayName;
  sss(); // window: 独立函数调用
  person.sayName(); // person: 隐式调用
  (person.sayName)(); // person: 隐式调用
  (b = person.sayName)(); // window: 赋值表达式(独立函数调用)
}
sayName();

代码二

var name = 'window'

var person1 = {
  name: 'person1',
  foo1: function () {
    console.log(this.name)
  },
  foo2: () => console.log(this.name),
  foo3: function () {
    return function () {
      console.log(this.name)
    }
  },
  foo4: function () {
    return () => {
      console.log(this.name)
    }
  }
}

var person2 = { name: 'person2' }

person1.foo1(); // person1(隐式绑定)
person1.foo1.call(person2); // person2(显示绑定优先级大于隐式绑定)

person1.foo2(); // window(箭头函数不绑定作用域,上层作用域是全局)// 对象不绑定作用域
person1.foo2.call(person2); // window (箭头函数不绑定作用域的,)
 
person1.foo3()(); // window(独立函数调用)
person1.foo3.call(person2)(); // window(独立函数调用)
person1.foo3().call(person2); // person2(最终调用返回函数式, 使用的是显示绑定)

person1.foo4()(); // person1(箭头函数不绑定this, 上层作用域this是person1)
person1.foo4.call(person2)(); // person2(上层作用域被显示的绑定了一个person2)
person1.foo4().call(person2); // person1(上层找到person1)

代码三

var name = 'window'

function Person (name) {
  this.name = name
  this.foo1 = function () {
    console.log(this.name)
  },
  this.foo2 = () => console.log(this.name),
  this.foo3 = function () {
    return function () {
      console.log(this.name)
    }
  },
  this.foo4 = function () {
    return () => {
      console.log(this.name)
    }
  }
}

var person1 = new Person('person1')
var person2 = new Person('person2')

person1.foo1() // person1
person1.foo1.call(person2) // person2(显示高于隐式绑定)

person1.foo2() // person1 (上层作用域中的this是person1)
person1.foo2.call(person2) // person1 (上层作用域中的this是person1)  //call不绑定this

person1.foo3()() // window(独立函数调用)   
person1.foo3.call(person2)() // window  //独立调用
person1.foo3().call(person2) // person2  //拿到了window后显示绑定在了person2

person1.foo4()() // person1
person1.foo4.call(person2)() // person2    //自己独立调用显示绑定person2
person1.foo4().call(person2) // person1    //call调用不绑定this

代码四

var name = 'window'

function Person (name) {
  this.name = name
  this.obj = {
    name: 'obj',
    foo1: function () {
      return function () {   //返回l全局调用
        console.log(this.name)
      }
    },
    foo2: function () {
      return () => {
        console.log(this.name)
      }
    }
  }
}

var person1 = new Person('person1')
var person2 = new Person('person2')
       
person1.obj.foo1()() // window
person1.obj.foo1.call(person2)() // window
// ()直接调用
person1.obj.foo1().call(person2) // person2

person1.obj.foo2()() // obj箭头函数没有this 返回上一层作用域
person1.obj.foo2.call(person2)() // person2
person1.obj.foo2().call(person2) // obj

7 .补充

{ } 这个是块级作用域

var obj={
}
 //对象字面量 全局作用域

//上层作用域的理解
var obj = {
  name: "obj",
  foo: function() {
    // 上层作用域是全局
  }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值