JS函数的this指向
this作用
JavaScript中的this和常见的面向对象语言中的this区别:
- 常见面向对象的编程语言中,比如Java、C++、Swift、Dart等等一系列语言中,this通常只会出现在类的方法中。
- 也就是你需要有一个类,类中的方法(特别是实例方法)中,this代表的是当前调用对象。
- 但是JavaScript中的this更加灵活,无论是它出现的位置还是它代表的含义
JavaScript
// 没有this,编写代码不方便
var obj1 = {
name :"why",
eating: function() {
console.log(obj1.name,+ "在吃东西")
},
running: function() {
console. log(obj1.name + "在跑步")
}
}
var info= {
name :"why",
eating: function() {
console.log(info.name + "在吃东西")
},
running: function() {
console. log(info.name + "在跑步")
}
}
this指向
在全局作用域下
- 浏览器: window (global0bject)
- Node环境:{} (把每个文件当成module -> 加载->编译->放到一个函数compiledWrapper ->执行这个函数.call({}))
this通常在函数中使用
动态绑定:this在函数执行时才会确定
函数执行上下文(函数的调用栈、AO对象、this)
- 函数在调用时,JavaScript会默认给this绑定一个值;
- this的绑定和定义的位置(编写的位置)没有关系;
- this的绑定和调用方式以及调用的位置有关系;
- this是在运行时被绑定的;
JavaScript
// this指向什么,跟函数所处的位置是没有关系的
// 跟函数被调用的方式是有关系
function foo() {
console.log(this)
}
// 1. 直接调用这个函数
foo()
// 2. 创建一个对象,对象中的函数指向foo
var obj = {
name: 'why',
foo:foo
}
obj. foo()
// 3. call/apply调用
foo.apply("abc")
this绑定规则
- 默认绑定:独立函数调用
没有调用主题
- 隐式绑定:object.fn()
前提:
- 必须在调用的对象内部有一个对函数的引用(比如一个属性);
- 如果没有这样的引用,在进行调用时,会报找不到该函数的错误;
- 正是通过这个引用,间接的将this绑定到了这个对象上;
object对象会被js引擎绑定到fn函数的中this里面
在其调用位置中,是通过某个对象发起的函数调用。
- 显示绑定:call、apply、bind
call、apply
JavaScript所有的函数都可以使用call和apply方法(这个和Prototype有关)。
第一个参数都要求是一个对象(给this准备),后面的参数,apply为数组,call为参数列表;
在调用这个函数时,会将this绑定到这个传入的对象上
JavaScript
function sum(num1, num2, num3) {
console.log(num1 + num2 + num3, this)
}
sum.call("call", 20, 30, 40)
sum.apply("apply", [20, 30, 40])
foo直接调用和 call/apply 调用的不同在于this绑定的不同
foo直接调用指向的是全局对象(window);call/apply是可以指定this的绑定对象
bind
JavaScript
function foo() {
console.log(this)
}
// 默认绑定和显示绑定bind冲突: 优先级(显示绑定)
var newFoo = foo.bind("aaa")
newFoo()
- new绑定
- 创建一个全新的对象;
- 这个新对象会被执行prototype连接;
- 这个新对象会绑定到函数调用的this上(this的绑定在这个步骤完成);
- 如果函数没有返回其他对象,表达式会返回这个新对象;
JavaScript
// 我们通过一个new关键字调用一个函数时(构造器), 这个时候this是在调用这个构造器时创建出来的对象
// this = 创建出来的对象
// 这个绑定过程就是new 绑定
function Person(name, age) {
this.name = name
this.age = age
}
var p1 = new Person("why", 18)
console.log(p1.name, p1.age) // this->Peson {}
var p2 = new Person("kobe", 30)
console.log(p2.name, p2.age)
this其他补充
JavaScript
setTimeout(function() {
console.log(this) // window,内部是**独立函数调用**
}, 2000) // 默认绑定
JavaScript
const boxDiv = document.querySelector('.box')
boxDiv.onclick = function() {
console.log(this) // boxDiv元素
} // 隐式绑定
boxDiv.addEventListener('click', function() {
console.log(this) // boxDiv元素
}) // 显示绑定
JavaScript
// 3.数组.forEach/map/filter/find,可以传两个参数(函数、this指向)
var names = ["abc", "cba", "nba"]
names.forEach(function(item) {
console.log(item, this)
}) // window
names.map(function(item) {
console.log(item, this)
}, "cba") // "cba"
规则优先级
1.默认规则的优先级最低
2.显示绑定优先级高于隐式绑定
JavaScript
// 1.call/apply的显示绑定高于隐式绑定
var obj = {
name: "obj",
foo: function() {
console.log(this)
}
}
obj.foo.apply('abc') // 'abc'
obj.foo.call('abc') // 'abc'
// 2.bind的优先级高于隐式绑定
function foo() {
console.log(this)
}
var obj = {
name: "obj",
foo: foo.bind("aaa")
}
obj.foo() // "aaa"
3.new绑定优先级高于隐式绑定
JavaScript
var obj = {
name: "obj",
foo: function() {
console.log(this)
}
}
// new的优先级高于隐式绑定
var f = new obj.foo() // foo {}
4.new绑定优先级高于bind
JavaScript
// 结论: new关键字不能和apply/call一起来使用
// new的优先级高于bind
function foo() {
console.log(this)
}
var bar = foo.bind("aaa")
var obj = new bar() // foo {}
// new绑定 > 显示绑定(apply/call/bind) > 隐式绑定(obj.foo()) > 默认绑定(独立函数调用)
this规则之外
- 忽略显示绑定
JavaScript
function foo() {
console.log(this)
}
foo.apply("abc") // "abc"
foo.apply({}) // {}
// apply/call/bind: 当传入null/undefined时, 自动将this绑定成全局对象
foo.apply(null) // window
foo.apply(undefined) // window
var bar = foo.bind(null)
bar() // window
- 间接函数引用
JavaScript
// 争论: 代码规范 ;
var obj1 = {
name: "obj1",
foo: function() {
console.log(this)
}
}
var obj2 = {
name: "obj2"
}; // 不加";"词法分析会认为代码还未结束,与下面的(obj2.bar = obj1.foo)()一起
// obj2.bar = obj1.foo
// obj2.bar() //obj2
(obj2.bar = obj1.foo)() //独立函数调用
- ES6箭头函数(arrow function)
JavaScript
// 1. 编写箭头函数
// 1> (): 参数
// 2> =>: 箭头
// 3> {}: 函数的执行体
var foo = (num1, num2, num3) => {
console.log(num1, num2, num3)
}
function bar(num1, num2, num3) {
}
// 高阶函数在使用时, 也可以传入箭头函数
var nums = [10, 20, 45, 78]
nums.forEach((item, index, arr) => {})
// 箭头函数有一些常见的简写:
// 简写一: 如果参数只有一个, ()可以省略
nums.forEach(item => {
console.log(item)
})
// 简写二: 如果函数执行体只有一行代码, 那么{}也可以省略
// 强调: 并且它会默认将这行代码的执行结果作为返回值,不用return
nums.forEach(item => console.log(item))
var newNums = nums.filter(item => item % 2 === 0)
console.log(newNums) // [10,20,78]
// filter/map/reduce
var result = nums.filter(item => item % 2 === 0)
.map(item => item * 100)
.reduce((preValue, item) => preValue + item)
console.log(result) // 10800
// 简写三: 如果一个箭头函数, 只有一行代码, 并且返回一个对象, 这个时候如何编写简写
// var bar = () => {
// return { name: "why", age: 18 }
// }
var bar = () => ({ name: "why", age: 18 })
箭头函数不使用this的四种标准规则(也就是不绑定this),而是根据外层作用域来决定this。
JavaScript
// 1.测试箭头函数中this指向
// var name = "why"
// var foo = () => {
// console.log(this)
// }
// foo() //window
// var obj = {foo: foo}
// obj.foo() //window
// foo.call("abc") // window
// 2.应用场景
var obj = {
data: [],
getData: function() {
// 发送网络请求, 将结果放到上面data属性中
// 在箭头函数之前的解决方案
// var _this = this
// setTimeout(function() {
// var result = ["abc", "cba", "nba"]
// _this.data = result
// }, 2000);
// 箭头函数之后
// 箭头函数没有this,就会向上层作用域找,上层function隐式绑定obj
setTimeout(() => {
var result = ["abc", "cba", "nba"]
this.data = result
}, 2000);
}
}
obj.getData()