理解JS函数调用和"this"

简介: 如果理解核心函数的调用机制,同时验证一些以核心函数为基础的其他实现方式的运行机制,关于上述所说的问题就会迎刃而解。

该文章是直接翻译国外一篇文章,关于JS函数调用和"this"的处理。

都是基于原文处理的,其他的都是直接进行翻译可能有些生硬,所以为了行文方便,就做了一些简单的本地化处理。

如果想直接根据原文学习,可以忽略此文。

关于JS函数是如何调用的困惑了很多年,尤其是在JS函数中this的语法机制很让人头疼。

在我看来,如果理解核心函数的调用机制,同时验证一些以核心函数为基础的其他实现方式的运行机制,关于上述所说的问题就会迎刃而解。

核心机制(The Core Primitive)

首先,让我们来解析一些核心函数的调用机制的重点---Function对象的call方法。 call函数的调用过程如下:

  1. parameters第二个值到最后一个剥离出来并重新构建一个新的参数列表(argList)
  2. 传入函数的第一个值赋值给thisValue
  3. 调用函数,在此过程中,将thisValue赋值给this,argList作为函数的参数列表(argument list)

示例如下:

function hello(thing){
    console.log(this+ "says hello"+ thing);
}
hello.call("北宸南蓁","world");
//输出结果:北宸南蓁 says hello world
复制代码

正如实践之后所得到的结果,我们调用hello()的时候,将this的值赋值为北宸南蓁同时将world作为hello运行时的参数list。上述的处理流程就是JS函数调用的核心机制。你可以这样粗略的认为:其他函数的调用机制就是在核心机制的基础上进行了封装/简化处理(desugar)。

简单函数调用(Simple Function Invocation)

很显然,利用call调用函数看起来,不是一个很聪明的亚子。所以,JS运行利用简单语法 hello("world")直接调用函数。

function hello(thing){
    console.log("Hello" + thing);
}
//简化的语法(封装之后的语法)
hello("world")
//核心语法
hello.call(window,"world");
复制代码

NOTE:在ECMAScript 5的严格模式下有些许的不同:

//简化语法
hello("world");
//核心语法
hello(undefined,"world");
复制代码

综上所述:可以将简单函数fn(...args)的调用汇总为fn.call(window [ES5-strict:undefined],...args)

NOTE

上述的调用公式同样也适应于:(funciton(){})() ==>(function(){}).call(window [ES5-strict:undefined])

成员函数(Member Functions)

在js的应用场景中,函数作为对象的属性也是很常见的情景。在这种情景下,会发生如下的简化处理:

var person ={
    name:"北宸南蓁",
    hello:function(){
        console.log(this + "says hello" + thing);
    }
}
//简化的语法
person.hello("world");
//核心语法/
person.hello.call(person,"world");
复制代码

Note: 上述的简化过程不受成员函数的赋值和定义方式的影响的。例如上面的例子中,成员函数是直接定义在对象中。如果动态的对成员函数进行赋值,最后的简化结果也是一样的。

function hello(thing){
    console.log(this + "says hello" + thing);
}
person = { name:"北宸南蓁"};
person.hello= hello;
//简化语法
person.hello("world") //该种的核心语法也是  `person.hello.call(person,'world')`
hello("world") //"[object DOMWindow]world"
复制代码

Note:上述所有的函数中this的值都不是确定的。this的值由调用函数的所在的 作用域决定。

Function.prototype.bind对作用域进行绑定

在某些应用场景中,需要将函数中的this值进行绑定到指定的环境中。就需要额外的借助一个函数进行特定环境的绑定。

var person ={
    name:"北宸南蓁",
    hello:function(thing){
        console.log(this + "says hello " + thing);
    }
}
var boundHello = function(thing){
    return person.hello.call(person,thing);
}
boundHello("world");
复制代码

虽然在boundHello("world")调用的时候,被脱糖(desugar)boundHello.call(window,"world")。但是在函数中,是将person对象的成员函数hello进行this值的处理,指向hello函数应该在的作用域中。

或者我们可以将boundXX函数变得更加通用。

var bind = function(func,thisValue){
    return function(){
        return func.apply(thisValue,arguments);
    }
}
var boundHello = bind(person.hello,person);
boundHello("world");
复制代码

其中实现的原理这里就不再赘述了。

由于这种场景很多,ES5在Function对象中新增了bind方法,用于对一个函数指定特定的this值。

var boundHello = person.hello.bind(person);
boundHello("world");
复制代码

这种处理方式很有用,比如,你将一个成员函数作为callback:

var person = {
  name: "北宸南蓁",
  hello: function() { console.log(this.name + " says hello world"); }
}
$("#some-div").click(person.hello.bind(person));
复制代码

拨开云雾见月明

在上文中,为了能够在现有的规范语法中解释清楚函数调用的核心机制。通过func.call来阐述函数底层是如何实现一系列的数据操作的。实际上,实现函数调用的核心语法另有其人[[Call]](这是一个内部属性),但是他是func.call[obj.]func()实现的基础零件

我们来了解一下func.call的定义

  1. 如果func不是一个函数,直接抛出错误。
  2. 定义一个长度为0的argList
  3. 如果传入函数的参数大于1个,从第一个参数arg1到参数结尾的所有参数的值作为一个新值,赋值argList.
  4. Return the result of calling the [[Call]] internal method of func, providing thisArg as the this value and argList as the list of arguments.(这个话真的不好翻译,感觉还是原文的语句更加贴切)

正如上面的定义所知,func.call的内部实现,都是基于[[Call]]的操作来实现。也就是说,函数调用的核心就是**[[Call]]**的实现。但是这个方法的实现方式和func.call的处理过程是一样的。所以,通过类比来模拟出函数的调用过程。

相关文章
|
2月前
|
JavaScript 前端开发
javascript中的this
javascript中的this
|
4月前
|
JavaScript 前端开发
错综复杂的this:理清你的JavaScript代码中的指向问题
错综复杂的this:理清你的JavaScript代码中的指向问题
|
2月前
|
JavaScript
JS中改变this指向的六种方法
JS中改变this指向的六种方法
|
3天前
|
自然语言处理 JavaScript 前端开发
在JavaScript中,this关键字的行为可能会因函数的调用方式而异
【5月更文挑战第9天】JavaScript中的`this`关键字行为取决于函数调用方式。在非严格模式下,直接调用函数时`this`指全局对象,严格模式下为`undefined`。作为对象方法调用时,`this`指向该对象。用`new`调用构造函数时,`this`指向新实例。通过`call`、`apply`、`bind`可手动设置`this`值。在回调和事件处理中,`this`可能不直观,箭头函数和绑定方法可帮助管理`this`的行为。
9 1
|
5天前
|
JavaScript 前端开发
深入探索JavaScript:如何改变this的指向
深入探索JavaScript:如何改变this的指向
|
12天前
|
JavaScript 前端开发
【专栏】`Function.prototype.apply` 在JavaScript中用于动态设定函数上下文(`this`)和参数列表
【4月更文挑战第29天】`Function.prototype.apply` 在JavaScript中用于动态设定函数上下文(`this`)和参数列表。它接受两个参数:上下文对象和参数数组。理解`apply`有助于深入JS运行机制。文章分三部分探讨其原理:基本概念和用法、工作原理详解、实际应用与注意事项。在应用中要注意性能、参数类型和兼容性问题。`apply`可用于动态改变上下文、传递参数数组,甚至模拟其他语言的调用方式。通过深入理解`apply`,能提升代码质量和效率。
|
15天前
|
JavaScript 前端开发 数据安全/隐私保护
|
16天前
|
自然语言处理 JavaScript 前端开发
js_关键字this指向哪里?
js_关键字this指向哪里?
4 0
|
19天前
|
JavaScript 前端开发
js开发:请解释this关键字在JavaScript中的用法。
【4月更文挑战第23天】JavaScript的this关键字根据执行环境指向不同对象:全局中指向全局对象(如window),普通函数中默认指向全局对象,作为方法调用时指向调用对象;构造函数中指向新实例,箭头函数继承所在上下文的this。可通过call、apply、bind方法显式改变this指向。
8 1
|
1月前
|
自然语言处理 JavaScript 前端开发
解析JavaScript中的this:新手常见误区与应对策略
【4月更文挑战第1天】本文介绍了JavaScript中`this`的关键作用及其常见误区,包括作用域与调用上下文混淆、回调函数中的`this`绑定、构造函数使用、对象字面量方法与普通函数的区别以及严格模式的影响。通过理解四条绑定规则(显式、new、隐式、默认)和采取相应避免策略,开发者能更好地掌握和运用`this`,提高编程效率和代码质量。
21 1