一、初识this
1、this的定义是什么?它的复杂之处体现在哪里?
- 【this的定义】this是JavaScript中的一个关键字,this会在执行上下文中绑定一个对象。
- 【复杂之处】this是根据什么条件绑定的对象呢?在不同的执行条件下会绑定不同的对象,这也是让人捉摸不定的地方。
2、对比其他面向对象语言中的this,JavaScript中的this有什么不同?
- 在常见的面向对象的编程语言中,比如Java、C++、Swift、Dart等等一些列语言中,this通常只会出现在
类的方法中
。 - 也就是你需要有一个类,类中的方法(特别是实例方法)中,this代表的是当前调用对象。
- 但是JavaScript中的this更加灵活,无论是它出现的位置还是它代表的含义。
3、既然this这么复杂,我们不用this会有什么影响吗?
- 不使用this的代码
var obj = {
name: "why",
running: function() {
console.log(obj.name + " running");
},
eating: function() {
console.log(obj.name + " eating");
},
studying: function() {
console.log(obj.name + " studying");
}
}
- 这么做有一个很大的弊端,如果我将obj名称换成了info,那么方法中的obj都需要换成info
- 我们都会使用this来进行优化:
var obj = {
name: "why",
running: function() {
console.log(this.name + " running");
},
eating: function() {
console.log(this.name + " eating");
},
studying: function() {
console.log(this.name + " studying");
}
}
- this让我们进行一些API设计时,代码更加的简洁和易于复用。
4、在全局作用域下,浏览器中的this指向什么?
- 所以,在全局作用域下,我们可以认为this就是指向的window
- 但是,开发中很少直接在全局作用域下去使用this,通常都是在函数中使用。
console.log(this); // window
var name = "why";
console.log(this.name); // why
console.log(window.name); // why
5、思考下面代码的打印?
// 定义一个函数
function foo() {
console.log(this);
}
// 1.调用方式一: 直接调用
foo();
// 2.调用方式二: 将foo放到一个对象中,再调用
var obj = {
name: "why",
foo: foo
}
obj.foo()
// 3.调用方式三: 通过call/apply调用
foo.call("abc");
- 【方式一】打印 【window】
- 【方式二】打印 【obj对象】
- 【方式三】打印 【String {"abc"}对象】
6、上面例子可以给我们什么样的启示呢?
- ①函数在调用,JavaScript会默认给this绑定一个值;
- ②this的绑定和定义的位置(编码位置)没有关系;
- ③this的绑定和调用方式以及调用的位置有关系;
- ④this是在运行时被绑定的;
二、this绑定规则?
我们现在已经知道this无非就是在函数调用时被绑定的一个对象,我们就需要知道它在不同的场景下的绑定规则即可。
1、默认绑定是什么?
-
【是什么】独立函数调用,我们可以理解成函数没有被绑定到某个对象上进行调用;
-
案例一
function foo() {
console.log(this);
}
foo();
- 案例二
// 2.案例二:
function test1() {
console.log(this);
test2();
}
function test2() {
console.log(this);
test3()
}
function test3() {
console.log(this);
}
test1();
- 案例三
function foo(func) {
func()
}
function bar() {
console.log(this);
}
foo(bar);
- 案例四
function foo(func) {
func()
}
var obj = {
name: "why",
bar: function() {
console.log(this);
}
}
foo(obj.bar);
- 上面案例一、二、三、四中打印结果都是 【window】,你能理解吗?
2、隐式绑定是什么?
-
【是什么】就是在函数调用的位置中,是通过某个对象发起的函数调用。
-
案例一
function foo() {
console.log(this); // obj对象
}
var obj1 = {
name: "why",
foo: foo
}
obj.foo();
- 案例二
function foo() {
console.log(this); // obj对象
}
var obj1 = {
name: "obj1",
foo: foo
}
var obj2 = {
name: "obj2",
obj1: obj1
}
obj2.obj1.foo();
- 案例三
function foo() {
console.log(this);
}
var obj1 = {
name: "obj1",
foo: foo
}
// 讲obj1的foo赋值给bar
var bar = obj1.foo;
bar();
- 案例一、二打印【obj1】;案例三打印【window】
3、显式绑定是什么?
-
【是什么】借助apply、call、bind等函数,对this进行明确的绑定。
-
案例一
function foo() {
console.log(this);
}
foo.call(window); // window
foo.call({name: "why"}); // {name: "why"}
foo.call(123); // Number对象,存放时123
- 案例二
function foo() {
console.log(this);
}
var obj = {
name: "why"
}
var bar = foo.bind(obj);
bar(); // obj对象
bar(); // obj对象
bar(); // obj对象
4、new绑定是什么?
JavaScript中的函数可以当做一个类的构造函数来使用,也就是使用new关键字。
使用new关键字来调用函数时,会执行如下操作:
- ①创建一个全新的对象;
- ②这个新对象会被执行Prototype连接;
- ③这个新对象会绑定到函数调用的this上(this的绑定在这个步骤完成);
- ④如果函数没有返回其他对象,表达式会返回这个新对象;
// 创建Person
function Person(name) {
console.log(this); // Person {}
this.name = name; // Person {name: "why"}
}
var p = new Person("why");
console.log(p);
5、上述四种方式的优先级顺序是什么?
- new绑定 > 显示绑定(bind) > 隐式绑定 > 默认绑定
三、箭头函数的this
1、什么是箭头函数?
- 【箭头函数】是ES6中新增的一个非常好用的函数类型。
- 核心写法如下:
()=> {
}
2、箭头函数的this遵守上面的四种绑定规则吗?
- 【箭头函数】不使用this的四种标准规则(也就是不绑定this)
- 【箭头函数】根据外层作用域来决定this。
3、箭头函数的一些案例
- 案例一:没有箭头函数之前,我们常见的写法
var obj = {
data: [],
getData: function() {
var _this = this;
setTimeout(function() {
// 模拟获取到的数据
var res = ["abc", "cba", "nba"];
_this.data.push(...res);
}, 1000);
}
}
obj.getData();
- 案例二:使用箭头函数后
var obj = {
data: [],
getData: function() {
setTimeout(() => {
// 模拟获取到的数据
var res = ["abc", "cba", "nba"];
this.data.push(...res);
}, 1000);
}
}
obj.getData();
- 案例三:如果getData也是一个箭头函数,那么setTimeout中的回调函数中的this指向谁呢?
var obj = {
data: [],
getData: () => {
setTimeout(() => {
console.log(this); // window
}, 1000);
}
}
obj.getData();
四、经典题目演练
1、面试题一
var name = "window";
var person = {
name: "person",
sayName: function () {
console.log(this.name);
}
};
function sayName() {
var sss = person.sayName;
sss();
person.sayName();
(person.sayName)();
(b = person.sayName)();
}
sayName();
- 代码解析
function sayName() {
var sss = person.sayName;
// 独立函数调用,没有和任何对象关联
sss(); // window
// 关联
person.sayName(); // person
(person.sayName)(); // person
(b = person.sayName)(); // window
}
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 () {
return () => {
console.log(this.name)
}
}
}
var person2 = { name: 'person2' }
person1.foo1();
person1.foo1.call(person2);
person1.foo2();
person1.foo2.call(person2);
person1.foo3()();
person1.foo3.call(person2)();
person1.foo3().call(person2);
person1.foo4()();
person1.foo4.call(person2)();
person1.foo4().call(person2);
- 代码解析
// 隐式绑定,肯定是person1
person1.foo1(); // person1
// 隐式绑定和显示绑定的结合,显示绑定生效,所以是person2
person1.foo1.call(person2); // person2
// foo2()是一个箭头函数,不适用所有的规则
person1.foo2() // window
// foo2依然是箭头函数,不适用于显示绑定的规则
person1.foo2.call(person2) // window
// 获取到foo3,但是调用位置是全局作用于下,所以是默认绑定window
person1.foo3()() // window
// foo3显示绑定到person2中
// 但是拿到的返回函数依然是在全局下调用,所以依然是window
person1.foo3.call(person2)() // window
// 拿到foo3返回的函数,通过显示绑定到person2中,所以是person2
person1.foo3().call(person2) // person2
// foo4()的函数返回的是一个箭头函数
// 箭头函数的执行找上层作用域,是person1
person1.foo4()() // person1
// foo4()显示绑定到person2中,并且返回一个箭头函数
// 箭头函数找上层作用域,是person2
person1.foo4.call(person2)() // person2
// foo4返回的是箭头函数,箭头函数只看上层作用域
person1.foo4().call(person2) // person1
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.foo1.call(person2)
person1.foo2()
person1.foo2.call(person2)
person1.foo3()()
person1.foo3.call(person2)()
person1.foo3().call(person2)
person1.foo4()()
person1.foo4.call(person2)()
person1.foo4().call(person2)
- 代码解析
// 隐式绑定
person1.foo1() // peron1
// 显示绑定优先级大于隐式绑定
person1.foo1.call(person2) // person2
// foo是一个箭头函数,会找上层作用域中的this,那么就是person1
person1.foo2() // person1
// foo是一个箭头函数,使用call调用不会影响this的绑定,和上面一样向上层查找
person1.foo2.call(person2) // person1
// 调用位置是全局直接调用,所以依然是window(默认绑定)
person1.foo3()() // window
// 最终还是拿到了foo3返回的函数,在全局直接调用(默认绑定)
person1.foo3.call(person2)() // window
// 拿到foo3返回的函数后,通过call绑定到person2中进行了调用
person1.foo3().call(person2) // person2
// foo4返回了箭头函数,和自身绑定没有关系,上层找到person1
person1.foo4()() // person1
// foo4调用时绑定了person2,返回的函数是箭头函数,调用时,找到了上层绑定的person2
person1.foo4.call(person2)() // person2
// foo4调用返回的箭头函数,和call调用没有关系,找到上层的person1
person1.foo4().call(person2) // person1
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')
person1.obj.foo1()()
person1.obj.foo1.call(person2)()
person1.obj.foo1().call(person2)
person1.obj.foo2()()
person1.obj.foo2.call(person2)()
person1.obj.foo2().call(person2)
- 代码解析
// obj.foo1()返回一个函数
// 这个函数在全局作用于下直接执行(默认绑定)
person1.obj.foo1()() // window
// 最终还是拿到一个返回的函数(虽然多了一步call的绑定)
// 这个函数在全局作用于下直接执行(默认绑定)
person1.obj.foo1.call(person2)() // window
person1.obj.foo1().call(person2) // person2
// 拿到foo2()的返回值,是一个箭头函数
// 箭头函数在执行时找上层作用域下的this,就是obj
person1.obj.foo2()() // obj
// foo2()的返回值,依然是箭头函数,但是在执行foo2时绑定了person2
// 箭头函数在执行时找上层作用域下的this,找到的是person2
person1.obj.foo2.call(person2)() // person2
// foo2()的返回值,依然是箭头函数
// 箭头函数通过call调用是不会绑定this,所以找上层作用域下的this是obj
person1.obj.foo2().call(person2) // obj
网友评论