文章目录
1. JavaScript 高级-this指向
1.1 this的绑定规则
function foo() {
console.log('foo函数:', this)
}
// 1.方式一:直接调用/默认调用
foo()
// 2.方式二:通过对象调起
let obj = {
age: 18,
}
obj.aaa = foo
obj.aaa()
采用不同的调用方式,this产生了不同的效果?
- 函数在调用时,JavaScript会默认给this绑定一个值
- this的绑定和定义的位置(编写的位置)没有关系
- this的绑定和调用方式以及调用的位置有关系
- this是在运行时被绑定的
this的绑定规则:默认绑定;隐式绑定;显示绑定;new绑定
-
默认绑定。只要是函数独立的直接调用,this就指向window(形式再复杂,也只看是不是独立函数调用)。严格模式下,独立调用的函数中的this指向的是undefined。
案例一
function foo(func) { func() } let obj = { name: 'why', bar: function () { console.log(this) }, } // obj.bar作为参数传递到foo函数中,此时foo函数内部调用func(),func()是普通函数直接调用 foo(obj.bar) // window obj.bar() // obj对象
案例二
function test1() { console.log(this) test2() } function test2() { console.log(this) test3() } function test3() { console.log(this) } test1() // 打印了3个window
-
隐式绑定。通过某个对象进行调用,是由某个对象发起的函数调用。
function foo() { console.log(this) } let obj = { name: 'jiaqian', } obj.aaa = foo obj.aaa()
案例二
function foo() { console.log(this) } let obj1 = { name: 'jiaqian', foo: foo, } let obj2 = { obj1: obj1, } obj2.obj1.foo() // 打印obj1,因为foo是通过obj1对象来调用的
案例三
function foo() { console.log(this) } let obj1 = { name: 'jiaqian', foo: foo, } let bar = obj1.foo bar() // window
-
显式绑定。显示出this绑定哪个对象。
隐式绑定有一个前提条件: 必须在调用的对象内部有一个对函数的引用(比如一个属性);如果没有这样的引用,在进行调用时,会报找不到该函数的错误; 正是通过这个引用,间接的将this绑定到了这个对象上;
如果此不需要在对象内部包含这个函数的引用,同时在这个对象上进行强制调用
JavaScript所有的函数都可以使用call和apply方法
-
第一个参数是相同的,要求传入一个对象,给this准备的,在调用这个函数时,会将this绑定到传入的对象上
-
后面的参数,apply是数组,call为参数列表
func.apply(thisArg,[argsArray])
function.call(thisArg,arg1,arg2,...)
因为明确绑定了this指向的对象,所以称之为显式绑定
理解:函数不是经常要传参嘛,之前直接直接调用直接传参,现在的apply和call方法进行调用,此时传参的方式不同是apply和call方法的区别所在
function foo(name, age, height) { console.log(this) console.log('foo函数打印', name, age, height) } foo('kobe', 18, 1.99) let obj = {} // apply第一个参数:绑定this // 第二个参数:传入额外的实参,以数组的形式 foo.apply(obj, ['kobe', 18, 1.99]) // call第一个参数:绑定this // 第二个参数:参数列表,后续的参数以多参数的形式,会作为实参 foo.call(obj, 'james', 35, 2.11)
-
-
new绑定:JavaScript中的函数可以当做一个类的构造函数来使用
- 使用new关键字来调用函数首先会创建一个新的对象
- 这个新对象会被执行prototype连接
- 这个新对象会绑定到函数调用的this上(this的绑定在这个步骤完成)
- 执行函数的内部代码(函数体代码)
- 如果函数没有返回其他对象,表达式会返回这个新对象
1.2 apply/call/bind
// bind
function foo() {
console.log('fooL:', this)
}
var obj = {
age: 18,
}
// 之前:通过apply方法将foo绑定在obj上调用
// 需求:调用foo时,总是绑定到obj对象身上,而且obj对象上没有函数
foo.apply(obj)
foo.apply(obj)
foo.apply(obj)
foo.apply(obj)
foo.apply(obj)
foo.apply(obj)
// 如果只是这样绑定在obj上,每次都用apply代码重复
var bar = foo.bind(obj)
bar() // 因为此时foo的this已经指向obj,即便写成默认绑定(存在优先级),this指向的对象仍为obj
console.log(typeof bar)
如何让一个函数总是显示的绑定到一个对象上?使用bind方法,bind() 方法创建一个新的绑定函数(bound function,BF),绑定函数是一个 exotic function object(怪异函数对象,ECMAScript 2015 中的术语)。在 bind() 被调用时,这个新函数的 this 被指定为 bind() 的第一个参数,而其余参数将作为新函数的参数,供调用时使用。
内置函数的调用的this绑定:内置函数(第三方库比如Vue),根据一些经验
1.3 this绑定优先级
- 默认规则的优先级最低
- 显式绑定优先级高于隐式绑定
- new绑定优先级高于隐式绑定
- new绑定优先级高于bind: new绑定和call、apply是不允许同时使用的,所以不存在谁的优先级更高;new绑定可以和bind一起使用,new绑定优先级更高;bind优先级比apply/call高
// function foo() {
// console.log('foo:', this)
// }
// let obj = {
// foo: foo,
// }
// 比较优先级
// 1.显示绑定的优先级高于隐式绑定
// obj.foo.apply('abc')
// 2.bind绑定高于隐式绑定
// foo.bind(obj)
// obj.foo()
// 3.new绑定优先级高于隐式绑定
// var obj = {
// name: 'Why',
// foo: function () {
// console.log('foo:', this)
// },
// }
// obj.foo()
// new obj.foo()
// 4.1new不可以和apply/call一起使用
// 4.2new优先级高于bind
// function Foo() {
// console.log('Foo:', this)
// }
// let Func1 = Foo.bind('abc')
// new Func1()
// 5.bind优先级高于apply/call
function Foo1() {
console.log('Foo1', this)
}
Foo1.bind('aaa').call('bbb')
1.4 绑定之外的情况
-
忽略显式绑定:如果在显式绑定中,传入一个null或者undefined,显式绑定会被忽略,使用默认规则
function foo() { console.log('foo', this) } foo.apply('abc') foo.apply(null) // window foo.apply(undefined) // window
-
间接函数引用:创建一个函数的间接引用,这种情况使用默认绑定规则
function foo() { console.log(this) } let obj1 = { name: 'obj1', foo: foo, } let obj2 = { name: 'obj2', } ;(obj2.foo = obj1.foo)()
1.5 箭头函数的使用
箭头函数是ES6之后增加的一种编写函数的方法,并且它比函数表达式要更加简洁
- 箭头函数不会绑定this、arguments属性
- 箭头函数不能作为构造函数来使用(不能和new一起来使用,会抛出错误)
let names = ['kobe', 'jiangping', 'jiangfei']
names.forEach((item, index, arr) => {
console.log(item, index, arr)
})
setTimeout(() => {
console.log('setTimeout')
}, 3000)
箭头函数的编写优化
-
如果只有一个参数()可以省略
-
如果函数执行体中只有一行代码, 那么可以省略大括号
names.forEach((item, index, arr) => console.log(item, index, arr))
并且这行代码的返回值会作为整个函数的返回值
let foo1 = item => item * 2 console.log(foo1(2))
-
如果函数执行体只有返回一个对象, 那么需要给这个对象加上(),此举是为了对象的{}和箭头函数的{}产生混淆设置的规则
let foo1 = () => ({ name: 'jiangfei' }) console.log(foo1())
// 箭头函数实现nums所有偶数平方的和
var nums = [20, 30, 11, 15, 111]
let result = nums
.filter(item => item % 2 === 0)
.map(item => item * item)
.reduce((preValue, item) => preValue + item, 0)
console.log(result)
this规则之外 – ES6箭头函数:箭头函数不绑定this,而是根据外层作用域来决定this。箭头函数的{ }算是作用域,但是其自身的作用域里面没有this。箭头函数的this
是在定义时绑定的,而不是在调用时绑定的。
let bar = () => {
console.log('bar', this)
}
// 即使通过apply调用,也是没有this
bar.apply('aaaa') // bar window
// this的查找规则
let obj = {
name: 'obj',
foo: function () {
let bar = () => {
console.log('bar', this)
}
return bar
},
}
let foo1 = obj.foo()
foo1.apply('aaa') // bar object
箭头函数的应用
模拟网络请求
// 网络请求的工具函数
function request(url, callbackFn) {
let results = ['abc', 'kobe', 'james']
callbackFn(results)
}
// 实际操作的位置(业务)
let obj = {
name: [],
network: function () {
request('/names', function (res) {
this.names.concat(res)
})
},
}
obj.network()
上述案例中:由于在这里使用的函数是普通函数不是箭头函数,普通的函数里面绑定了this,这个this的指向取决于如何调用,工具函数中的回调函数的调用方式为默认调用,所以函数network在回调调用时this指向了window而不是我们希望的obj
早期没有箭头函数时:引入一个变量去改变this的指向
// 网络请求的工具函数
function request(url, callbackFn) {
var results = ['abc', 'kobe', 'james']
callbackFn(results)
}
// 实际操作的位置(业务)
var obj = {
names: [],
network: function () {
var _this = this
request('/names', function (res) {
_this.names = _this.names.concat(res)
})
},
}
obj.network()
console.log(obj.names) // ['abc', 'kobe', 'james']
有了箭头函数后,因为箭头函数不绑定this,而是根据外层作用域找this,这里外层作用域就是network里,network函数中通过对象调用,所以network的this指向对象,所以箭头函数的this指向对象
function request(url, callbackFn) {
var results = ['abc', 'kobe', 'james']
callbackFn(results)
}
// 实际操作的位置(业务)
var obj = {
names: [],
network: function () {
request('/names', res => {
this.names = this.names.concat(res)
})
},
}
obj.network()
console.log(obj.names)
1.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,同上,括号不会改变this的绑定,它们仅用于控制表达式的优先级
;(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 箭头函数的this看外层作用域的this,即全局对象
person1.foo2.call(person2) // window 同上,箭头函数自身不绑定this,调用call方法也没办法将this绑定到传入的对象上
person1.foo3()() // window person1.foo3()返回一个函数,最后面的()表示默认调用了该函数
/* window 首先person1.foo3.call(person2)改变了this指向,this指向了person2
,foo3方法执行后返回了一个匿名函数,此时直接调用了这个返回的匿名函数 */
person1.foo3.call(person2)() // window
person1.foo3().call(person2) // person2 foo3方法返回了一个匿名函数,该匿名函数经过call方法this指向了person2
person1.foo4()() // person1 foo4返回一个箭头函数,箭头函数的this绑定到foo4的this,即person1
person1.foo4.call(person2)() // person2 foo4方法的this被call改变为person2,返回的箭头函数的this绑定到foo4的this,即person2
person1.foo4().call(person2) // person1 先调用foo4返回一个箭头函数,箭头函数的this绑定到foo4的this,即person1,call无法改变箭头函数的this。
面试题三
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调用
person1.foo1.call(person2) // person2
person1.foo2() // person1 箭头函数foo2的this在Person构造函数执行时被绑定到了新创建的对象person1
person1.foo2.call(person2) // person1
person1.foo3()() // window
person1.foo3.call(person2)() // window person1.foo3.call(person2)返回的普通函数直接调用
person1.foo3().call(person2) // person2
person1.foo4()() // person1
person1.foo4.call(person2)() // person2 foo4的this被call方法绑定到person2,返回的箭头函数的this绑定到foo4的this
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()() // obj
person1.obj.foo2.call(person2)() // person2
person1.obj.foo2().call(person2) // obj