资讯专栏INFORMATION COLUMN

【2】this

Alex / 2619人阅读

摘要:否则如果是或,则设绑定为全局对象。令为解释执行的结果。返回一个值类型的引用,其基值为且其引用名为,严格模式标记为。进入函数代码,为,非严格模式下将赋值为全局对象。内置函数如何使用的内置函数修改是通过给的内置方法传递来实现的。

this

说到this,需要明确三方面内容:

this何时被赋值

this被赋了什么值

内置函数如何使用this的

this何时被赋值 进入函数代码

当控制流根据一个函数对象 F、调用者提供的 thisArg 以及调用者提供的 argumentList,进入函数代码的执行环境时,执行以下步骤:

如果函数代码是严格模式下的代码,设 this 绑定 为 thisArg。

否则如果 thisArg 是 null 或 undefined ,则设 this 绑定为全局对象。

……

以上信息来源:进入函数代码

从上诉信息中可以知道

this 与调用者提供的 thisArg 密切相关

this 在严格模式下为 thisArg

this 在非严格模式下为 thisArg 或 全局对象

那么 thisArg 又是怎么来的呢?下面来看下函数调用过程:

函数调用

令 ref 为解释执行 MemberExpression 的结果。

令 func 为 GetValue(ref)。

令 argList 为解释执行 Arguments 的结果,产生参数值们的内部列表(参见 11.2.4)。

如果 Type(func) 不是 Object,抛出一个 TypeError 异常。

如果 IsCallable(func) 为 false,抛出一个 TypeError 异常。

如果 Type(ref) 为 Reference,那么

如果 IsPropertyReference(ref) 为 true,

那么令 thisValue 为 GetBase(ref)。

否则,ref 的基值是一个环境记录项。

令 thisValue 为调用 GetBase(ref) 的 ImplicitThisValue 具体方法的结果。

否则,Type(ref) 不是 Reference。

令 thisValue 为 undefined。

返回调用 func 的 [[Call]] 内置方法的结果,传入 thisValue 作为 this 值和列表 argList 作为参数列表。

以上信息来源:函数调用

从上诉信息中可以知道

thisArg 即 thisValue

thisValue 与 ref 的类型密切相关

如果 ref 的类型是 Reference(引用规范类型)

如果 ref 是属性引用,通过 GetBase(ref)(返回引用值ref的基值部分) 获取 thisValue

否则,通过 ImplicitThisValue 方法获取 thisValue

否则,thisValue 为 undefined

那么,了解到这里可能有许多新的疑问,比如:

Reference 是怎样的类型

ref 是怎么来的

ref 什么时候是 Reference,什么时候不是。

GetBase(ref) 和 ImplicitThisValue 是如何产生结果的

Reference 是怎样的类型

首先先解释下 Reference。

其实ES中的类型分为ECMAScript语言类型和规范类型

ECMAScript语言类型对应的是程序员使用 ECMAScript 语言直接操作的值,如 Undefined、Null、Boolean、String、Number、Object等。
规范类型可用来描述 ECMAScript 表达式运算的中间结果,但这样的值不能储存为对象的属性或 ECMAScript 语言的变量值。引用尤雨溪的解释:

这里的 Reference 是一个 Specification Type,也就是 “只存在于规范里的抽象类型”。它们是为了更好地描述语言的底层行为逻辑才存在的,但并不存在于实际的 js 代码中。
ref 是怎么来的

从上诉函数调用中可以知道,ref 是解释执行 MemberExpression 的结果。
下面详细看下 MemberExpression 的解析过程:

产生式 CallExpression : MemberExpression Arguments 按照下面的过程执行 :

令 baseReference 为解释执行 MemberExpression 的结果。

令 baseValue 为 GetValue(baseReference)。

令 propertyNameReference 为解释执行 Expression 的结果。

令 propertyNameValue 为 GetValue(propertyNameReference)。

调用 CheckObjectCoercible(baseValue)。

令 propertyNameString 为 ToString(propertyNameValue)。

如果正在执行中的语法产生式包含在严格模式代码当中,令 strict 为 true,否则令 strict 为 false。

返回一个值类型的引用,其基值为 baseValue 且其引用名为 propertyNameString,严格模式标记为 strict。

从上诉信息中分析可以知道

解释执行 MemberExpression 的结果是一个引用规范类型(Reference)

这个引用规范类型包含三部分信息:

baseValue

propertyNameString

strict

thisValue 的值取决于 baseValue

baseValue 是调用 GetValue 获得的。

GetValue 得到的基值是 undefined、Object、Boolean、String、Number、环境记录项中的任意一个(详见:引用规范类型),而不是引用规范类型。

GetValue 详细过程见:GetValue
ref 什么时候是 Reference,什么时候不是 Reference

一般来说,ref 是MemberExpression解析的结果,都将是 Reference。
但是,如果 MemberExpression 是其函数表达式的一部分,则可能将改变最终解析结果的类型。
而改变解析结果类型的主要原因取决于是否调用了 GetValue 方法,如果调用了 GetValue ,函数中间值 ref 将是 Object 类型。

那么哪些表达式不使用 GetValue 呢?

标识符引用 : 标识符执行的结果总是一个 Reference 类型的值。

群组表达式:本算法不在执行 Expression 后使用 GetValue。这主要的目的是让 delete 与 typeof 运算符可以作用在被括号括起来的表达式。

成员表达式

调用表达式

更多表达式详见:表达式
GetBase(ref) 和 ImplicitThisValue 是如何产生结果的

GetBase:返回引用值ref的基值部分

ImplicitThisValue : 声明式环境记录项永远将 undefined 作为其 ImplicitThisValue 返回。

this被赋了什么值

其实在 this何时被赋值 部分已经介绍了 this被赋了什么值。下面总结三种赋值过程:

第一种this赋值过程:
var v = 1; 
var obj = {
    v: 2,
    fn: function(){
        console.log(this.v);
    }
}
obj.fn(); // 2
(obj.fn)(); // 2

调用表达式解析 obj.fn ,返回引用规范类型,baseValue 为 obj。

函数调用,由于 1 过程返回的为引用规范类型,且为属性引用,调用 GetBase 将 baseValue (obj) 作为返回值,返回给 thisValue。

进入函数代码,thisArg 为 obj,将其赋值给 this。

如果对步骤1中,baseValue 为 obj 有疑问,详见属性访问, 产生式 MemberExpression : MemberExpression [ Expression ] 执行过程。
相当于有两个过程:

解析obj,baseValue 为声明式环境记录项。

解析obj.fn,baseValue 为 obj。

第二种this赋值过程:
function foo(){
    console.log(this);
}
foo(); // Window

调用表达式解析 foo, 返回引用规范类型,baseValue 为声明式环境记录项(函数声明时绑定)。

函数调用,由于 1 过程返回的为引用规范类型,且不为属性引用,调用 ImplicitThisValue 方法,返回 undefined 。

进入函数代码,thisArg 为 undefined,非严格模式下将 this 赋值为全局对象。

第三种this赋值过程:
var v = 1; 
var obj = {
    v: 2,
    fn: function(){
        console.log(this.v);
    }
}
var fn2 = obj.fn;
fn2(); // 1
(obj.fn, obj.fn)(); // 1

调用表达式解析 fn2,(obj.fn, obj.fn) ,由于赋值表达式、逗号表达式都使用了 GetValue 方法,返回函数(Object 类型)。

函数调用,由于 1 过程返回的不是引用规范类型,所以 thisValue 为 undefined`。

进入函数代码,thisArg 为 undefined,非严格模式下将 this 赋值为全局对象。

内置函数如何使用this的

内置函数修改 this 是通过给 func 的 [[Call]] 内置方法传递 thisArg 来实现的。

内置方法

Function.prototype.apply (thisArg, argArray)

Function.prototype.call (thisArg [ , arg1 [ , arg2, … ] ] )

Function.prototype.bind (thisArg [, arg1 [, arg2, …] ] )

Array.prototype.every ( callbackfn [ , thisArg ] )

Array.prototype.some ( callbackfn [ , thisArg ] )

Array.prototype.forEach ( callbackfn [ , thisArg ] )

Array.prototype.map ( callbackfn [ , thisArg ] )

Array.prototype.filter ( callbackfn [ , thisArg ] )

new 运算符 使用内置方法 [[Construct]] 实现this指定

内置方法模拟实现

使用成员表达式模拟内置方法[[Call]]的效果:

apply
Function.prototype.apply_ = function (context, arr) { 
    var context = Object(context) || window;
    var result;

    // 临时记录需要调用的function 
    context.fn = this;
      
    if (!arr) {
        result = context.fn();
    }
    else {
        var args = [];
        for (var i = 0, len = arr.length; i < len; i++) {
            args.push("arr[" + i + "]");
        }
        // 使用成员表达式指定context.fn执行时this为context 
        result = eval("context.fn(" + args + ")")
    }

    delete context.fn
    return result;
}
call
Function.prototype.call_ = function (context) {
    var context = context || window;
    context.fn = this;

    var args = [];
    // 获取参数列表 
    for(var i = 1, len = arguments.length; i < len; i++) {
        args.push("arguments[" + i + "]");
    } 
    // 使用成员表达式指定context.fn执行时this为context  
    var result = eval("context.fn(" + args +")");

    delete context.fn
    return result;
}  
bind
Function.prototype.bind_  = function (context) {
    // 记录bind的函数 
    var self = this;
    var args = [];
    // 获取绑定的参数列表 
    for(var i = 1, len = arguments.length; i < len; i++) {
        args.push("arguments[" + i + "]");
    } 

    // 创建新函数 
    var fbound = function () {
        // 获取未绑定的参数列表 
        var bindArgs = Array.prototype.slice.call(arguments);
        // fbound被当做构造函数使用,this指向实例。否则,指向 context
        self.apply(this instanceof self ? this : context, args.concat(bindArgs));
    } 
    // 维护原型关系   
    fbound.prototype = self.prototype || new Function().prototype ;
    return fbound;
}
参考文档

【1】冴羽的深入理解系列

【2】ES5 Wiki

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/102001.html

相关文章

  • canvas之转盘抽奖

    摘要:最近工作中重构了抽奖转盘,给大家提供一个开发转盘抽奖的思路需求转盘根据奖品数量不同而有变化目录结构由于业务需要所以开发了两个版本抽奖,和,不过部分只能替换图片,没有功能逻辑。 最近工作中重构了抽奖转盘,给大家提供一个开发转盘抽奖的思路 需求 1、转盘根据奖品数量不同而有变化 2、canvas 目录结构 showImg(https://segmentfault.com/img/bVbwL...

    _ang 评论0 收藏0
  • 构建二叉树进行数值数组的去重及优化

    摘要:构建二叉树进行数值数组的去重及优化常见两层循环实现数组去重构建二叉树实现去重仅适用于数值类型的数组将先前遍历过的元素,构建成二叉树,树中每个结点都满足左子结点的值当前结点的值右子结点的值这样优化了判断元素是否之前出现过的过程若元素比当前结点 构建二叉树进行数值数组的去重及优化 常见两层循环实现数组去重 let arr = [11, 12, 13, 9, 8, 7, 0, 1, 2, 2...

    sarva 评论0 收藏0
  • ionic 2+ 手势解锁界面

    摘要:手势解锁界面一些对安全要求比较高的少不了锁屏页面,而手势解锁对于用户来说使用方便,对于程序员来说小有挑战,怎么有弃之不用的道理。 ionic 2+ 手势解锁界面 一些对安全要求比较高的app少不了锁屏页面,而手势解锁对于用户来说使用方便,对于程序员来说小有挑战,怎么有弃之不用的道理。 效果图 效果图处理短,方便大家阅读showImg(https://segmentfault.co...

    Hancock_Xu 评论0 收藏0
  • JS+canvas实现五子棋人机大战

    摘要:五子棋人机大战创建实例是否结束我电脑所有棋子已经落下的棋子赢法总数所有赢法统计我的赢法统计电脑赢法统计初始化初始化生成棋盘棋盘初始化鼠标移动聚焦功能实现算法初始化落子功能实现生成棋盘初始化生成不是的倍数棋盘列初始化棋盘棋盘初始化画棋盘画 JS+canvas五子棋人机大战 1. 创建实例 function Gobang () { this.over = false; // 是否结...

    sutaking 评论0 收藏0
  • js数据结构-二叉树(二叉堆)

    摘要:二叉树二叉树是一种树形结构,它的特点是每个节点最多只有两个分支节点,一棵二叉树通常由根节点,分支节点,叶子节点组成。 二叉树 二叉树(Binary Tree)是一种树形结构,它的特点是每个节点最多只有两个分支节点,一棵二叉树通常由根节点,分支节点,叶子节点组成。而每个分支节点也常常被称作为一棵子树。 showImg(https://segmentfault.com/img/bVbmEd...

    ningwang 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<