函数中的this指向

this的绑定规则

1.函数在调用时,JavaScript会默认给this绑定一个值;
2.this的绑定和定义的位置(编写的位置)没有关系;
3.this的绑定和调用方式以及调用的位置有关系;
4.this是在运行时被绑定的;

  • 绑定一:默认绑定;
  • 绑定二:隐式绑定;
  • 绑定三:显式绑定;
  • 绑定四:new绑定;

绑定一:默认绑定;

什么情况下使用默认绑定呢? ** 独立函数调用**。
独立的函数调用我们可以理解成函数没有被绑定到某个对象上进行调用;
*严格模式下:独立调用的函数中的this,指向是undefined。

// 1. 直接调用
function foo() {
  console.log("this的指向:", this);
}
foo(); // window

// 2. 函数定义在对象中,但是独立调用
var obj = {
  name: 'zhangsan',
  bar: function () {
    console.log("bar的this:", this);
  },
}
var baz = obj.bar;
baz(); // window

// 3. 高阶函数
function test(fn) { // fn = obj.bar
  fn()
}
test(obj.bar) // window

绑定二:隐式绑定;

通过某个对象发起的函数调用

// 隐式绑定
function foo() {
  console.log("foo()函数this", this)
}
var obj = {
  name: 'zhangsan',
  bar: foo,
}
obj.bar(); // obj

绑定三:显式绑定

隐式绑定有一个前提条件:

必须在调用的对象内部有一个对函数的引用(比如一个属性); 
如果没有这样的引用,在进行调用时,会报找不到该函数的错误; 
正是通过这个引用,**间接**的将this绑定到了这个对象上;

如果我们不希望在 对象内部 包含这个函数的引用,同时又希望在这个对象上进行强制调用,该怎么做呢?

JavaScript所有的函数都可以使用`call` 和 `apply` 方法。
第一个参数是相同的,要求传入一个对象;
这个对象的作用是什么呢?就是给this准备的。
在调用这个函数时,会将this绑定到这个传入的对象上。
后面的参数,apply为数组,call为参数列表;

因为明确的绑定了this指向的对象,所以称之为 显式绑定。

// 显式绑定
var obj = {
  name: 'zhangsan',
}
function foo() {
  console.log("foo函数this", this);
}

// 实现:执行函数,并且函数的this指向为为obj对象

// 隐式绑定实现
// obj.foo = foo;
// obj.foo();
// 执行函数,并且强制this指向为为obj对象
foo.call(obj)
foo.call(123)
foo.call('abc')

:::tips
call/apply/bind
通过call或者apply绑定this对象 ----> 显示绑定后,this就会明确的指向绑定的对象
如果我们希望一个函数总是显示的绑定到一个对象上,可以怎么做呢?
使用bind方法,bind() 方法创建一个新的绑定函数(bound function,BF);
 绑定函数是一个 exotic function object(怪异函数对象,ECMAScript 2015 中的术语)
 在 bind() 被调用时,这个新函数的 this 被指定为 bind() 的第一个参数,而其余参数将作为新函数的参数,供调用时使用。
:::

function foo() {
  console.log("foo:", this);
}
var obj = { name: 'zhangsan' };
// 需求:调用foo时,总是绑定到obj对象身上(不希望obj对象身上有函数)
// 1. bind函数的基本使用
var bar = foo.bind(obj);
bar();

绑定四:new绑定

JavaScript中的函数可以当做一个类的构造函数来使用,也就是使用new关键字。
使用new关键字来调用函数是,会执行如下的操作:
1.创建一个全新的对象;
2.这个新对象会被执行prototype连接;
3.这个新对象会绑定到函数调用的this上(this的绑定在这个步骤完成);
4.如果函数没有返回其他对象,表达式会返回这个新对象;

// new 关键字
/**
  1. 创建新的空对象
  2. 将this指向这个空对象
  3. 执行函数体中的代码
  4. 没有显示返回非空对象时,默认返回这个对象
 */
function foo() {
  this.name = "luna",
    console.log("foo函数", this);
}
new foo();
// foo函数 foo {name: 'luna'}

内置函数的调用绑定
有些时候,我们会调用一些JavaScript的内置函数,或者一些第三方库中的内置函数。
 这些内置函数会要求我们传入另外一个函数;
 我们自己并不会显式的调用这些函数,而且JavaScript内部或者第三方库内部会帮助我们执行; 
这些函数中的this又是如何绑定的呢?
setTimeout、数组的forEach、div的点击

// 内置函数(第三方库):根据一些经验
// 1. 定时器
setTimeout(function () {
  console.log("定时器函数:", this)  //window
}, 1000);
// 2.按钮的点击监听
var btnEl = document.querySelector('button');
btnEl.onclick = function () {
  console.log("btn的点击:", this)// this--> button
}
btnEl.addEventListener('click', function () {
  console.log("btn的点击:", this) // this--> button
})
// 3. foreach
var names = ['abc', 'hhy', 'dream'];
var obj = { name: 'yena' }
names.forEach((function (item) {
  console.log("foreach", this);
}), obj) // this-->obj

规则的优先级

1.默认规则的优先级最低
毫无疑问,默认规则的优先级是最低的,因为存在其他规则时,就会通过其他规则的方式来绑定this
2.显示绑定优先级高于隐式绑定
3.new绑定优先级高于隐式绑定
4.new绑定优先级高于bind, new绑定和call、apply是不允许同时使用的,所以不存在谁的优先级更高
new绑定可以和bind一起使用,new绑定优先级更高
:::info
new > bind > apply/call > 隐式绑定 > 默认绑定
:::

this规则之外 – 忽略显示绑定

如果在显示绑定中,我们传入一个null或者undefined,那么这个显示绑定会被忽略,使用默认规则:

function foo() {
  console.log("foo", this);
}
var obj = {
  name: 'zhanga',
}
foo.call(123); // foo Number {123}
foo.call(null); // window
foo.call(undefined); // window

var baz = foo.bind(null);
baz(); //window

this规则之外 – 间接函数引用

创建一个函数的 间接引用,这种情况使用默认绑定规则。

  • 赋值(obj2.foo = obj1.foo)的结果是foo函数;
  • foo函数被直接调用,那么是默认绑定;
function foo() {
  console.log("foo", this)
}
var obj1 = {
  name: 'obj1',
  foo: foo,
}
var obj2 = {
  name: "obj2",


}
  // obj1.foo(); // this-->obj1
  // obj2.foo = obj1.foo;
  // obj2.foo(); // this-->obj2
;(obj2.foo = obj1.foo)(); // this-->window

this规则之外 – ES6箭头函数

**箭头函数不使用this的四种标准规则(也就是不绑定this),而是根据外层作用域来决定this。 **
模拟网络请求的案例

// 模拟网络请求
// 网络请求的工具函数
function request(url, callbackFn) {
  var results = ["abc", "cba", "nba"]
  callbackFn(results)
}
// 实际操作的位置(业务)
var obj = {
  names: [],
  network: function () {
    // 1.早期的时候
    // var _this = this
    // request("/names", function(res) {
    //   _this.names = [].concat(res)
    // })

    // 2.箭头函数写法
    request("/names", (res) => {
      this.names = [].concat(res)
    })
  },
}

obj.network()
console.log(obj)

面试题

var name = "window";


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

function sayName() {
  var sss = person.sayName; // 赋值
  sss(); // 独立函数调用:默认绑定 this --> window
  person.sayName(); // 隐式绑定:调用 this--> person
  (person.sayName)(); // 隐式绑定:调用 this--> person

  (b = person.sayName)();// 间接函数引用 b = function(){ ... } --> 独立调用 this--> 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(); // 箭头函数没有this,隐式绑定没有意义,向上层作用域查找:window
person1.foo2.call(person2); // 没有this,使用call绑定this没有用,向上层作用域查找 window


person1.foo3()(); // 默认绑定:独立函数调用  window
person1.foo3.call(person2)(); // 默认绑定 window
person1.foo3().call(person2); // 显示绑定:person2


person1.foo4()(); // 隐式绑定:person1
person1.foo4.call(person2)(); // 显示绑定: person2
person1.foo4().call(person2); // 隐式绑定+显示绑定: person1
var name = 'window';
/**
  new
  1. 创建一个空的对象
  2. 将这个空的对象赋值给this
  3. 执行函数体中的代码
  4. 将这个新的对象默认返回
 */
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)
      }
    }
}
// person1/person2 都是对象/实例
var person1 = new Person('person1');
var person2 = new Person('person2');

![截屏2023-02-24 10.37.25.png](https://img-blog.csdnimg.cn/img_convert/2204a6ca2ae85483b0e310d43d49902d.png#averageHue=#2a2b37&clientId=u0d502b66-b8e9-4&from=drop&id=u81f160ce&name=截屏2023-02-24 10.37.25.png&originHeight=426&originWidth=540&originalType=binary&ratio=1.100000023841858&rotation=0&showTitle=true&size=117968&status=done&style=none&taskId=ub521d578-95fc-4494-baba-d5ca15d93be&title=内存图 “内存图”)

person1.foo1() // 隐式绑定: person1
person1.foo1.call(person2) // 隐式绑定:person1.foo1 + 显示绑定:call > this-> person2


person1.foo2() // 隐式绑定+箭头函数 上层作用域查找this  person1
person1.foo2.call(person2) // person1


person1.foo3()() // 隐式绑定+默认绑定 window
person1.foo3.call(person2)()
// foo3是一个带有返回函数的函数 + 显示绑定 call  --》 this--> window
person1.foo3().call(person2) // person1.foo3()拿到一个返回值+显示绑定  person2


person1.foo4()() // 隐式绑定 + 默认绑定 person1
person1.foo4.call(person2)() // person2


person1.foo4().call(person2) // person1
var name = 'window';
// 构造函数
function Person(name) {
  this.name = name
  this.obj = {
    name: 'obj',
    foo1: function () {
      return function () {
        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()()
// person1.obj.foo2() 隐式绑定 obj
// 箭头函数 this向上层作用域查找-->  obj
person1.obj.foo2.call(person2)() //this向上层作用域查找 --> person2

person1.obj.foo2().call(person2) // obj
// person1.obj.foo2() 隐式绑定 obj 
// 箭头函数  call无意义,向上层作用域查找-->  obj

this面试题的newPerson内存图.pngthis面试题四的newPerson内存图2.png

你可能感兴趣的:(JS,javascript,前端,程序人生)