笔记-你不知道的JS-this

数字映月使
• 阅读 1004

1 this的概念

this 在任何情况下都不指向函数的词法作用域。作用域“对象”无法通过 JavaScript代码访问,它存在于 JavaScript 引擎内部。

this 是在运行时进行绑定的,并不是在编写时绑定,它的上下文取决于函数调用时的各种条件。this 的绑定和函数声明的位置没有任何关系,只取决于函数的调用方式。

当一个函数被调用时,会创建一个活动记录(有时候也称为执行上下文)。这个记录会包含函数在哪里被调用(调用栈)、函数的调用方法、传入的参数等信息。this 就是记录的其中一个属性,会在函数执行的过程中用到。

2 this的绑定

函数的执行过程中调用位置如何决定 this 的绑定对象,绑定规则:

1 默认绑定

function foo(){
  console.log(a);
}
var a=2;
foo();

foo() 是直接使用不带任何修饰的函数引用进行调用的,因此只能使用默认绑定,无法应用其他规则。如果使用严格模式(strict mode),那么全局对象将无法使用默认绑定,因此 this 会绑定
到 undefined。

2 隐式绑定
调用位置是否有上下文对象,或者说是否被某个对象拥有或者包含。当 foo() 被调用时,它的落脚点确实指向 obj 对象。当函数引用有上下文对象时,隐式绑定规则会把函数调用中的 this 绑定到这个上下文对象。因为调用 foo() 时 this 被绑定到 obj,因此 this.a 和 obj.a 是一样的。对象属性引用链中只有最顶层或者说最后一层会影响调用位置。

function fooo(){
  conosle.log(this.a);
}
var obj={
  a:2,
  foo:foo
};
var obj2={
  a:22,
  obj: obj
}
obj.foo();  // 2
obj2.obj.foo();  // 2

隐式丢失
虽然 bar 是 obj.foo 的一个引用,但是实际上,它引用的是 foo 函数本身,因此此时的bar() 其实是一个不带任何修饰的函数调用,因此应用了默认绑定。

当函数被作为参数传递时(如:回调函数),会丢失this,因为参数传递其实就是一种隐式赋值

function foo() { 
  console.log( this.a );
}
var obj = { 
  a: 2,
  foo: foo
};
var bar = obj.foo; // 函数别名!
var a = "oops, global"; // a 是全局对象的属性 
bar(); // "oops, global"

3 显式绑定
JavaScript 提供的绝大多数函数以及你自己创建的所有函数都可以使用 call(..) 和 apply(..) 方法。这两个方法是如何工作的呢?它们的第一个参数是一个对象,它们会把这个对象绑定到this,接着在调用函数时指定这个 this。因为你可以直接指定 this 的绑定对象,因此我们称之为显式绑定。

function foo(){
  console.log(this.a);
}
var obj={a:2};
foo.call(obj);  // 2

如果你传入了一个原始值(字符串类型、布尔类型或者数字类型)来当作 this 的绑定对象,这个原始值会被转换成它的对象形式(也就是new String(..)、new Boolean(..)或者new Number(..))。这通常被称为“装箱”。

4 new 绑定
使用 new 来调用函数,或者说发生构造函数调用时,会自动执行下面的操作。

  1. 创建(或者说构造)一个全新的对象。
  2. 这个新对象会被执行[[原型]]连接。
  3. 这个新对象会绑定到函数调用的this。
  4. 如果函数没有返回其他对象,那么new表达式中的函数调用会自动返回这个新对象。
function foo(a) { 
  this.a = a;
}
var bar = new foo(2); 
console.log( bar.a ); // 2

使用 new 来调用 foo(..) 时,我们会构造一个新对象并把它绑定到 foo(..) 调用中的 this上。new 是最后一种可以影响函数调用时 this 绑定行为的方法,我们称之为 new 绑定。

3 判断this绑定


注意:如果你把 null 或者 undefined 作为 this 的绑定对象传入 call、apply 或者 bind,这些值在调用时会被忽略,实际应用的是默认绑定规则。

那么什么情况下你会传入 null 呢?一种非常常见的做法是使用 apply(..) 来“展开”一个数组,并当作参数传入一个函数。类似地,bind(..) 可以对参数进行柯里化(预先设置一些参数),这种方法有时非常有用:

function foo(a,b) {
console.log( "a:" + a + ", b:" + b );
}
// 把数组“展开”成参数
foo.apply( null, [2, 3] ); // a:2, b:3
// 使用 bind(..) 进行柯里化
var bar = foo.bind( null, 2 ); 
bar( 3 ); // a:2, b:3

软绑定会对指定的函数进行封装,首先检查调用时的 this,如果 this 绑定到全局对象或者 undefined,那就把指定的默认对象 obj 绑定到 this,否则不会修改 this。

if (!Function.prototype.softBind) { 
  Function.prototype.softBind = function(obj) {
    var fn = this;
    // 捕获所有 curried 参数
    var curried = [].slice.call( arguments, 1 ); var bound = function() {
      return fn.apply(
      (!this || this === (window || global)) ?
        obj : this
        curried.concat.apply( curried, arguments )
      ); 
    };
    bound.prototype = Object.create( fn.prototype );
    return bound; };
}

4 箭头函数

箭头函数并不是使用 function 关键字定义的,而是使用被称为“胖箭头”的操作符 => 定义的。箭头函数不使用 this 的四种标准规则,而是根据外层(函数或者全局)作用域来决定 this。

function foo() {
// 返回一个箭头函数 
  return (a) => {
//this 继承自 foo()
  console.log( this.a ); };
}
var obj1 = { a:2};
var obj2 = { a:3};
var bar = foo.call( obj1 );
bar.call( obj2 ); // 2, 不是 3 !

foo() 内部创建的箭头函数会捕获调用时 foo() 的 this。由于 foo() 的 this 绑定到 obj1,bar(引用箭头函数)的 this 也会绑定到 obj1,箭头函数的绑定无法被修改。(new 也不行!)

点赞
收藏
评论区
推荐文章
Souleigh ✨ Souleigh ✨
4年前
JS - 作用域
一、作用域作用域,即变量(变量作用域又称上下文)和函数生效(能被访问)的区域或集合换句话说,作用域决定了代码区块中变量和其他资源的可见性举个例子function myFunction(){    let inVariable  "函数内部变量";}myFunction();//要先执行这个函数,否则根本不知
Symbol卢 Symbol卢
4年前
秒懂js作用域与作用域链
JavaScript中有一个被称为作用域(Scope)的特性。虽然对于许多新手开发者来说,作用域的概念并不是很容易理解,本文我会尽我所能用最简单的方式来解释作用域和作用域链,希望大家有所收获!好了下面开始我们的正文作用域常见的解释(什么是作用域)1.一段程序代码中所用到的名字并不总是有效,而限定它的可用性的范围就是这个名字的作用域;2.作用域规定了
Karen110 Karen110
4年前
一篇文章带你了解JavaScript作用域
在JavaScript中,对象和函数也是变量。在JavaScript中,作用域是你可以访问的变量、对象和函数的集合。JavaScript有函数作用域:这个作用域在函数内变化。一、本地JavaScript变量一个变量声明在JavaScript函数内部,成为函数的局部变量。局部变量有局部作用域:它们只能在函数中访问。JS://codeherecann
菜园前端 菜园前端
2年前
一篇文章教会你什么是闭包
原文链接:什么是闭包?闭包的概念并不复杂,但是它的定义比较绕(就像平时经常用到它,却又说不出来是什么)。可以在一个作用域中调用函数的内部函数并访问到该函数中的作用域的成员,这就是闭包。给一个建议,网上闭包的概念可以搜出来一大堆,但是你真的了解它吗?你有去调
菜园前端 菜园前端
2年前
为你解惑JS作用域和作用域链知识
原文链接:变量作用域一个变量的作用域(scope)是程序源代码中定义这个变量的区域。全局变量拥有全局作用域,在JavaScript代码中的任何地方都是可以访问的。然而在函数内声明的变量只能在函数体内访问,它们是局部变量,作用域是局部性的。函数参数也是局部变
Jacquelyn38 Jacquelyn38
4年前
你所知道的JS变量作用域
变量的作用域,指的是变量在脚本代码中的可读、可写的有效范围,也就是脚本代码中可以使用这个变量的区域。在ES6之前,变量的作用域主要分为全局作用域、局部作用域(也称函数作用域)两种;在ES6及其之后,变量的作用域主要分为全局作用域、局部作用域、块级作用域这3种。相应作用域变量分别称为全局变量、局部变量、块级变量。全局变量声明在所有函数之外;局部变量是在函数体内
Jacquelyn38 Jacquelyn38
4年前
重学JavaScript第1集|变量提升
变量提升就好比JavaScript引擎用一个很小的代码起重机将所有var声明和function函数声明都举起到所属作用域(所谓作用域,指的是可访问变量和函数的区域)的最高处。这句话的意思是:如果在函数体外定义函数或使用var声明变量。则变量和函数的作用域会提升到整个代码的最高处,此时任何地方访问这个变量和调用这个函数都不会报错;而在函数体内定义函数或使用va
御弟哥哥 御弟哥哥
4年前
彻底理解js的执行上下文,以及变量对象
在js中,执行上下文(ExecutionContext)是非常重要的一种对象,它保存着函数执行所需的重要信息,其中有三个属性:变量对象(variableobject),作用域链(scopechain),this指针(thisvalue),它们影响着变量的解析,变量作用域和函数this的指向。上下文栈(ExecutionContextS
Stella981 Stella981
3年前
JavaScript易错知识点整理
前言本文是我学习JavaScript过程中收集与整理的一些易错知识点,将分别从变量作用域,类型比较,this指向,函数参数,闭包问题及对象拷贝与赋值这6个方面进行由浅入深的介绍和讲解,其中也涉及了一些ES6的知识点。JavaScript知识点1.变量作用域vara1;functio
Stella981 Stella981
3年前
JavaScript之函数
    玩js自然要和函数打交到。函数嘛简单来说就是给代码分个块,方便调用、信息隐藏和代码复用,还可以用于指定对象的行为。另外函数还可以玩出很多花样来。。。JavaScript使用关键字function定义函数。定义一个函数://函数声明//这种定义函数的好处是可以在当前作用域内任何位置调用,因为变量的声明和函数的
小万哥 小万哥
1年前
Python 作用域:局部作用域、全局作用域和使用 global 关键字
变量只在创建它的区域内可用。这被称为作用域。局部作用域在函数内部创建的变量属于该函数的局部作用域,并且只能在该函数内部使用。示例:在函数内部创建的变量在该函数内部可用:pythondefmyfunc():x300print(x)myfunc()函数内部的函