前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >你不知道的JavaScrpit(上卷) 随记(二)

你不知道的JavaScrpit(上卷) 随记(二)

原创
作者头像
邱邱邱邱yf
发布2021-12-22 15:38:27
3170
发布2021-12-22 15:38:27
举报
文章被收录于专栏:邱邱邱邱yf的读书笔记

第二部分 this和对象原型

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

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

1. this全面解析

1. 调用位置

调用位置就是函数在代码中被调用的位置(而不是声明位置)。最重要的就是分析 调用栈(就是为了达到当前执行位置所调用的所有函数)。 我们关心的调用位置就在当前正在执行的函数的前一个调用中。

代码语言:javascript
复制
function baz() {
    // 当前调用栈 baz, 因此当前调用位置为全局作用域(当前正在执行的函数的前一个调用)
    console.log("baz");
    bar()
}
​
function bar() {
    // 当前调用栈 baz -> bar, 因此当前调用位置为baz(当前正在执行的函数的前一个调用)
    console.log("bar");
    foo();
}
​
function foo() {
    // 当前调用栈 baz -> bar -> foo, 因此当前调用位置为bar(当前正在执行的函数的前一个调用)
    console.log("foo");
}
​
baz() // <--从这里开始, baz
2. 绑定规则

函数的执行过程中调用位置决定this的绑定对象。

步骤就是, 找到调用位置然后判断需要使用下面的四条规则中的哪一条。

规则1: 默认绑定

最常用的函数调用类型: 独立函数调用。可以把这看成无法应用其他规则的默认规则。

代码语言:javascript
复制
function foo() {
    console.log(this.a);
}
var a = 2;
foo() // 2;
​
/**
    首先第一件事: 声明在全局作用域中的变量(比如 var a= 2 )就是全局对象的一个同名属性。
    调用foo时,this.a被解析成了全局变量a。因为在本例中,函数调用时应用了this的默认绑定,因此this
    执行全局对象。那么我们怎么知道这里应用了默认绑定呢?可以通过分析调用位置来看。在代码中,foo()是
    直接使用不带任何修饰的函数引用进行调用的,因此只能使用默认绑定。如果使用严格模式(strict mode),
    则不能将全局作对象应用于默认绑定,因此this会绑定到undefined, 如下:
*/
​
function foo() {
    "use strict";
    console.log(this.a);
}
var a = 2;
foo() // TypeError: this is undefined
​
/**
    这里有一个微妙但是非常重要的细节,虽然this的绑定规则完全取决于调用位置,但是只有foo()
    运行在非strict mode下时(个人理解就是声明的函数体不在严格模式下的意思),默认绑定才能
    绑定到全局对象;函数体声明于非严格模式,但是严格模式下调用foo()则不影响默认绑定
*/
function foo() {
    console.log(this.a);
}
var a = 2;
(function (){
    "use strict";
    foo() // 2
})()
规则2: 隐式绑定

考虑: 调用位置是否有上下文对象,或者说是否被某个对象拥有或者包含。

代码语言:javascript
复制
function foo() {
    console.log(this.a);
}
var obj = {
    a: 2,
    foo: foo,
}
obj.foo() // 2
/**
    首先需要注意的是foo()的声明方式,及其之后时如何被当作引用属性添加到obj中的。但是无论时直接在obj
    中定义还是先定义再添加为引用属性,这个函数严格来说都不属于obj对象。然而调用位置会使用obj上下文来
    引用函数,因此你可以说 函数被调用时obj对象 "拥有"或"包含"函数引用。无论你如何称呼,当foo()被调用时,
    它的前面确实加上了对obj的引用。当函数引用有上下文对象时, 隐式绑定 规则会把函数调用中的this绑定到
    这个上下文对象。因为调用foo()时this被绑定到obj, 因此this.a和obj.a是一样的。
    注意: 对象属性引用链上只有上一层或者说最后一层在调用位置中起效果。比如:
*/
function foo() {
    console.log(this.a);
}
var obj2 = {
    a: 42,
    foo: foo,
}
var obj1 = {
    a: 2,
    obj2: obj2,
}
obj1.obj2.foo() // 42
​
/**
    一个常见的this绑定问题就是被 隐式绑定 的函数会丢失绑定对象,也就是说它会应用 默认绑定。如:
*/
function foo() {
    console.log(this.a);
}
var obj = {
    a: 2,
    foo: foo,
}
var bar = obj.foo;
var a = "oops, global";
bar(); // "oops, global";
​
/**
    虽然bar是obj.foo的一个引用,但是实际上,它引用的是foo函数本身,因此此时的bar()其实是一个不带
    任何修饰的函数调用,因此应用了默认绑定。一种更微妙更常见的情况发生在传入回调函数时:
*/
function foo() {
    console.log(this.a);
}
function dooFoo(fn) {
    fn(); // fn其实引用的是foo
}
var obj = {
    a: 2,
    foo: foo,
}
var a = "oops, global";
 doFoo()(obj.foo); // "oops, global"
/**
    参数传递其实就是一种隐式赋值,因此我们传入函数时也会被隐式赋值,所以结果和上一个例子一样。还有:
*/
function foo() {
    console.log(this.a);
}
var obj = {
    a: 2,
    foo: foo,
}
var a = "oops, global";
setTimeout(obj.foo, 100); // "oops, global"
// js的setTimeout实现和下面的伪代码类似:
function setTimeout(fn, delay) {
    // wait delay
    fn() // <--调用位置
}
规则3: 显式绑定

在分析 隐式绑定 时,我们必须在一个对象内部包含一个指向函数的属性,并通过这个属性间接引用函数,从而把this间接(隐式)绑定到这个对象上。但是我们其实可以使用call(...)和apply(...)在调用函数时指定它的this,因为你可以直接指定thia实我绑定对象,因此我们称之为显示绑定。

代码语言:javascript
复制
function foo() {
    console.log(this.a);
}
var obj = {
    a:2,
}
foo.call(obj) // 2
/**
    在调用时强制把它的this绑定在obj上。如果你传入了一个原始值(字符串类型,布尔类型或数字类型)来当作
    this的绑定对象,这个原始值会把转换成它的对象形式(new String()等,其实就是装箱)有不少内置API提供上
    下文,确保你的回调函数使用指定的this,例如
*/
function foo(el) {
    console.log(el, this.id);
}
var obj = {
    id: 'awesome',
}
[1, 2, 3].forEach(foo, obj) // 1 awesome 2 awesome 3 awesome
规则4: new绑定

首先我们重新定义一下js中的“构造函数”。在js中,构造函数只是一些使用new操作符时被调用的函数。他们并不会属于某个类,也不会实例化一个类。实际上他们甚至都不能说是一种特殊的函数类型,他们只是被new操作符调用的普通函数而已。

js中,实际上并不存在所谓的“构造函数”,只有对于函数的“构造调用”。

使用new来调用函数,会自动执行:

  1. 创建一个全新的对象
  2. 这个对象会执行[[prototype]]连接
  3. 这个新对象会绑定到函数调用的this
  4. 如果函数没有返回其他对象,那么new表达式中的函数调用会自动返回这个新对象
代码语言:javascript
复制
function foo(a) {
    this.a = a
}
var bar = new foo(2);
console.log(bar.a); // 2
​
/**
    使用new来调用foo()时,我们会构造一个新对象并把它绑定到foo(...)调用中的this上。
*/
优先级
代码语言:javascript
复制
function foo() {
    console.log(this.a);
}
var obj1 = {
    a: 2,
    foo: foo,
}
var obj2 = {
    a: 3,
    foo: foo,
}
obj1.foo() // 2
obj2.foo() // 3
​
obj1.foo.call(obj2) // 3
obj2.foo.call(obj1) // 2
​
/**
    可以看到,显示绑定 优先级高于 隐式绑定。
*/
function foo(something) {
    this.a = something;
}
var obj1 = {}
var bar = foo.bind(obj1);
bar(2)
console.log(obj1.a) // 2
var baz = new bar(3);
console.log(obj1.a) // 2
console.log(baz.a) // 3 
    foo: foo,
}
var obj2 = {}
obj1.foo(2)
console.log(obj1.a) // 2
obj1.foo.call(obj2, 3)
console.log(obj2.a) // 3
​
var bar = new obj1.foo(4)
console.log(obj1.a) // 2 
console.log(bar.a) // 4   这边说明在new obj1.foo时, this优先获取到的是new是新创建的新对象,不是隐式绑定的obj1
/**
    可以看到new绑定比隐式绑定优先级高。
*/
function foo(something) {
    this.a = something;
}
var obj1 = {}
var bar = foo.bind(obj1);
bar(2)
console.log(obj1.a) // 2
var baz = new bar(3);
console.log(obj1.a) // 2
console.log(baz.a) // 3 这边说明在new obj1.foo时, this优先获取到的是new是新创建的新对象,不是显示硬绑定的obj1

总结:

按顺序判断

  1. 函数是否在new中调用? 是的话this绑定新创建的对象
  2. 函数是否通过call,apply调用? 是的话this绑定指定的对象
  3. 函数是否在某个上下文对象中调用? 是的话this绑定这个上下文对象
  4. 默认绑定全局对象(非严格模式)
箭头函数

ES6中介绍了一种无法使用这些规则的特殊函数类型: 箭头函数。

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

代码语言:javascript
复制
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 也不行!)
*/

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 第二部分 this和对象原型
    • 1. this全面解析
      • 1. 调用位置
      • 2. 绑定规则
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档