目标
- 定义什么是this关键字
- 理解四种常用的方法去找出关键字this代表什么
- 尽可能的在语句中不要使用this
'this' 是什么
- JavaScript中的保留关键字
- 通常由函数的调用方式决定(我们称之为执行上下文)
- 可以用四种规则来确定(全局(默认绑定), 对象/隐式, 显式, new)
1. 全局环境/默认绑定(global context)
- 当“this”不在已声明对象的内部时, this 会指向window
console.log(this) // Window
复制代码
function whatIsThis () {
return this;
}
whatIsThis()
复制代码
function variablesInThis(){this.person = 'elie'}
variablesInThis() //this ---> window
console.log(person) // elie
复制代码
- 严格模式下面
"use strict"
console.log(this); // window
function whatIsThis(){
return this;
}
whatIsThis(); // undefined
function variablesInThis(){
this.person = "Elie";
}
variablesInThis(); // TypeError, can't set person on undefined!
复制代码
2. 隐式绑定/对象 (Implicit/Object)
- 当“this”在已声明对象的内部时, this会被绑定到对象,但是嵌套对象中this会丢失! 会丢失!
var person = {
firstName:'Elie',
sayHi: function() {
return "Hi" + this.firstName
},
determineContext: function() {
return this === person
}
}
// 测试
person.sayHi() // "Hi Elie"
person.determineContext(); // true
复制代码
思考 这里的关键字“this”应该指什么?
var person = {
firstName: "张三",
determineContext: this
}
person.determineContext; // window
复制代码
函数运行时定义一个关键字“this”!这里没有运行一个函数来创建关键字'this'的新值,所以'this'的值仍然是窗口!
- 嵌套对象 (Nested Objects) 当我们有一个嵌套对象的时候,this绑定会如何
var person = {
firstName: '张三',
sayHi: function () {
return 'Hi' + this.firstName
},
determineContext: function() {
return this === person
},
dog: {
sayHello: function() {
return 'Hello' + this.firstName
},
determineContext: function() {
return this === person
}
}
}
person.sayHi(); // "Hi张三"
person.determineContext(); // true
// 嵌套对象中的this
person.dog.sayHello(); // "Helloundefined"
person.dog.determineContext(); // false
复制代码
嵌套对象中的this 丢失了!!! 丢失了!!!
3. 显式绑定 (Explicit Binding)
显式绑定一般通过使用 call apply 和 bind ,三种方法的区别
方法名 参数 立即调用?
Call thisArg, a, b, c, d , ... YES
Apply thisArg, [a,b,c,d, ...] YES
Bind thisArg, a, b, c, d , ... NO
复制代码
- 显式绑定call 修复了嵌套对象丢失this问题
var person = {
firstName: '张三',
sayHi: function () {
return 'Hi' + this.firstName
},
determineContext: function() {
return this === person
},
dog: {
sayHello: function() {
return 'Hello' + this.firstName
},
determineContext: function() {
return this === person
}
}
}
// 测试
person.dog.sayHello.call(person); // "Hello张三"
person.dog.determineContext.call(person); // true
复制代码
一个普通的例子
var javabook = {
name:'java book',
showBook: function() {
return 'this book"s name is ' + this.name
}
}
var phpbook = {
name:'php book',
showBook: function() {
return 'this book"s name is ' + this.name
}
}
javabook.showBook() // "this book"s name is php book"
phpbook.showBook() // "this book"s name is php book"
// 通过call 我们可以让showBook() 成为大家的
function showBook() {
return 'this book"s name is ' + this.name
}
var cssbook = {
name:'css book'
}
var htmlbook = {
name: 'html book'
}
// test
showBook.call(cssbook)
// "this book"s name is css book"
showBook.call(htmlbook)
// "this book"s name is html book"
复制代码
再来一个call的例子
- 假设我们要选择页面上所有的“divs”
var divs = document.getElementsByTagName('divs') //类数组对象
复制代码
- 然后我们想找到含有"hello"的div, 我们想使用filter
divs.filter // undefined 它是一个类似对象的数组,所以过滤器不会起作用。
复制代码
- 解决办法 使用数组slice方法
Array.prototype.slice.call(arguments),你也可以简单的使用 [].slice.call(arguments) 来代替 slice 不修改原数组,只会返回一个浅复制了原数组中的元素的一个新数组。原数组的元素会按照下述规则拷贝:
- 如果该元素是个对象引用 (不是实际的对象),slice 会拷贝这个对象引用到新的数组里。两个对象引用都引用了同一个对象。如果被引用的对象发生改变,则新的和原来的数组中的这个元素也会发生改变。
- 对于字符串、数字及布尔值来说(不是 String、Number 或者 Boolean 对象),slice 会拷贝这些值到新的数组里。在别的数组里修改这些字符串或数字或是布尔值,将不会影响另一个数组。
var divsArray = [].slice.call(divs);
divsArray.filter(function(val){
return val.innerText === 'Hello';
})
复制代码
- Apply? 看代码
function showBook() {
return 'this book"s name is ' + this.name
}
var cssbook = {
name:'css book'
}
var htmlbook = {
name: 'html book'
}
// test
showBook.call(cssbook) // "this book"s name is css book"
showBook.apply(cssbook) // "this book"s name is css book"
// 这样用看起来好像没有区别
复制代码
似乎相同的…但是如果我们开始添加参数会发生什么呢?
function addNumbers(a,b,c,d){
return this.name + "计算: " + (a+b+c+d);
}
var cssbook = {
name:'css book'
}
var htmlbook = {
name: 'html book'
}
addNumbers.call(cssbook,1,1,1,1)
// "css book计算: 4"
addNumbers.apply(cssbook,1,1,1,1)
// VM273:1 Uncaught TypeError: CreateListFromArrayLike called on non-object
// at <anonymous>:1:12
addNumbers.apply(cssbook,[1,1,1,1])
// "css book计算: 4"
复制代码
所以 我们什么时候使用apply呢 当一个函数不接受一个数组时,apply将为我们在一个数组中展开值!
var nums = [5,7,1,4,2]
Math.max(nums) // NaN
Math.max.apply(this, nums) //7
复制代码
function sumValues(a,b,c){
return a+b+c;
}
var values = [4,1,2];
sumValues(values); // "4,1,2undefinedundefined"
sumValues.apply(this,[4,1,2]); // 7
复制代码
- 再来看看bind
参数的工作方式类似于call,但是bind返回一个具有“this”绑定上下文的函数! 不会立刻执行需要调用
function addNumbers(a,b,c,d){
return this.name + " just calculated " + (a+b+c+d);
}
var htmlbook = {
name: 'html book'
}
// test
var htmlbookCalc = addNumbers.bind(htmlbook, 1,2,3,4) // f(){}
htmlbookCalc() // "html book just calculated 10"
复制代码
bind的使用
在我们不想马上执行的函数中!,通常我们会失去“this”的上下文
var colt = {
firstName: "Colt",
sayHi: function(){
setTimeout(function(){
console.log("Hi " + this.firstName);
},1000);
}
}
// test
colt.sayHi(); // Hi undefined (1000 milliseconds later)
复制代码
丢掉了 以前的解决方法 利用作用域保存this
var colt = {
firstName: "Colt",
sayHi: function(){
var that = this
setTimeout(function(){
console.log("Hi " + that.firstName);
},1000);
}
}
// test
colt.sayHi(); // Hi Colt
复制代码
使用bind来设置“this”的正确上下文
var colt = {
firstName: "Colt",
sayHi: function(){
setTimeout(function(){
console.log("Hi " + this.firstName);
}.bind(this),1000);
}
}
colt.sayHi(); // Hi Colt
复制代码
4. new绑定
我们可以使用“new”关键字来设置关键字“this”的上下文
function Person(firstName, lastName){
this.firstName = firstName;
this.lastName = lastName;
}
// test
var Lily = new Person("Lily", "Study")
Lily.firstName; // "Lily"
Lily.lastName; // "Study"
复制代码
总结
- 关键字“this”是JavaScript中的保留关键字,其值在执行时确定
- 它可以使用全局上下文、对象绑定、显式绑定或new关键字来设置
- 当在函数的全局上下文中设置时,它要么是全局对象(在浏览器中是窗口),要么是未定义的对象(如果我们使用严格模式)
- 要显式地设置关键字“this”的值,我们使用call、apply或bind
- 我们还可以使用“new”关键字来设置“this”的上下文