수동시제, 과거시제, 현재시제, 과거진행시제 등 고등학교 영어의 다양한 시제를 아무리 놓치더라도 이것이 자바스크립트의 가장 흥미로운 특징이라고 할 수 있습니다. , 다음에도 여전히 동일할 것입니다. 이 기사는 "당신이 모르는 자바스크립트 1권"에서 영감을 얻었으며 이에 대한 요약을 자바스크립트로 제공합니다.
이것을 배우기 위한 첫 번째 단계는 이것이 함수 자체나 함수의 범위를 가리키는 것이 아니라는 점을 이해하는 것입니다. 이는 실제로 함수가 호출될 때 발생하는 바인딩이며, 그것이 가리키는 위치는 전적으로 함수가 호출되는 위치에 따라 달라집니다.
기본 바인딩
자바스크립트에서 가장 일반적으로 사용되는 함수 호출 유형은 독립 함수 호출이므로 다른 규칙을 적용할 수 없는 경우 이 규칙을 기본 규칙으로 간주할 수 있습니다. 함수가 수정 없이 호출되면, 즉 "barely"라고 호출되면 기본 바인딩 규칙이 적용되고 기본 바인딩은 전역 범위를 가리킵니다.
function sayLocation() { console.log(this.atWhere) } var atWhere = "I am in global" sayLocation() // 默认绑定,this绑定在全局对象,输出 “I am in global”
다른 예 보기
var name = "global" function person() { console.log(this.name) // (1) "global" person.name = 'inside' function sayName() { console.log(this.name) // (2) "global" 不是 "inside" } sayName() // 在person函数内部执行sayName函数,this指向的同样是全局的对象 } person()
이 예에서는 person 함수가 전역 범위에서 호출되므로 문장 (1)의 이 내용이 전역 개체에 바인딩됩니다. (브라우저의 창, 노드의 전역) 따라서 문장 (1)의 자연스러운 출력은 물론 "global"인 전역 개체의 이름 속성입니다. sayName 함수는 person 함수 내에서 호출됩니다. 그럼에도 불구하고 문장 (2)의 this는 person 함수가 name 속성을 설정하더라도 여전히 전역 개체를 참조합니다.
이것은 JavaScript에서 가장 일반적인 함수 호출 모드인 기본 바인딩 규칙이며, 이 바인딩 규칙은 네 가지 바인딩 규칙 중 가장 간단합니다. 즉, 바인딩은 도메인에 영향을 미칩니다. .
기본 바인딩의 Strict 모드
Javascript에서 Strict 모드를 사용하면 전역 객체에 바인딩할 수 없습니다. 여전히 첫 번째 예를 취하지만 이번에는 엄격 모드 선언
'use strict' function sayLocation() { console.log(this.atWhere) } var atWhere = "I am in global" sayLocation() // Uncaught TypeError: Cannot read property 'atWhere' of undefined
을 추가합니다. 엄격 모드에서 이를 전역 객체에 바인딩하면 실제로는 정의되지 않음을 알 수 있습니다. 따라서 위 코드는 오류를 보고합니다.
암시적 바인딩
함수가 호출될 때 함수에 소위 "발판"이 있는 경우, 즉 컨텍스트 개체가 있는 경우 암시적 바인딩 규칙에 따라 이를 바인딩합니다. 함수는 이 컨텍스트 객체로 설정됩니다. 위 단락이 충분히 간단하지 않다고 생각되면 코드를 살펴보겠습니다.
function say() { console.log(this.name) } var obj1 = { name: "zxt", say: say } var obj2 = { name: "zxt1", say: say } obj1.say() // zxt obj2.say() // zxt
아주 간단하죠? 위 코드에서 obj1과 obj2는 소위 say 함수의 시작점입니다. 보다 전문적인 용어는 컨텍스트 객체입니다. 이 컨텍스트 객체가 함수에 할당되면 함수 내부의 this는 자연스럽게 이 컨텍스트 객체를 가리킵니다. 이는 매우 일반적인 함수 호출 패턴이기도 합니다.
암시적 바인딩 중에 컨텍스트가 손실됩니다
function say() { console.log(this.name) } var name = "global" var obj = { name: "inside", say: say } var alias = obj.say // 设置一个简写 (1) alias() // 函数调用 输出"global" (2)
여기서 출력이 "global"임을 알 수 있습니다. 왜 위의 예와 다른가요? 이름만?
먼저 위의 (1)문의 코드를 살펴보겠습니다. JavaScript에서는 함수가 객체이므로 객체는 값이 아닌 참조로 전달됩니다. 따라서 문장 (1)의 코드는 단지 alias = obj.say = say, 즉 alias = say입니다. 객체 obj로. 이를 "컨텍스트 상실"이라고 합니다. 마지막으로 alias 함수가 실행되는데 say 함수만 실행되고 "global"이 출력된다.
명시적 바인딩
명시적 바인딩은 이름에서 알 수 있듯이 이를 컨텍스트에 명시적으로 바인딩합니다. JavaScript에서는 Apply 및 Call, Bind라는 세 가지 명시적 바인딩 방법이 제공됩니다. apply와 call의 사용법은 기본적으로 비슷합니다.
apply(obj,[arg1,arg2,arg3,...] 호출된 함수의 매개변수는 다음과 같은 형식으로 제공됩니다. array
call(obj, arg1, arg2, arg3,...) 호출된 함수의 매개변수가
순서대로 주어지며, 바인드 함수가 실행된 후 새로운 함수가 반환됩니다. 다음은 코드 설명입니다. 🎜>
// 不带参数 function speak() { console.log(this.name) } var name = "global" var obj1 = { name: 'obj1' } var obj2 = { name: 'obj2' } speak() // global 等价于speak.call(window) speak.call(window) speak.call(obj1) // obj1 speak.call(obj2) // obj2
// 带参数 function count(num1, num2) { console.log(this.a * num1 + num2) } var obj1 = { a: 2 } var obj2 = { a: 3 } count.call(obj1, 1, 2) // 4 count.apply(obj1, [1, 2]) // 4 count.call(obj2, 1, 2) // 5 count.apply(obj2, [1, 2]) // 5
// 带参数 function count(num1, num2) { console.log(this.a * num1 + num2) } var obj1 = { a: 2 } var bound1 = count.bind(obj1) // 未指定参数 bound1(1, 2) // 4 var bound2 = count.bind(obj1, 1) // 指定了一个参数 bound2(2) // 4 var bound3 = count.bind(obj1, 1, 2) // 指定了两个参数 bound3() //4 var bound4 = count.bind(obj1, 1, 2, 3) // 指定了多余的参数,多余的参数会被忽略 bound4() // 4
function Person(name,age) { this.name = name this.age = age console.log("我也只不过是个普通函数") } Person("zxt",22) // "我也只不过是个普通函数" console.log(name) // "zxt" console.log(age) // 22 var zxt = new Person("zxt",22) // "我也只不过是个普通函数" console.log(zxt.name) // "zxt" console.log(zxt.age) // 22
上面这个例子中,首先定义了一个 Person 函数,既可以普通调用,也可以以构造函数的形式的调用。当普通调用时,则按照正常的函数执行,输出一个字符串。 如果是通过一个new操作符,则构造了一个新的对象。那么,接下来我们再看看两种调用方式, this 分别绑定在了何处首先普通调用时,前面已经介绍过,此时应用默认绑定规则,this绑定在了全局对象上,此时全局对象上会分别增加name 和 age 两个属性。当通过new操作符调用时,函数会返回一个对象,从输出结果上来看 this 对象绑定在了这个返回的对象上。
因此,所谓的new绑定是指通过new操作符来调用函数时,会产生一个新对象,并且会把构造函数内的this绑定到这个对象上。
事实上,在javascript中,使用new来调用函数,会自动执行下面的操作。
1.创建一个全新的对象
2.这个新对象会被执行原型连接
3.这个新对象会绑定到函数调用的this
4.如果函数没有返回其他对象,那么new表达式中的函数调用会自动返回这个新对象
四种绑定的优先级
上面讲述了javascript中四种this绑定规则,这四种绑定规则基本上涵盖了所有函数调用情况。但是如果同时应用了这四种规则中的两种甚至更多,又该是怎么样的一个情况,或者说这四种绑定的优先级顺序又是怎么样的。
首先,很容易理解,默认绑定的优先级是最低的。这是因为只有在无法应用其他this绑定规则的情况下,才会调用默认绑定。那隐式绑定和显式绑定呢?还是上代码吧,代码可从来不会说谎。
function speak() { console.log(this.name) } var obj1 = { name: 'obj1', speak: speak } var obj2 = { name: 'obj2' } obj1.speak() // obj1 (1) obj1.speak.call(obj2) // obj2 (2)
所以在上面代码中,执行了obj1.speak(),speak函数内部的this指向了obj1,因此(1)处代码输出的当然就是obj1,但是当显式绑定了speak函数内的this到obj2上,输出结果就变成了obj2,所有从这个结果可以看出显式绑定的优先级是要高于隐式绑定的。事实上我们可以这么理解obj1.speak.call(obj2)这行代码,obj1.speak只是间接获得了speak函数的引用,这就有点像前面所说的隐式绑定丢失了上下文。好,既然显式绑定的优先级要高于隐式绑定,那么接下来再来比较一下new 绑定和显式绑定。
function foo(something) { this.a = something } var obj1 = {} var bar = foo.bind(obj1) // 返回一个新函数bar,这个新函数内的this指向了obj1 (1) bar(2) // this绑定在了Obj1上,所以obj1.a === 2 console.log(obj1.a) var baz = new bar(3) // 调用new 操作符后,bar函数的this指向了返回的新实例baz (2) console.log(obj1.a) console.log(baz.a)
我们可以看到,在(1)处,bar函数内部的this原本指向的是obj1,但是在(2)处,由于经过了new操作符调用,bar函数内部的this却重新指向了返回的实例,这就可以说明new 绑定的优先级是要高于显式绑定的。
至此,四种绑定规则的优先级排序就已经得出了,分别是
new 绑定 > 显式绑定 > 隐式绑定 > 默认绑定
箭头函数中的this绑定
箭头函数是ES6里一个重要的特性。
箭头函数的this是根据外层的(函数或者全局)作用域来决定的。函数体内的this对象指的是定义时所在的对象,而不是之前介绍的调用时绑定的对象。举一个例子
var a = 1 var foo = () => { console.log(this.a) // 定义在全局对象中,因此this绑定在全局作用域 } var obj = { a: 2 } foo() // 1 ,在全局对象中调用 foo.call(obj) // 1,显示绑定,由obj对象来调用,但根本不影响结果
从上面这个例子看出,箭头函数的 this 强制性的绑定在了箭头函数定义时所在的作用域,而且无法通过显示绑定,如apply,call方法来修改。在来看下面这个例子
// 定义一个构造函数 function Person(name,age) { this.name = name this.age = age this.speak = function (){ console.log(this.name) // 普通函数(非箭头函数),this绑定在调用时的作用域 } this.bornYear = () => { // 本文写于2016年,因此new Date().getFullYear()得到的是2016 // 箭头函数,this绑定在实例内部 console.log(new Date().getFullYear() - this.age) } } } var zxt = new Person("zxt",22) zxt.speak() // "zxt" zxt.bornYear() // 1994 // 到这里应该大家应该都没什么问题 var xiaoMing = { name: "xiaoming", age: 18 // 小明永远18岁 } zxt.speak.call(xiaoMing) // "xiaoming" this绑定的是xiaoMing这个对象 zxt.bornYear.call(xiaoMing) // 1994 而不是 1998,这是因为this永远绑定的是zxt这个实例
因此 ES6 的箭头函数并不会使用四条标准的绑定规则,而是根据当前的词法作用域来决定 this ,具体来说就是,箭头函数会继承 外层函数调用的this绑定 ,而无论外层函数的this绑定到哪里。
小结
以上就是javascript中所有this绑定的情况,在es6之前,前面所说的四种绑定规则可以涵盖任何的函数调用情况,es6标准实施以后,对于函数的扩展新增了箭头函数,与之前不同的是,箭头函数的作用域位于箭头函数定义时所在的作用域。
而对于之前的四种绑定规则来说,掌握每种规则的调用条件就能很好的理解this到底是绑定在了哪个作用域。