目录
1. 概念
- this 是 JavaScript 关键字
是当前环境执行期上下文对象的一个属性
函数内部的一个执行期的上下文的指向
执行期上下文:可以理解为一个对象里封装了很多方法和属性 - this 在不同的环境、不同作用下,表现是不同的
- this 明确指向的时机:执行期
函数的执行条件与环境决定了 this 到底指向谁
2. 全局作用域下的 this,叫做 全局对象
2.1 window 和 this 的关系
2.1.1 全局作用域下 window 等于 this
// 全局作用域下 window 等于 this
console.log(this === window); // true
2.1.2 全局作用域下声明的变量是挂载到 window 上的
var a = 1;
var b = function() {
return 'function'
};
console.log(window.a === a); // true
console.log(window.b === b); // true
console.log(a); // 1
console.log(b); // f(){}
window.aa = 1;
window.bb = function() {
return 'function'
};
console.log(aa); // 1
console.log(bb); // f(){}
this.aa = 1;
this.bb = function() {
return 'function'
};
console.log(aa); // 1
console.log(bb); // f(){}
2.2 this 在浏览器、node 环境下的表现
2.2.1 获取全局对象的方法
web: window, self, frames, this
node: global
web worker: self
通用方式: globalThis(可以在任何上拿到全局作用域)⭐
2.2.2 web 环境下
var a = 'global -> a';
var obj = {
a: 'obj -> a',
test: function() {
console.log(this.a); // obj -> a
console.log(window.a); // global -> a
console.log(self.a); // global -> a
console.log(frames.a); // global -> a
}
}
obj.test(); // obj -> a global -> a
2.2.3 node 环境下
在 node 环境下,需要把属性定义到 global 上,否则直接 var 声明,global 拿不到
var a = 'global -> a';
global.b = 'global -> b';
var obj = {
a: 'obj -> a',
test: function() {
console.log(this.a)
console.log(global.a)
console.log(global.b)
}
}
obj.test(); // obj -> a undefined global -> b
2.2.4 严格模式下,谁调用函数,函数内部的指向默认就是谁
function test() {
return this; // 非严格模式 this 指向 window
}
console.log(test()); // window
// this 明确指向的时机:执行期
// 函数的执行条件与环境决定了 this 到底指向谁
function test() {
'use strict';
return this;
}
// 严格模式下 return this 返回 undefined
console.log(test()); // undefined
// 谁调用函数,函数内部的指向默认就是谁
console.log(window.test()); // window
3. 类中的 this
3.1 类的本质
类的本质其实就是函数
类中是严格模式
// 类 class xx 理解为 容器/作用域/模块 -> 壳子
class Test1 {
constructor() {
}
say() {
}
static do() {}
}
const test1 = new Test1();
// 函数
function Test2() { // new 的时候,Test2里面指向构造器
}
Test2.prototype.say = function() {}
Test2.do = function() {}
const test2 = new Test2();
// 立即执行函数
const Test = (function() {
function Test() { // new 的时候,Test2里面指向构造器
}
Test.prototype.say = function() {}
Test.do = function() {}
window.Test = Test;
})();
都可以通过 new 实例化出来一个对象, 它们在本质上是没有什么不同的
// 默认情况下,一个对象一定有 proto 指向它的构造器的原型属性
class Test {
constructor(){
// 类的非静态方法,会在 new 实例化的过程中,添加到 this 中去
this.test = function(){
console.log('none-static: ' + this)
}
}
// 类的静态方法,也叫类原型上的静态属性
// 定义的时候就已经放到了 Test.prototype 中了 -> test 方法
// new 的时候,生成了 this 的新的指向,指向了空对象 {},这个对象有自己的 __proto__ 属性,这个 __proto__ 属性又指向了 Test.prototype
test(){
console.log('static: ' + this);
}
}
分析:
/**
* this -> {
* test: function(){}
* }
**/
// new 的过程中 this 指向了 {}
const test = new Test();
console.log(test);
test.test(); // Test{}
const TestA = Object.create(null);
console.log(TestA); // 没有 ptototype
const TestB = {}; // 有 prototype
3.2 继承
问:为什么可以实现继承?
class Father {
constructor(){
// 基类在继承的时候是没有 this 绑定的 no this binding
// new -> this -> {} -> age 属性
this.age = 44;
}
swim(){
console.log('Go swimming');
}
}
// Son 继承的是 Father 的原型
class Son extends Father {
constructor(){
// 问:super 做了什么?
// 答:调用 Father 上的 constructor ,
// 相当于生成了 this 的绑定,Father this 指向了 Son 的实例,
// 相当于当前 Son 的 this 指向了一个 new Father(),new Father() 生成了一个对象 {}
// this -> new Father() -> {}
super();
// 问:为什么 super() 在调用之前不可以访问 this ?
// 答:因为先绑定 this,再 super() -> new Father() 生成了一个新的对象,this 就指向了这个对象了,就无法实现 同时访问 this.hobby, this.age
this.hobby = 'travel';
console.log(this.age); // 44
}
study(){
// 这个 this 指向的还是 Son 的对象实例
console.log(this);
// 这里实际上访问的是 Son 的原型属性当中的 study,然后再访问到 Father 的原型属性上面
// 沿着 proto 一直找原型链
this.swim();
}
}
const son = new Son();
son.study();
4. call, apply, bind
var obj = {
a: 1
}
var obj2 = {
a: 100
}
var a = 2;
function test(b, c) {
// this 默认指向 全局对象 window
console.log(this.a, b, c);
}
test(); // 2
// 让 test 方法中的 this 指向 obj
test.call(obj); // 1
test.apply(obj);
test.call(obj, 3, 4); // 1 3 4
test.apply(obj, [3, 4]); // 1 3 4
// bind 只会生效一次,绑定的第一次⭐
var test1 = test.bind(obj, 3, 4);
test1(); // 1 3 4
var test2 = test1.bind(obj2, 3, 4);
test2(); // 1 3 4
var t = test.bind(obj, 3, 4).bind(obj2, 3, 4);
t(); // 1 3 4
5. 函数
5.1 普通函数
一个函数内部是有隐式的 this,当函数执行的时候,决定 this 指向谁
函数声明和函数表达式内部的 this 是由执行期的环境和调用方式决定,-> this 是不稳定的
5.2 回调函数
一个函数一般在自调用的时候都会指向 window
函数声明与函数表达式中的 this 在非严格模式下,自调用 this 指向 window
function test(callback){
callback();
}
// 1.
var obj = {
test: function(callback) {
callback(); // 自调用
},
test1: function(){
var fn = function(){
console.log(this); // window
}
fn(); // 自调用
}
}
// 2.
obj.test(function(){
console.log(this); // window
})
obj.test1();
// -----------------------------
var obj = {
test: function(){
obj.test1 = function(){ // 不是自己调用,是obj去调用 test1
console.log(this); // -> obj
}
obj.test1()
}
}
obj.test();
promise,跟promise异步没有任何关系
var p = new Promise(function(resolve, reject){ // 回调函数
console.log(this); // 指向window
})
// ---------------
var obj = {};
obj.p = new Promise(function(resolve, reject){ // 回调函数
console.log(this); // 指向window
})
6. 箭头函数中的 this
箭头函数存在的目的:稳定程序中的 this 指向⭐
箭头函数内部没有this指向,箭头函数的 this指向是由外层函数作用域决定的⭐
6.1 严格模式和非严格模式
箭头函数在严格模式和非严格模式下都是指向 window 的;
普通函数在非严格模式下指向 window,严格模式下返回 undefined
'use strict';
const test = () =>{
console.log(this);
}
function test1(){
console.log(this);
}
test();
test1();
6.2 改变 this 指向
6.2.1 箭头函数是忽略任何形式的 this 指向的改变
- 箭头函数是忽略任何形式的 this 指向的改变
静态this 指向 - 默认绑定规则(独立调用)对箭头函数无效
- 箭头函数一定不是一个构造器,所以不能new
var obj = {
a: 1
}
var a = 2;
const test = () => {
console.log(this.a);
}
test(); // 2
test.call(obj); // 2
var test1 = test.bind(obj)
test1(); // 2
// 箭头函数是不能作为构造函数使用的
// 箭头函数一定不是一个构造器,所以不能new
new test(); // 报错
function Foo(){
console.log(this);
var test = () => {
console.log(this);
}
return test;
}
var obj = {
a: 1,
foo: foo,
foo1: () => {
console.log(this);
}
}
var obj2 = {
a: 2,
foo: foo
}
obj.foo()(); // 默认绑定规则(独立调用)对箭头函数无效
var bar = Foo().call(obj2); // 显示绑定规则无效
// 箭头函数中不存在 this,往上找到全局的 this -> window
obj.foo1(); // 隐式绑定无效
//
6.2.2 箭头函数中的 this 不是谁绑定指向谁 ⭐
var obj = {
a: 1
}
obj.test = () => {
console.log(obj);
console.log(this); // window
}
obj.test();
var obj = {
a: 1
}
obj.test = function() {
var t = () => {
// this -> obj
console.log(this); // obj
}
t();
}
obj.test();
var obj = {
a: 1
}
obj.test = function() {
setTimeout(()=>{
// this -> obj
console.log(this);
}, 0)
}
obj.test();
var obj = {
a: 1
}
obj.test = function() {
var t1 = () => {
var t2 = () => {
// this -> obj
console.log(this);
}
t2();
}
t1();
}
obj.test();
var obj = {
a: 1
}
obj.test = function() {
var t1 = function() {
var t2 = () => {
// t1 是箭头函数 this -> obj
// t1 是普通函数 this -> window
// this -> window
console.log(this);
}
t2();
}
t1();
}
obj.test();
6.2.3 箭头函数中的this 始终保持与父级作用域的this一致 ⭐
var obj = {
test: () => {
// 父作用域 -> global -> window
console.log(this); // window
var test1 = () => {
console.log(this); // window
}
test1();
}
}
obj.test();
// ------------------------
var obj = {
test(){
var test1 = () => {
console.log(this); // -> obj
var test2 = () => {
console.log(this); // -> obj
}
}
test1();
}
}
obj.test();
6.2.4 事件处理函数中的箭头函数
document.querySelector('#btn').addEventListener('click', ()=>{
// 父作用域 -> global -> window
console.log(this); // window
});
6.2.5 回调函数中的箭头函数
var obj = {
test(callback) {
callback();
}
}
obj.test(()=>{
console.log(this);
})
6.3 总结
箭头函数中的 this -> 非箭头函数的外层作用域的 this 指向(外层的函数不能是箭头函数)
7. 对象中的 this 指向
this 的指向的基本原则:谁调用 this 的宿主,this 就指向谁
const test3 = () => {
console.log(this.b);
}
var obj = {
a: 1, // 成员属性
b: 2,
test: function(){ // 成员方法
// this -> obj
console.log(this.a)
},
test2: test2,
test3: test3,
c: {
d: 4,
test3: function(){
// this -> obj.c
console.log(this);
console.log(this.d);
}
}
}
function test2() {
console.log(this.b);
}
obj.test();
obj.test2();
obj.test3(); // undefined
obj.c.test3();
对象方法内部的 this 总是指向最近的引用 ⭐
var obj2 = {
a: 1,
b: 2,
test3: function(){
function t(){ // 这是一个孤立的函数,不属于任何成员,最近的引用就是 window ⭐
// this -> window
// 最近的引用就是 window
console.log(this);
}
t();
}
}
obj2.test3();
obj2.__proto__ = {
e: 20
}
console.log(obj2.e); // 20 值是继承而来
var obj3 = Object.create({
test4: function(){
console.log(this.a + this.b)
}
})
obj3.a = 1;
obj3.b = 2;
obj3.test4(); // 3 沿着链去找对应的原型
分析:
1. test4 由 obj3 调用
2. obj3 就是 test4 最近的引用
3. test4 this -> obj3
4. obj3 中不存在 test4
5. obj3 沿着 __proto__ 去找 prototype 对象
6. 直到找到 Object.prototype 为止
7. 只要链上有 test4 直接调用
8. 如果找不到,报错 -> 因为它是 undefined
9. undefined 无法执行 -> not a function -> TypeError
// 从调用的角度 obj3.test4 同下方
var obj3 = {
a: 1,
b: 2,
test4: function(){
console.log(this.a + this.b)
}
}
将对象中的方法赋值给其它变量⭐
var obj = {
test: function(){
console.log(this);
}
}
obj.test();
var a = obj.test;
console.log(a()); // window
8. Object.defineProperty()
字面量方式定义对象
构造函数:function Object(){}
var obj = {
a: 1,
b: 2
}
Object.create 创建
var obj2 = Object.create({
c: 3,
d: 4
})
console.log(obj2);
Object.defineProperty
var obj3 = {};
// 第二个参数:要增加的属性的名称
// 第三个参数:一个对象
Object.defineProperty(obj3, 'a', { // 专门的视频看一下⭐
get: function() { // 获取属性值的时候会走这个方法
console.log(this) // -> obj3
return 4;
}
})
console.log(obj.a)
9. 构造函数
- 构造函数里默认隐式返回 this,或者手动返回 this,这个 this 指向的新对象构造都是成功的
- 如果手动返回了一个新对象,那么 this 指向的新对象的构造是失败的
- 如果你手动返回一个新对象,那个 this 指向的这个对象就被忽略了
问:new关键字起了什么作用?
答:1. 指示函数 Test执行;2. 函数内部的this 指向新的对象;3. 执行期间返回 this
问:new 的过程中经历了哪几个过程?
答:1. 在函数内部隐式创建一个对象 2. 让this 指向这个对象 3. 将 this上挂载的属性放入到新对象 4. 隐式返回 this
function Test() {
/**
* new 的过程
* 1. this -> {}
* 2. {a: 1}
* 3. {a: 1, b: 2}
* 4. return this
*/
this.a = 1;
this.b = 2;
console.log(this);
}
// this -> 实例化出来的对象
new Test(); // -> {a: 1, b: 2}
function Test() {
this.a = 1;
this.b = 2;
// 执行阶段 this 是存在的,只是 return 的时候 this 被忽略了
return {
c: 3,
d: 4
}
}
// new 指示函数 Test执行,并且执行期间返回 this
var test = new Test();
console.log(test); // {c: 3, d: 4}
10. 事件处理函数绑定的问题
10.1 事件处理函数内部的 this 总是指向被绑定 DOM 元素
事件是不需要绑定的,事件是浏览器给我们的机制,我们绑定的是针对事件的处理函数
var oBtn = document.getElementById('btn');
// onclick 事件处理函数内部的 this 指向被绑定 DOM 元素
oBtn.onclick = function(){
console.log(this);
}
oBtn.addEventListener('click', function(){
console.log(this);
}, false)
;(function(doc){
var oBtn = document.getElementById('btn');
function Plus(a, b) {
this.a = a;
this.b = b;
this.init();
}
Plus.prototype.init = function() {
this.bindEvent();
}
Plus.prototype.bindEvent = function() {
// 第一种方式
// bind this: 把这个函数内部的 this 指向 Plus 实例
// oBtn.addEventListener('click', this.handleBtnClick.bind(this), false)
// 第二种方式
var _self = this;
oBtn.addEventListener('click', function(){
_self.handleBtnClick()
}, false)
}
Plus.prototype.handleBtnClick = function() {
console.log(this.a + this.b);
}
window.Plus = Plus;
})(document);
new Plus(3, 4);
<!-- this -> button -->
<button onclick="console.log(this)">test</button>
<!-- this -> window 在函数的作用域内部,行内里面,this -> window -->
<button onclick="(function(){console.log(this)})()">test2</button>
类中是严格模式
10.2 例子
父亲有个吃水果的方法 水果
儿子有自己的水果 -> 使用父亲吃水果的方法吃自己的水果
class Father {
constructor(){
// eat 函数内部的 this 永远指向 father 的实例
// 让函数内部的 this 指向固定
this.eat = this.eat.bind(this);
}
get fruit(){
return 'apple';
}
eat(){
console.log('eat ' + this.fruit)
}
}
class Son {
get fruit(){
return 'orange';
}
}
const father = new Father();
const son = new Son();
father.eat();
son.eat = father.eat;
son.eat();
// 儿子也必须吃父亲的水果
// Father 中加 下面这段
constructor(){
// eat 函数内部的 this 永远指向 father 的实例
// 让函数内部的 this 指向固定
this.eat = this.eat.bind(this);
}
11. 四种绑定规则
-
默认绑定规则
-
隐式绑定规则
-
显示绑定:call, apply, bind
-
new 绑定
优先级: 4 > 3 > 2 > 1
只有构造函数才需要 new
11.1 默认绑定规则;
window 独立调用也指向 window
this === window; // true
{} === {} // false 对象对比的是引用
// 函数的独立调用
function test(){
console.log(this === window); // true
}
test();
11.2 隐式绑定规则
对象调用,谁调用就指向谁 -> 存在两种情况:隐式丢失,参数赋值
函数执行,this 才有意义,函数不执行是不存在this指向的,每一个函数执行都会有一个自身的this指向
var a = 0;
var obj = {
a: 2,
foo: function(){
console.log(this);
function test() {
console.log(this)
}
test(); // 独立调用 -> 内部 this 指向 window
(function(){
console.log(this); // 立即执行函数 this -> window(对应环境的全局变量)
})()
// 闭包:当函数执行的时候,导致函数被定义,并抛出。(不严谨的说法,方便理解)
// 闭包是一个现象
function test1() {
console.log(this)
}
return test1;
}
}
obj.foo();// obj
obj.foo()();// window -> 相当于 test() -> 独立调用 -> 指向 window
变量赋值
var a = 0;
function foo(){
console.log(this);
}
var obj = {
a: 2,
foo: foo
}
obj.foo();
var bar = obj.foo; // 函数赋值的时候,会存在隐式丢失 -> 它的this指向重新归为window -> 函数独立调用
bar(); // window -> 独立调用
参数赋值的情况
var a = 0;
function foo(){
console.log(this);
}
function bar(fn){ // fn -> 回调函数 bar -> 子函数
console.log(this);
fn(); // -> foo() -> 独立执行 -> window
}
// 父函数是有能力决定子函数的this指向的
// 在 bar 内部,是有能力决定 fn 的 this 指向的,
// 如:new fn(); fn.bind(obj)();
var arr = [1, 2, 3];
arr.forEach(function(item, idx, arr){
console.log(this);
}, obj) // 第二个参数 决定 function 中的 this 指向
var obj = {
a: 2,
foo: foo
}
// 预编译的过程中,实参被赋值为形参(值的拷贝的过程,浅拷贝)
bar(obj.foo);
11.3 显示绑定:call, apply, bind
-> 这三个就是为了绑定 this 指向而存在的
var a = 0;
function foo(a, b, c, d, e){
console.log(a, b, c, d, e);
console.log(this);
}
var obj = {
a: 2,
foo: foo
}
obj.foo();
var bar = obj.foo;
bar();
obj.foo(1, 2, 3, 4, 5);
bar.call(obj, 1, 2, 3, 4, 5);
bar.apply(obj, [1, 2, 3, 4, 5]);
bar.bind(obj)(1, 2, 3, 4, 5);
// 如果绑定的值不是对象
bar.call(1); // this -> 包装类 Number(1)
// null 和 undefined 没有包装类,不是对象,绑定失败,会采用强绑定的方式,默认的绑定方式,this 指向 window
bar.apply(null);
bar.bind(undefined)();
// 立即执行函数使用 call
(function(){
console.log(this);
}).call(obj);
11.4 new 绑定 -> 优先级最高
function Person(){
// new 做了什么
// var this = {};
// this.a = 1;
// return this;
}
var person = new Person(); // this 指向实例化之后的对象
// this 的重写
function Person(){
return 1;
// return 的值为引用值,会改变 this 指向,函数内部的 this 就不再指向实例之后的对象了
// return {}
}
var person = new Person(); // person 指向实例化之后的对象
12. 优先级
function foo(){
console.log(this.a);
}
var obj1 = {
a: 2,
foo: foo
}
var obj2 = {
a: 3,
foo: foo
}
obj1.foo();
obj2.foo();
// 显示绑定优先级 高于 隐式绑定
obj1.foo.call(obj2);
obj2.foo.call(obj1);
new > 显示绑定
function foo(b){
this.a = b;
}
var obj1 = {};
// bar 和 foo 是一个拷贝关系,仅仅是内部的 this 不一样而已
var bar = foo.bind(obj1); // bar 和 foo 不是同一个函数
bar(2); // 这里相当于赋值 obj1.a = 2
console.log(obj1.a); // 2
var bar2 = new bar(3); // new 可以更改当前的 this 指向
console.log(obj1.a); // 2
console.log(bar2.a); // 3
13. 题目
题1
var name = "window";
var obj1 = {
name:'1',
fn1: function () {
console.log(this.name)
},
fn2: () => console.log(this.name),
fn3: function () {
return function () {
console.log(this.name)
}
},
fn4: function () {
return () => console.log(this.name)
}
}
var obj2 = {
name:'2'
};
obj1.fn1(); // 1
obj1.fn1.call(obj2); // 2
obj1.fn2(); // window -> 箭头函数所有绑定规则无效
obj1.fn2.call(obj2); // window
obj1.fn3()(); // window
obj1.fn3().call(obj2); // 2
obj1.fn3.call(obj2)(); // window
obj1.fn4()(); // 1
obj1.fn4().call(obj2); // 1
obj1.fn4.call(obj2)(); // 2
题2
function Foo() {
getName = function () { console.log(1); };
return this;
}
Foo.getName = function () { console.log(2);};
Foo.prototype.getName = function () { console.log(3);};
var getName = function () { console.log(4);};
function getName() { console.log(5);}
//请写出以下输出结果,
Foo.getName(); // 2
getName(); // 4
Foo().getName(); // 1
getName(); // 1
new Foo.getName(); // 2 先 点运算,在 new
new Foo().getName(); // 3 new 和 函数执行符号()在一起,要先执行 new fn(),new 一个函数执行
new new Foo().getName(); // 3