JavaScript学习日记 : Day10 - This

1. 为什麽要有this?

JavaScript允许在函数内部,引用当前执行中环境的其他变数。

function func() {
    console.log(a)
}

如上代码,func函数引用了当前执行环境的a,问题是这个函数可以在任何执行环境中被调用,因此这时候的a可能就指向不同了,所以JS引擎需要有一个机制,可以依靠其优雅的、准确地指向当前代码运行的执行环境。

何谓优雅的?

//假设这个object名称很长,且有可能改名
let objectWithALongName={
    name:"David",
    func1(){
        return objectWithALongName.name;
    },
    func2(){
        return this.name;
    }
}

objectWithALongName的方法func2使用了this关键字,就优雅多了,然後即使往後对象名称改变了,func2内部代码也不需改变。

另外如果有其他执行环境也需要使用到这个函数,使用this也可以优雅的指向该执行环境:

let otherObject = {
    name:"John",
    cloneMethod:objectWithALongName.func2
}

otherObject.cloneMethod(); // John func2中的this这时候指到otherObject

何谓精准的

this可以精准的指向某个对象。

//全域变数
let objectWithALongName = {
  name: "David"
};

(function() {
  //局部变数
  let objectWithALongName = {
    name: "John",
    func1() {
      return objectWithALongName.name;
    },
    func2() {
      //相对於func1,这边用了this可以清楚知道指向内部IIFE的执行环境
      return this.name;
    }
  };

  console.log(objectWithALongName.func1());// John
})();

2. 呼叫的位置

呼叫位子就是函数在代码中在哪里被调用,而不是声明的位子。所以搞清楚「由谁在哪调用」才能精确的找到this的指向。

let module = {
  id: 10,
  getId: function() {
    return this.id;
  }
}

console.log(module.getId());//>> 10
let globalGetId = module.getId;//getX和module.getX都是指向记忆体中函数的地址而已,这边并没有被()执行
console.log(globalGetId()); //>> undefined

作为module对象的getId方法被调用,this指向module,module对象有一个属性id值为10,所以console.log(module.getX())出10,而globalGetId中的this会指向全域,全域中并没有定义id,所以出undefined。

3. 对this的误解

-->This既不指向函数自身,也不指向函数的作用域

  1. This的指向,是在函数被调用的时候确定的,也就是执行环境被创建时确定的。
  2. this的指向与函数声明的位置没有任何关系,只取决於函数调用的位置(由谁、在哪调用这个函数)
  3. 正因为执行环境创建阶段this就确定了,在执行阶段this的指向不可在被变更。
let obj = {
    a:"123"
}
function func() {
    this = obj; //报错,因为在执行阶段试图改变this
    console.log(this.a);
}
func();

4. this指向规则

  1. 默认指向
    独立函数的调用(无法应用後面指向规则时),this指向全域对象。
function func() {
    console.log( this.a ); // this指向全域对象
}
let a = 2;
func(); // 2

对於默认指向来说,决定this指向的并不是调用位置是否是严格模式(use strict),而是函数本体是否处於严格模式。如果函数处於严格模式,this指向undefined,否则指向全域。

function func() {
  "use strict";//函数中处於严格模式下,this指向undefined
  console.log(this.a);
}

let a = 123;

func(); // 报错

function func() {
  console.log(this.a);
}

var a = 123;
(function() {
  "use strict";
  func(); // 123
})();
  1. 隐式指向

隐式指向是日常开发中最常见的,也就是决定this指向的是由谁呼叫,与函数存在的位置无关:

function func() {
  console.log(this.a);
}
let obj = {
  a: 2,
  func: func
};
obj.func(); // 2
// 找到呼叫位置,由obj对象来呼叫函数func,
// 此时可以说函数func被呼叫时,obj对象拥有或者包含func函数
// 所以此时的 this 指向呼叫 func 函数的 obj 对象。

对象属性引用链中只有最顶层或者说最後一层才会影响调用位置,简单说,this指向最靠近被调用函数的对象。

function func() {
  console.log(this.a);
}

let obj2 = {
  a: 10,
  func: func
};

let obj1 = {
  a: 20,
  obj2: obj2
};

// this指向obj2对象,因为obj2离的最近
obj1.obj2.func(); // 10
  1. 显示指向

Javascript内建对象Function的三种原型方法call()、apply()、bind(),他们的第一个参数是对象,他们会把这个对象绑定到this,接着在调用函数时让this指向这个对象。

let a = 10;

function func() {
    console.log( this.a );
}
let obj = {
    a:20
};

func.call(obj); // 20
// 在呼叫func时强制把this指向obj

5.new操作符与this关系

使用new来调用函数,或者说发生构造函数调用时,会执行底下操作 :

  1. 创建一个新的对象。
  2. 将构造函数的作用域赋与新对象(因此this就指向了这个新对象)
  3. 执行构造函数中的代码(为这个新对象添加属性、方法等)
  4. 如果函数没有返回其他对象,那就返回这个新对象。
function func(a) {
    this.a = a;
}
let bar = new func("test");
console.log(bar.a); // test
// 使用new 来呼叫func(..)时,创建一个新对象并把它绑定到func(..)调用中的this上

另外一个观点:

function User(name) {
  // this = {};(隐藏创建)

  // 添加属性到 this
  this.name = name;
  this.isAdmin = false;

  // return this;(隐藏返回)
}

6. 整理判断顺序

用以上介绍规则可以简单做一个判断流程:

  1. 函数是否在new中被调用(new操作符指向)?

如果是的话,this绑定的是新创建的对象。

function func(name) {
  this.name = name;
  this.getName = function() {
    return this.name;
  };
}

let obj = new func("apple"); //this会指向obj
console.log(obj.getName()); // apple
  1. 函数是否通过call、apply、bind显式指向?
    如果是的话,this指向的是call、apply、bind三个方法的第一个参数指定的对象。
let obj1 = {
  name: "David"
};
function func() {
  return this.name; //这里的this本来指向window
}
let str = func.call(obj1); //改变了func函数里面this的指向,指向obj1
console.log(str); // David
  1. 函数是否被当作某个对象的方法而调用(隐式指向)?
    如果是的话,this指向是这个对象。
let obj1 = {
  name: "Jack",
  func() {
    return this.name; //指向obj1
  }
};

//这里的obj1.func(),表明func函数被obj1调用,因此func中的this指向obj1
console.log(obj1.func()); // Jack
  1. 若以上都不是的话,使用默认绑定

如果在非严格模式下,就绑定到全域对象。

let a = 123; //为全域对象增加一个变数a
function func() {
  return this.a;
}

console.log(func()); // 123

<<:  day7: CSS style 规划 - CSS in JS(emotion 使用 - 1)

>>:  图的连通 (2)

Day 7 - 数学是不是会击垮一个人的信心? 会

简介 上一篇介绍了如何利用2进位来表示10进位的数字,这次则要再进阶的介绍一下4、8、16进位。在下...

Day 28 - 设籍有关涉及射击的射击游戏

Intro 这次是写了两个小游戏,并从里面学到一点 member function 的用法,还有字串...

Day 19 : 案例分享(6.2) 人事、差勤与薪资 - 组织架构、人事资料及个人合同管理

案例说明及适用场景 组织架构是由部门及职务做为骨架,员工就职於某一个职务 员工在企业的职务,就如同系...

【Day 14】- 实战爬取 Ubuntu ISO 映像档下载网址

前情提要 前三篇文章带各位开发了一只 PTT 爬虫,具备持续爬取,并将爬取到的文只内容储存於 JSO...

Day34 ( 电子元件 ) 小夜灯 ( 光敏电阻 )

小夜灯 ( 光敏电阻 ) 教学原文参考:小夜灯 ( 光敏电阻 ) 虽然 micro:bit 有内建侦...