1. 看一个现象
-
定义一个函数,采用三种不同的方式对它进行调用,产生了三种不同的结果
function foo() { console.log(this) } // 调用方式一:直接调用 foo() // window // 调用方式二:将foo放到一个对象中调用 var obj = { name: 'kobe', foo: foo } obj.foo() // obj对象 // 调用方式三:通过call/apply调用 foo.call('abc') // String {'abc'} -
通过上面代码可以看出:
- 函数在调用时,JavaScript会默认给this绑定一个值
- this的绑定和定义的位置(编写的位置)没有关系
- this的绑定和调用方式以及调用的位置有关系
- this是在运行时被绑定的
2. 绑定规则
2.1 默认绑定
-
独立函数调用:可以理解成函数没有被绑定到某个对象上进行调用
function foo() { console.log(this) } foo() // windowvar obj = { name: "zhangsan", foo: function() { console.log(this) } } // 函数定义在对象中,但是独立调用 var bar = obj.foo bar() // windowfunction foo() { function bar() { console.log(this) } return bar } var fn = foo() fn() // window -
注意: 严格模式下, 独立调用的函数中的this指向的是
undefined
2.2 隐式绑定
-
通过某个对象发起的函数调用:
object.fn()var obj = { name: "zhangsan", eating: function() { console.log(this.name + "在吃东西") }, running: function() { console.log(obj.name + "在跑步") } } obj.eating() // zhangsan在吃东西 // 案例二 var obj1 = { name: "obj1", foo: function() { console.log(this) } } var obj2 = { name: "obj2", bar: obj1.foo } obj2.bar() // 指向obj2对象
2.3 显式绑定
- 通过某种方式(
call,apply,bind)明确的将this绑到一个对象上// 显式绑定 var obj = { name: "kobe" } function foo() { console.log("foo函数:", this) } // 执行函数, 并且函数中的this指向obj对象 // obj.foo = foo // obj.foo() // 执行函数, 并且强制this就是obj对象 foo.call(obj) // foo函数: {name: 'kobe'} foo.call(123) // Number {123} foo.call("abc") // String {'abc'}
-
apply/call
-
JavaScript所有的函数都可以使用call和apply方法
-
第一个参数是相同的,要求传入一个对象
-
这个对象就是给this准备的
-
在调用这个函数时,会将this绑定到这个传入的对象上
-
-
后面的参数,apply为数组,call为参数列表
function foo(name, age, height) { console.log("foo函数被调用:", this) console.log("打印参数:", name, age, height) } // ()调用 foo("curry", 20, 1.88) // apply调用 // 第一个参数: 绑定this // 第二个参数: 传入额外的实参, 以数组的形式 foo.apply("apply", ["kobe", 30, 1.98]) // call调用 // 第一个参数: 绑定this // 参数列表: 后续的参数以多参数的形式传递, 会作为实参 foo.call("call", "james", 25, 2.05) -
-
bind
-
使用bind方法,bind() 方法创建一个新的绑定函数(bound function,BF)
-
在 bind() 被调用时,这个新函数的 this 被指定为 bind() 的第一个参数,而其余参数将作为新函数的参数,供调用时使用
function foo(name, age, height, address) { console.log("foo:", this) console.log("参数:", name, age, height, address) } var obj = { name: "zhnagsan" } // 需求: 调用foo时, 总是绑定到obj对象身上(不希望obj对象身上有函数) // 1.bind函数的基本使用 // var bar = foo.bind(obj) // bar() // this -> obj // 2.bind函数的其他参数(了解) var bar = foo.bind(obj, "kobe", 18, 1.88) bar("洛杉矶") // this -> obj -
2.4 new绑定
使用new关键字来调用函数是,会执行如下的操作:
- 创建一个全新的对象
- 这个新对象会被执行prototype连接
- 这个新对象会绑定到函数调用的this上(this的绑定在这个步骤完成)
- 如果函数没有返回其他对象,表达式会返回这个新对象
function foo() {
this.name = "zhangsan"
console.log(this)
}
new foo() // 指向foo
function Person(name, age) {
this.name = name
this.age = age
}
var p = new Person("kobe", 30)
console.log(p.name, p.age) // kobe 30
2.5 内置函数的规则(根据经验或者查阅官方文档)
- 定时器
- 按钮的点击
- 一些API
// 1.定时器
setTimeout(function() {
console.log("定时器函数:", this) // window
}, 1000)
// 2.按钮的点击监听
var btnEl = document.querySelector("button")
btnEl.onclick = function() {
console.log("btn的点击:", this) // button的dom元素
}
btnEl.addEventListener("click", function() {
console.log("btn的点击:", this) // button的dom元素
})
// 3.forEach 默认指向 window
var names = ["abc", "cba", "nba"]
names.forEach(function(item) {
console.log("forEach:", this) // String {'aaaa'}
}, "aaaa")
2.6 规则的优先级
- 从高到低
- new
- bind
- apply/call
- 隐式绑定
- 默认绑定
function foo() {
console.log("foo:", this)
}
// 比较优先级:
// 1.显式绑定绑定的优先级高于隐式绑定
// 1.1.测试一:apply高于默认绑定
// var obj = { foo: foo }
// obj.foo.apply("abc") String {'abc'}
// obj.foo.call("abc") String {'abc'}
// 1.2.测试二:bind高于默认绑定
// var bar = foo.bind("aaa")
// var obj = {
// name: "kobe",
// baz: bar
// }
// obj.baz() // String {'aaa'}
// 2.new绑定优先级高于隐式绑定
// var obj = {
// name: "kobe",
// foo: function() {
// console.log("foo:", this)
// console.log("foo:", this === obj)
// }
// }
// new obj.foo() // foo {}
// 3.new/显式
// 3.1. new不可以和apply/call一起使用
// 3.2. new优先级高于bind
// function foo() {
// console.log("foo:", this)
// }
// var bindFn = foo.bind("aaa")
// new bindFn() // foo {}
// 4.bind/apply优先级
// bind优先级高于apply/call
function foo() {
console.log("foo:", this)
}
var bindFn = foo.bind("aaa")
bindFn.call("bbb") // String {'aaa'}
3. 规则之外的情况
- 在显示绑定中,传入
undefined/null,这个显示绑定会被忽略,使用默认绑定规则- 注意:严格模式下,会绑定基本值类型
function foo() {
console.log(this)
}
var obj = {
name: "kobe"
}
foo.call(obj) // obj对象
foo.call(null) // window
foo.call(undefined) // window
var bar = foo.bind(null)
bar() // window
- 间接函数引用:创建一个函数的
间接引用,使用默认绑定规则
var obj1 = {
name: "obj1",
foo: function() {
console.log("foo:", this)
}
}
var obj2 = {
name: "obj2"
};
// obj2.foo = obj1.foo
// obj2.foo() // obj2
// 赋值(obj2.foo = obj1.foo)的结果是obj1.foo函数
(obj2.foo = obj1.foo)() // window
- ES6箭头函数:箭头函数不使用this的四种标准规则(也就是不绑定this),而是根据外层作用域来决定
this- 箭头函数的用法:JavaScript-箭头函数
4. 面试题详解
4.1 面试题一
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
(b = person.sayName)(); // 术语: 间接函数引用 window
}
sayName();
4.2 面试题二
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 () {
// console.log(this) // 第一个表达式this -> person1
// console.log(this) // 第二个表达式this -> person2
// console.log(this) // 第三个表达式this -> person1
return () => {
console.log(this.name)
}
}
}
var person2 = { name: 'person2' }
person1.foo1(); // 隐式绑定 person1
person1.foo1.call(person2); // 显式绑定 person2
person1.foo2(); // 箭头函数 查找上层 window
person1.foo2.call(person2); // call无效 查找上层 window
person1.foo3()(); // 获取到foo3,但是调用位置是全局作用于下,属于默认绑定 window
person1.foo3.call(person2)(); // foo3显示绑定到person2中,但是拿到的返回函数依然是在全局下调用 window
person1.foo3().call(person2); // 拿到foo3返回的函数,显式绑定person2 person2
person1.foo4()(); // 查找上层,上层隐式绑定 person1
person1.foo4.call(person2)(); // 查找上层,上层显式调用person2 person2
person1.foo4().call(person2); // 查找上层,上层隐式绑定person1,此处call不绑定this person1
4.3 面试题三
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.foo1.call(person2) // 显式绑定 person2
person1.foo2() // 箭头函数查找上层 person1
person1.foo2.call(person2) // 箭头函数查找上层,call不绑定this person1
person1.foo3()() // 独立函数调用 window
person1.foo3.call(person2)() // 拿到foo3返回的函数,显示绑定person2后在全局调用 window
person1.foo3().call(person2) // 拿到foo3返回的函数,显式绑定person2调用 person2
person1.foo4()() // 返回箭头函数,和自身绑定没有关系,查找上层 person1
person1.foo4.call(person2)() // foo4调用时绑定person2,返回的函数是箭头函数,调用时,找到了上层绑定的person2 person2
person1.foo4().call(person2) // foo4调用返回的箭头函数,和call调用没有关系,找到上层的person1 person1
4.4 面试题四
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')
// obj.foo1()返回一个函数foo1
person1.obj.foo1()() // 返回的foo1函数独立调用 window
person1.obj.foo1.call(person2)() // 返回的foo1函数显式绑定person2后独立调用 window
person1.obj.foo1().call(person2) // 返回的foo1函数显式调用person2 person2
// obj.foo2()返回一个箭头函数
person1.obj.foo2()() // 箭头函数调用 查找上层 obj
person1.obj.foo2.call(person2)() // foo2执行时显式绑定person2返回的箭头函数调用,查找上层 person2
person1.obj.foo2().call(person2) // 箭头函数显式绑定person2调用,查找上层 obj