一次性搞定JS中的this绑定

前言


😈大家好,我是一溪风月一名前端工程师,在开发的过程中,总觉得很多小伙伴对this的使用方式不太了解,或者说有时候摸不着头脑this是JavaScript中非常重要的概念,如果this不会使用会在开发中遇到问题不能够解决,所以今天我们就来学习下this的常见使用方式,在这里我们不会考虑箭头函数的情况,因为它本就没有this我们只会考虑有this的情况,那么让我们一次性解决这些问题吧。

一.直接调用(独立函数调用/全局调用)


😂直接调用顾名思义就是直接进行函数的调用,怎么直接调用哪? 我们常见的直接调用分为两种情况,如下:

js 复制代码
function foo () {
  console.log(this)
}
//进行函数调用
foo()

这是最常见的一种情况,这种情况this在非严格模式的情况下指向的是全局对象,这种调用方式,我们称之为独立函数调用,或者称之为全局调用。

还有另外一种情况是在对象中定义,然后赋值给一个变量,然后单独对这个赋值后的函数进行调用,依然是独立函数调用或者称之为全局调用,非严格模式下指向的是window。

js 复制代码
let obj = {
  name: "zpj",
  foo: function () {
    console.log(this)
  }
}
let bar = obj.foo
bar()

这种写法,依然属于独立函数调用,指向的依然是window或者说全局对象。

🚨注意:独立函数调用(全局调用)this的指向在严格模式下并不指向window而是指向undefined所以我们在使用的时候千万不要使用this来代替window,而是直接使用window。

二.对象绑定


😈通过对象绑定比较容易理解,因为我们在平时会经常用到,当我们在如下的这种方式进行的时候会指向调用它的对象obj

js 复制代码
let obj={
  name:"aaa",
  foo:function(){
    console.log(this);
  }
}
obj.foo()

// { name: 'aaa', foo: [Function: foo] }

三.new绑定


🦊在讲解new绑定之前,我们先来看下当我们在new一个对象的时候到底做了什么事情。

  1. 创建新的空对象
  2. this指向这个空对象。
  3. 指向函数体中的代码。
  4. 没有显示返回空对象的时候默认返回这个对象。

😶‍🌫️从第二步我们知道,当我们进行new操作的时候会将this绑定到实例化的这个对象上面。

js 复制代码
function foo (name) {
  this.name = name
  console.log(this.name)
}
let bar = new foo("zzz")
console.log(bar)

// zzz
// foo { name: 'zzz' }

通过上述的代码我们可以看到当我们进行对象实例化的时候函数内部的this指向的是实例化的这个对象。

四.this指向绑定事件的元素


js 复制代码
<ul id="color-list">
  <li>red</li>
  <li>yellow</li>
  <li>blue</li>
  <li>green</li>
  <li>black</li>
  <li>white</li>
</ul>
js 复制代码
// this 是绑定事件的元素
// target 是触发事件的元素 和 srcElememnt 等价
let colorList = document.getElementById("color-list");
colorList.addEventListener("click", function (event) {
  console.log('this:', this);
  console.log('target:', event.target);
  console.log('srcElement:', event.srcElement);
})

🤡有些时候我们会遇到一些困扰,比如在div节点的事件函数内部,有一个局部的 callback 方法,该方法被作为普通函数调用时,callback 内部的this是指向全局对象 window的.

js 复制代码
<div id="div1">我是一个div</div>
js 复制代码
window.id = 'window';
document.getElementById('div1').onclick = function(){
  console.log(this.id); // div1
  const callback = function(){
    console.log(this.id); // 因为是普通函数调用,所以 this 指向 window
  }
  callback();
}

此时有一种简单的解决方案,可以用一个变量保存 div节点的引用,如下:

js 复制代码
window.id = 'window';
document.getElementById('div1').onclick = function(){
  console.log(this.id); // div1
  const that = this; // 保存当前 this 的指向
  const callback = function(){
    console.log(that.id); // div1
  }
  callback();
}

五.显式绑定(call/apply)


🥴有的时候this的指向并不能如我们所愿,这个时候我们需要手动去更改this的指向,来满足我们的需求,其实在JavaScript中给我们提供了能够更改this的绑定,首先我们看下call和apply。

js 复制代码
function foo(name,age){
  console.log(this);
  console.log(name,age);
}
const obj = {
  name:"zs",
  age:12,
}
foo.call(obj,'ls',30)

// { name: 'zs', age: 12 }
// ls 30

通过call函数我们可以看到我们可以手动的将this绑定到我们新定义的对象上面来,并且通过call单个传参的方式将参数传递给了这个函数,实现了函数的调用和this的绑定。

js 复制代码
function foo (name, age) {
  console.log(this)
  console.log(name, age)
}
const obj = {
  name: "zs",
  age: 12,
}
foo.apply(obj, ['nnn', 45])

// { name: 'zs', age: 12 }
// ls 30

我们会发现我们使用apply的方式进行绑定的修改结果依然如此,差别在于他们的传参方式不同,call是单个的方式进行传参的,而apply是通过数组的方式传参的。

六.bind函数的显式绑定


🐻bind的绑定和callapply的使用有些差别,使用bind会生成一个新的函数,这个新函数我们称之为BF绑定函数,我们需要手动对这个函数进行调用。

js 复制代码
function foo(){
  console.log("foo",this)
}

let obj = {
  name:"why"
}

let bar = foo.bind(obj)
bar()

七.内置函数的调用绑定思考


😂我们在开发中会用到很多内置的函数,比如定时器setTimeout 这个时候我们需要靠经验来判断当前的this指向因为有些东西根本不是我们来调用的,而是函数内部调用的,我们根本不知道他们做了什么,这些内容需要我们自己总结一下,内容如下。

  1. 定时器内部的函数this指向window
js 复制代码
setTimeout(function () {
  console.log(this, "1")
}, 500)
setTimeout(() => {
  console.log(this, "2")
}, 500)
  1. 按钮的点击事件,指向事件发起的对象,也就是绑定事件的元素。
js 复制代码
let box = document.querySelector(".test")
  box.onclick = function () {
    console.log(this)
  }
  1. forEach中的this也是指向window
js 复制代码
const array = [1, 2, 3, 4, 5, 6]
  array.forEach(item => {
    console.log(this)
  })

八.绑定优先级的比较


🥴我们前面了解的都是一些独立的规则,但是实际的情况往往是比较复杂的,可能涉及到多个绑定一起使用的情况,这个时候我们就需要研究一下不同函数之间调用的优先级。

  1. 直接调用(默认绑定)的优先级是最低的。
  2. 显式绑定优先级高于对象绑定(隐式绑定)。
js 复制代码
function foo () {
  console.log(this.name)
}
let obj = {
  name: "zzz",
  bar: foo
}
obj.bar.call({
  name: "aaa"
})

// aaa
  1. new绑定优先级高于对象绑定(隐式绑定)的优先级
js 复制代码
function foo (name) {
  this.name = name
  console.log(this.name)
}
let obj = {
  name: "zzz",
  bar: foo
}
new obj.bar("ccc")

// ccc
  1. new绑定不能和callapply一起使用,new绑定的优先级比bind高。
js 复制代码
function foo (name) {
  this.name = name
  console.log(this.name)
}

let bar = foo.bind("zzz")
new bar("ccc")

// ccc
  1. bindapply的优先级,bind的优先级更高,也高于call因为callapply使用方法一样。
js 复制代码
function foo () {
  console.log(this)
}

let bar = foo.bind("zzz")
bar.call("aaa")

// zzz

九.绑定规则之外


🥴其实在上述的绑定规则之外还有许多我们有时候按照规则难以理解的情况,我们来总结下有哪些情况。

  1. 在显式绑定当中如果传入null undefined这个绑定会被忽略,使用默认规则,严格模式能够绑定。
js 复制代码
function foo () {
  console.log(this)
}

foo.apply(null)
foo.apply(undefined)

// window
// window
  1. 创建一个函数的间接引用,使用函数的默认绑定规则,指向window
js 复制代码
var obj1 = {
  name:"obj1",
  foo:function(){
    console.log("foo",this)
  }
}
var obj2={
  name:"obj2"
};
(obj2.foo = obj1.foo)()

// window

十 .面试题尝试


js 复制代码
const o1 = {
    text: 'o1',
    fn: function () {
        return this.text;
    }
}

const o2 = {
    text: 'o2',
    fn: function () {
        return o1.fn();
    }
}

const o3 = {
    text: 'o3',
    fn: function () {
        var fn = o1.fn;
        return fn();
    }
}

console.log(o1.fn()); // o1
console.log(o2.fn()); // o1
console.log(o3.fn()); // undefined
相关推荐
前端小巷子8 分钟前
CSS单位完全指南
前端·css
SunTecTec1 小时前
Flink Docker Application Mode 命令解析 - 修改命令以启用 Web UI
大数据·前端·docker·flink
拉不动的猪2 小时前
前端常见数组分析
前端·javascript·面试
小吕学编程2 小时前
ES练习册
java·前端·elasticsearch
Asthenia04122 小时前
Netty编解码器详解与实战
前端
袁煦丞2 小时前
每天省2小时!这个网盘神器让我告别云存储混乱(附内网穿透神操作)
前端·程序员·远程工作
一个专注写代码的程序媛3 小时前
vue组件间通信
前端·javascript·vue.js
一笑code3 小时前
美团社招一面
前端·javascript·vue.js
懒懒是个程序员4 小时前
layui时间范围
前端·javascript·layui
NoneCoder4 小时前
HTML响应式网页设计与跨平台适配
前端·html