【ECMAScript】this 总结

1. 概念

  1. this 是 JavaScript 关键字
    是当前环境执行期上下文对象的一个属性
    函数内部的一个执行期的上下文的指向
    执行期上下文:可以理解为一个对象里封装了很多方法和属性
  2. this 在不同的环境、不同作用下,表现是不同的
  3. 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 指向的改变

  1. 箭头函数是忽略任何形式的 this 指向的改变
    静态this 指向
  2. 默认绑定规则(独立调用)对箭头函数无效
  3. 箭头函数一定不是一个构造器,所以不能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. 构造函数

  1. 构造函数里默认隐式返回 this,或者手动返回 this,这个 this 指向的新对象构造都是成功的
  2. 如果手动返回了一个新对象,那么 this 指向的新对象的构造是失败的
  3. 如果你手动返回一个新对象,那个 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. 四种绑定规则

  1. 默认绑定规则

  2. 隐式绑定规则

  3. 显示绑定:call, apply, bind

  4. 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
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值