JavaScript 核心原理解析
周爱民
《JavaScript 语言精髓与编程实践》作者,南潮科技(Ruff)首席架构师
32699 人已学习
新⼈⾸单¥59
登录后,你可以任选3讲全文学习
课程目录
已完结/共 28 讲
开篇词 (1讲)
JavaScript 核心原理解析
15
15
1.0x
00:00/00:00
登录|注册

22 | new Function('x = 100')();:函数的类化是对动态与静态系统的再次统一

作为语句的执行结果将是“空(Empty)”
在所有代码运行之前被创建
函数表达式是匿名的,但结果会绑定给一个名字并最终导出
引入“函数定义(Function Definitions)”的概念
所有声明语句的完成结果都是Empty,无效,不能用于导出
导出的内容应该是一个声明的结果或一个声明的名字
静态装配
特性
深入探索JavaScript核心原理的乐趣
打下基础,理解ECMAScript作为“语言设计者”职责和关注点
22讲内容全部结束
将全局环境作为“父级的作用域”传入FunctionInitialize()
从函数对象所在的“域”中取出全局环境
初始化与具体实例相关的内部槽和外部属性
Function (p1, p2, … , pn, body)
用户代码可以派生函数的子类
函数也获得了“子类化”的能力
所有JavaScript中的函数原型最终指向Function
一切函数缺省的构造器
使用动态创建
使用eval()
匿名函数的问题
ECMAScript 6开始支持模块
具名的、静态的函数声明
牺牲一致性,换取性能
动态函数的创建与其他数据没有本质的不同
JavaScript核心原理解析
动态函数执行在非严格模式中
动态函数在创建时只检测代码文本中的第一行是否为use strict指示字
动态函数的父级作用域将指向全局
函数初始化(FunctionInitialize)阶段的规范约定
无法识别函数的类型
调用/执行方面没有特殊性
函数是数据
引用或表达式操作数
函数初始化(FunctionInitialize)的内置过程
动态创建(CreateDynamicFunction)的特点与实现过程
调用内部过程创建函数对象
一致的创建过程
动态函数的相同界面
结果
执行体
参数
类继承体系的提出
Function()
创建自定义函数的方式
声明的函数特性
结尾
唯一一点不同
作为一个函数
动态函数的创建过程
函数的三个组件
几种动态函数的构造器
函数的动态创建
为什么说函数的类化是对动态与静态系统的再次统一?

该思维导图由 AI 生成,仅供参考

你好,我是周爱民,欢迎回到我的专栏。
今天是专栏最后一讲,我接下来要跟你聊的,仍然是 JavaScript 的动态语言特性,主要是动态函数的实现原理。
标题中的代码比较简单,是常用、常见的。这里稍微需要强调一下的是“最后一对括号的使用”,由于运算符优先级的设计,它是在 new 运算之后才被调用的。也就是说,标题中的代码等义于:
// (等义于)
(new Function('x = 100'))()
// (或)
f = new Function('x = 100')
f()
此外,这里的new运算符也可以去掉。也就是说:
new Function(x)
// vs.
Function(x)
这两种写法没有区别,都是动态地创建一个函数。

函数的动态创建

如果在代码中声明一个函数,那么这个函数必然是具名的。具名的、静态的函数声明有两个特性:
是它在所有代码运行之前被创建;
它作为语句的执行结果将是“空(Empty)”。
这是早期 JavaScript 中的一个硬性的约定,但是到了 ECMAScript 6 开始支持模块的时候,这个设计就成了问题。因为模块是静态装配的,这意味着它导出的内容“应该是”一个声明的结果或者一个声明的名字,因为只有声明才是静态装配阶段的特性。但是,所有声明语句的完成结果都是 Empty,是无效的,不能用于导出。
NOTE:关于 6 种声明,请参见《第 02 讲》。
而声明的名字呢?不错,这对具名函数来说没问题。但是匿名函数呢?就成了问题了。
确认放弃笔记?
放弃后所记笔记将不保留。
新功能上线,你的历史笔记已初始化为私密笔记,是否一键批量公开?
批量公开的笔记不会为你同步至部落
公开
同步至部落
取消
完成
0/2000
荧光笔
直线
曲线
笔记
复制
AI
  • 深入了解
  • 翻译
    • 英语
    • 中文简体
    • 中文繁体
    • 法语
    • 德语
    • 日语
    • 韩语
    • 俄语
    • 西班牙语
    • 阿拉伯语
  • 解释
  • 总结

JavaScript中的函数类化是对动态与静态系统的再次统一。文章深入探讨了动态函数的实现原理,包括动态创建函数的方式、函数的三个组件以及动态函数的创建过程。作者指出,动态创建函数意味着创建一个对象,它创建自类/构造器。在JavaScript中,Function()是一切函数缺省的构造器,所有的内建函数也通过简单的映射将它们的原型指向Function。这种设计使得JavaScript中的函数有了“完整的”面向对象特性,函数的“类化”实现了JavaScript在函数式语言和面向对象语言在概念上的大一统。文章还介绍了动态函数的构造器、函数的三个组件以及动态函数的创建过程。此外,文章还探讨了动态函数与静态函数的统一性,指出在ECMAScript的内部方法`Call()`或者函数对象的内部槽`[[Call]] [[Construct]]`中,根本没有任何代码来区别这两种方式创建出来的函数。文章还指出了动态函数在创建时只检测代码文本中的第一行代码是否为`use strict`指示字,而忽略它“外部scope”是否处于严格模式中,因此即使在严格模式的全局环境中创建动态函数,它也是执行在非严格模式中的。总的来说,文章深入探讨了JavaScript中函数的动态特性,以及动态函数与静态函数的统一性,为读者提供了对JavaScript函数类化的深入理解。

仅可试看部分内容,如需阅读全部内容,请付费购买文章所属专栏
《JavaScript 核心原理解析》
新⼈⾸单¥59
立即购买
登录 后留言

全部留言(10)

  • 最新
  • 精选
  • 独钓寒江雪
    今天读完了专栏必学内容的最后一讲,跟着老师的步伐一路走来,很艰辛,同时也收获很多,通过一行极简的代码去洞悉一门语言的核心原理,也是我一直梦想着能做到的事,向老师致敬! 其实,可以说,看这种底层的东西,每一讲都很吃力,要想有更深入的理解,必须再花时间回过头反复研读;其实,阅读专栏,很多时候也是一种思维的提升,比如以前只知道变量提升,却没想过为什么要提升;知道...运算符,却说不出为什么可以用它来展开对象。。。 或许专栏短期内对开发能力不会有多么显著的提升,但我相信,因为对语言本质的洞悉而产生的自信以及思想层面的提升,将会使我在前端走的更远。衷心的感谢🙏

    作者回复: 🙏

    2020-01-03
    8
  • 行问
    new Function(x) vs Function(x) 没什么区别。如果是“类化”的话,也是没什么区别吗?在使用 class 声明一个类时,new class 与 class 直接调用。 函数是对象的概念比较清晰,明了。这让我想起之前的 "null",请教个问题,在通常的开发中,会把一些变量释放空间,把值置为 null, 那么如果是置为 {} 呢?如下: var a = null 和 var a = {},是否有大差异? 我的理解是 {} 会存放在“堆空间”占据内存,但同时它是一个空对象,null 也是一个什么都没有的空对象,但 null 也是其它对象的原型,所以也会有 Object.create(null) 不知道周大能否看懂我的逻辑? 感谢

    作者回复: Oh. 这个 > new Function(x) vs Function(x) 没什么区别 并不算是很特别的特例。你应该记得new AClass()的时候,允许“用户代码直接返回对象,而不是直接使用this” 这个特性对吧,其实这就是这个特性的应用。因为当用户代码返回自己创建的对象是,用不用new,效果都是一样的了。——所以,这里的意思是说,Function()在实现时也是自己返回了对象,而没有使用缺省new给他创建的this。 理论上,这对类化来说也是适用的。因为类化也允许用户代码返回对象来替代this。但是——呵呵,如果你用“class X...”来声明类,那么这个X是只能用new来调用的,而不能直接当做函数调用。 ``` > X() TypeError: Class constructor X cannot be invoked without 'new' ``` 关于null值和{}。其实null值是一个特殊性,它是真的“什么也没有”,你甚至可以将它理解为C里面的#0。而它是对象(typeof null),以及它能作为其它对象或类的原型,只是一个语言设计,而与它的内存占用没有关系。你可以这样理解,没问题。——另外,在ECMAScript中,null是一个原始值(Primitive value),这意味着它可以直接在引擎中表达为二进制的存储,真的跟#0很接近了。^^. 而{}是一个对象,它在引擎中表达为一个结构、一个数据块(也就是你认为的放在堆里,其实是不是放在堆里不重要,而且也并不确定)。对象之所以为空白(“{}”称为空白对象Empty objects),是因为它的自有属性表为空,当自有属性表里面没有属性项的时候,它就是空白的了。——你可以重置它的原型,让他表现得有一些属性什么的。因为它毕竟还是一个可操作的、占有引擎中的存储的真实对象。 另外,ECMAScript内部(以及引擎内部的执行逻辑中)其实是把null值理解为“值”的,而不是“对象”。所以ECMAScript的内部方法IsObject(null),是返回false的。

    2020-01-03
    2
    4
  • 晓小东
    好快,这个专栏结束了,有点舍不得,一个多月来我一直关注老师更新,反复阅听之前章节。体会深思理解,发现如果没有老师带领层层分析JavaScript 最核心那部分设计和概念,真的无缘了解这门语言了,谢谢老师给我们思维上的提升,同时也发现自己对这门语言的理解上,上了一个大大的台阶。在此由衷的感谢,真的感觉,遇到了恩师。

    作者回复: 😀+🤝

    2020-01-03
    2
  • weineel
    老师的每一篇都很有深度。我们平时开发中,this 的动态绑定虽然很复杂,但时间长了也能找到规律,仅仅是应用还是没啥问题的。老师要是有时间给我们加个餐,聊聊 this 的深层原理吧。

    作者回复: 这个可以有。我考虑一下怎么做到下一个课程吧。这一课结束哒所以也不再有加餐哒。^^.

    2020-01-05
    1
  • 大雄不爱吃肉
    专栏虽然是二十多讲,但是自己看了很久,很多地方反复看反复试。可能最终记住的不是很多,但对js以及语言规范有了深刻的理解,感谢老师这门课,这一门独特的课程我收获颇多,期待老师的下一门课!

    作者回复: 多谢多谢。有收获就好。:)

    2021-04-01
  • igetter
    老师,问一个不太相关的问题: MDN中说,Function()比eval()更高效。这是真的吗?

    作者回复: 是的。 Function(x)工作在全局,所以它的作用域层次通常要小于eval(x)。因为作用域(链)的深度小,所以Function()执行要略高效。 如果只是说对代码文本`x`的解析和处理等,两者并没有明显的性能区别。

    2020-06-14
  • James
    老师,我从头听了一遍,有几篇文章听了好几遍,但是感觉完全是云里雾里,没弄懂。我应该怎么办。🤣

    作者回复: 补一些基础,再看。建议边读ECMAScript边看。加餐里面我有给地址~找找哇😀

    2020-02-05
    2
  • 许童童
    一路跟着老师走过来,自以为对JavaScript这门语言有一定的了解,才发现只是懂点皮毛,更多深入的知识自己都还没有探究到,感谢老师带我领会了更深刻的JavaScript。之后还是会持续学习,保持对JavaScript的敬畏之心,加油。

    作者回复: 能对大家有用就好。我一直以这样的态度来做这件事,那怕能帮助一人,也是好的。多谢你的支持。^^.

    2020-01-04
  • 独钓寒江雪
    以前有碰到了这样一个疑惑,看了专栏前面的内容,还是不太明白。下面是我的代码,虽然问题比较好解决,但是不太明白: import { message } from 'antd' // 引入AntD组件库中的message export const generateRemark = (skus, message) => {// 这个方法被导出,接收两个参数,其中一个写成了message let remark = '' ...... remark = remark + (message || '')// 使用了message参数 return remark } 当我调用generateRemark(skus, '')时(message传入的是空字符串),返回是[object Object],调试发现,原来message被解析成了antd的message组件了。 是代码环境的问题还是JS底层机制的问题呢?希望老师能帮我解惑,谢谢🙏 最后,也感谢老师的专栏,这样关注底层核心原理的专栏,正是我这种自学前端出道的同学的所需要的。

    作者回复: 仔细读了几遍你的问题,我觉得这是不可能出现的。但还是小心地写了一个测试来运行了一下,但是还原不了你说的问题。(代码放在后面,你看看是不是这个意思) 我仔细想了一下,非常可疑的事情出来你使用import/export的方法上面。由于NodeJS在一般模式下并不支持ES Module,因此通常我们在应用环境中使用模块的时候,都是用babel来转码的。而早期babel(也包括其它的一些第三方转码器)可能对某些语法支持得不好,所以转出来的结果跟ECMAScript规范并不一致,做不到百分百地兼容。并且,在你的示例中还有一个箭头函数,这个东西在很多转码器和基于转码器的runtime中还是实现得不好的。 所以简单地说,我怀疑是你在应用环境中使用babel或typescript之类的转码器带来的结果。无论如何,ECMAScript的规范中不会有这个问题。如下例: ``` # 运行 > node --experimental-modules t1.mjs // 代码t1.mjs import { generateRemark } from './t2.mjs'; var skus = ''; console.log(generateRemark(skus, '')); // 代码t2.mjs import { message } from './t3.mjs' export const generateRemark = (skus, message) => { let remark = '' // ...... console.log(typeof message, message); remark = remark + (message || '') return remark } // 代码t3.mjs export var message = {}; ```

    2020-01-03
    2
  • K4SHIFZ
    动态函数创建在规范19.2.1.1.1 Let proto be ? GetPrototypeFromConstructor(newTarget, fallbackProto). Let realmF be the current Realm Record. Let scope be realmF.[[GlobalEnv]]. Let F be ! OrdinaryFunctionCreate(proto, sourceText, parameters, body, non-lexical-this, scope). Perform SetFunctionName(F, "anonymous")
    2020-05-03
收起评论
显示
设置
留言
10
收藏
沉浸
阅读
分享
手机端
快捷键
回顶部