js绑定事件的两种方式,js中如何绑定事件

本篇文章给大家谈谈js绑定事件的两种方式,以及js中如何绑定事件,希望对各位有所帮助,不要忘了收藏本站喔。

1 this的五种绑定方式

1.1 默认绑定

默认绑定是指当函数调用时,没有为其指定对象上下文,此时会将该函数的this绑定到全局对象(window对象)。自ES5有了严格模式之后,默认绑定方式又分为非严格模式的默认绑定和严格模式的默认绑定python for语句用法

1.1.1 非严格模式

在非严格模式下,函数的默认绑定只能绑定到全局对象window,见下面例子:

var myName = 'syzdev' // 相当于 window.myName = 'syzdev'
function sayName() {
  // 这里的 this === window
  console.log(this === window); // true
  console.log(this.myName); // syzdev
}
sayName()

在这个例子中,首先声明了一个全局变量myName

var声明的变量为全局变量,并且会将该变量添加为全局对象window的属性,需要注意的是,例子中的var不可修改为ES6中的letconst,不然结果大有不同,有兴趣的读者可以查阅资料“varletconst的区别”。

再定义了一个sayName()函数,在函数中输出this.myName,最后直接调用sayName()函数,再调用时,由于没有指定其对象上下文,所以触发了默认绑定,即将函数中的this绑定到全局对象window上,因此在函数内this === window,最后输出的this.myNamesyzdev

1.1.2 严格模式

关于严格模式,这里不做介绍,若不了解的读者可以阅读MDN-严格模式

在严格模式下,有一项规定为“禁止this关键字指向全局对象window”,此时this会绑定到undefined,见下面例子:

// 开启严格模式
'use strict'
var myName = 'syzdev'
function sayName() {
  // 这里的 this !== window
  console.log(this === window); // false
  console.log(this); // undefined
  console.log(this.myName); // 报错:Uncaught TypeError: Cannot read properties of undefined (reading 'myName')
}
sayName()

在这个例子中,在代码顶部使用'use strict'声明了严格模式,由于在严格模式下禁止this绑定到全局对象window,所以函数中的this !== window,输出thisundefined,因此输出this.myName时会直接抛出错误“Uncaught TypeError: Cannot read properties of undefined (reading ‘name’)”。

由于严格模式可以声明为全局或个别函数内,上面的例子中就是声明为全局严格模式,若声明为函数内严格模式,伪代码如下:

function sayName() {
  'use strict'
  ...
}

需要注意的是,严格模式并不会影响到调用函数内的默认绑定,见下面例子:

var myName = 'syzdev'
function sayName() {
  // 这里的 this === window
  console.log(this === window); // true
  console.log(this.myName); // syzdev
}

function strictSayName() {
  // 函数内的严格模式
  'use strict'
  sayName()
}
strictSayName()

在这个例子中,定义函数strictSayName()并在函数内部声明严格模式,再调用sayName()函数,此时sayName()函数内部依然能够将this绑定到全局对象window上。

1.2 隐式绑定

隐式绑定是日常开发中最为常见的绑定方式,即调用函数时为函数指明其对象上下文,此时函数中的this就为对象本身,见下面例子:

var personObj = {
  myName: 'syzdev',
  sayName: function() {
    console.log(this === personObj) // true
    console.log(this.myName); // syzdev
  }
}
personObj.sayName()

在这个例子中,定义一个personObj对象,包含myName属性和sayName()方法,通过对象personObj.sayName()调用函数,此时就会触发this隐式绑定,函数中的this就是personObj本身。

但隐式绑定还可能会出现绑定丢失的情况,此时隐式绑定就会变成了默认绑定,见下面例子:

var personObj = {
  myName: '123',
  sayName: function() {
    console.log(this === window); // true
    console.log(this.myName); // undefined
  }
}
var anotherSayName = personObj.sayName
anotherSayName()

在这个例子中,把personObj.sayName()函数赋值给了变量anotherSayName,再直接使用anotherSayName()调用,此时虽然调用的还是sayName()函数,但是却丢失了对象上下文,此时隐式绑定变成了默认绑定,anotherSayName()函数内的this绑定到全局对象window上,由于window对象上没有myName属性,所以输出undefined

1.3 显式绑定

显示绑定顾名思义,通过调用call/apply/bind方法,强制将函数中的this绑定到指定的对象,三者区别如下:

  • call()接受的是多个对象作为参数。
  • apply()接受的是多个对象组成的数组作为参数。
  • bind()返回的是一个函数,除此之外使用方法与call()一样。

使用call/apply/bind实现显示绑定的方法如下:

  1. 不含参数的使用方法:
function foo() {
  console.log(this.myName);
}

var myName = 'window myName'
var personObj = {
  myName: 'personObj myName'
}

foo() // window myName
foo.call(personObj) // personObj myName
foo.apply(personObj) // personObj myName
foo.bind(personObj)() // personObj myName

在这个例子中,直接调用sayName()函数会触发默认绑定,函数内的this绑定到全局对象window,所以输出window myName。由于该例子中函数不需要传参,所以在使用call/apply/bind方法时,不需要为其传递除this对象外的其他参数,其效果都是一致的,都是将sayNname()函数内的this绑定到personObj对象,最后输出的myNamepersonObj对象中的personObj myName

  1. 含参数的使用方法:
function foo(name, age) {
  console.log(name, age)
}

foo('syzdev', 18) // 直接调用函数传递参数
foo.call(this, 'syzdev', 18) // call执行函数,第一个参数指定this对象,后续参数依次为函数的传参
foo.apply(this, ['syzdev', 18]) // apply执行函数,第一个参数指定this对象,第二个参数为一个数组,数组中为函数的传参
foo.bind(this, 'syzdev', 18)() // bind执行函数,返回的是一个函数,除此之外用法与call相同
// 输出结果
// syzdev 18
// syzdev 18
// syzdev 18
// syzdev 18

代码详解见注释。

1.4 new绑定

在ES6以前,生成一个实例对象的方法是使用构造函数,构造函数只是一个普通的函数,当使用new操作符调用构造函数时,JavaScript解释器便会在底层创建一个新对象,构造函数内的this绑定的就是这个新对象,见下面例子:

function Person(name, age) {
  this.name = name
  this.age = age
}
var person = new Person('syzdev', 18)

在这个例子中,使用new操作符执行一个构造函数,创建一个person对象,在构造函数内会创建一个新的对象,将函数内的this绑定到这个新对象上,如果函数内部没有返回其他对象,则构造函数会默认返回这个新对象,流程如下图所示:

1.5 箭头函数绑定

箭头函数是ES6中的一个重要特性,在箭头函数中没有自己的this,其this是根据外层的作用域来决定的,箭头函数内的this指的是定义时所在的对象,是在函数定义时已经决定了,而不是像普通函数一样在调用时绑定this对象,见下面例子:

var foo = () => {
  console.log(this.myName);
}

var myName = 'window myName'
var personObj = {
  myName: 'personObj myName'
}

foo() // window myName
foo.call(personObj) // window myName
foo.apply(personObj) // window myName
foo.bind(personObj)() // window myName

在这个例子中,函数foo定义为箭头函数,由于箭头函数没有自己的this,其this值在定义时已经决定,所以无法被call/apply/bind方法所改变,在该例子中其this值为全局对象window,所以四种方法调用foo()函数的结果都为window myName

2 this绑定的优先级

所谓this绑定的优先级,指的是当函数执行时若同时指定了多个this对象时的绑定顺序。

  1. 隐式绑定 > 默认绑定

在第2章的例子中已经证明了这个结论:

var personObj = {
  myName: 'syzdev',
  sayName: function() {
    console.log(this === personObj) // true
    console.log(this.myName); // syzdev
  }
}
// 相当于 window.personObj.sayName()
personObj.sayName()

在这个例子中,personObj.sayName()为隐式绑定,而personObj.sayName()相当于window.personObj.sayName()可见window对象的默认绑定失效了,最终执行的this依旧是personObj,所以隐式绑定 > 默认绑定。

  1. 显示绑定 > 隐式绑定

修改第3章的例子如下:

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

var myName = 'window myName'
var personObj = {
  myName: 'personObj myName',
  foo: foo // 修改部分
}

personObj.foo.call(window) // window myName

在上面例子中的personObj.foo为隐式绑定,再通过callfoo()函数中的this显示绑定到全局对象window上,最终输出的结果还是window myName,可见显示绑定 > 隐式绑定。

  1. new绑定 > 显示绑定
function foo(name) {
  this.name = name
}

// 创建对象obj
var obj = {}
// 将obj绑定为函数foo中的this
var bindFoo = foo.bind(obj)
bindFoo('syzdev')
console.log(obj.name) // syzdev

var bindObj = new bindFoo('bind syzdev')
console.log(bindObj.name) // bind syzdev

在这个例子中,使用bind方法将obj绑定到foo()函数中并返回一个新的函数bindFoo,在使用new操作符创建一个新的对象bindObj时,函数bindFoo()this指向发生了改变,原本函数bindFoo()中的this指向的是obj,在使用new操作符后指向了一个新的对象并返回赋值给了bindObj

2.2 总结

在讨论this绑定优先级时之所以不讨论箭头函数,是因为箭头函数的this在其函数定义时就已经确定,不存在优先级一说。其他四种绑定方式的优先级如下,由高到低:

  1. new绑定
  2. 显示绑定
  3. 隐式绑定
  4. 默认绑定
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值