JavaScript核心原理解析
周爱民
《JavaScript语言精髓与编程实践》作者,南潮科技(Ruff)首席架构师
立即订阅
3733 人已学习
课程目录
已完结 27 讲
0/3登录后,你可以任选3讲全文学习。
开篇词 (1讲)
开篇词 | 如何解决语言问题?
免费
从零开始:JavaScript语言是如何构建起来的 (5讲)
01 | delete 0:JavaScript中到底有什么是可以销毁的
02 | var x = y = 100:声明语句与语法改变了JavaScript语言核心性质
03 | a.x = a = {n:2}:一道被无数人无数次地解释过的经典面试题
04 | export default function() {}:你无法导出一个匿名函数表达式
05 | for (let x of [1,2,3]) ...:for循环并不比使用函数递归节省开销
从表达式到执行引擎:JavaScript是如何运行的 (6讲)
06 | x: break x; 搞懂如何在循环外使用break,方知语句执行真解
07 | `${1}`:详解JavaScript中特殊的可执行结构
08 | x => x:函数式语言的核心抽象:函数与表达式的同一性
09 | (...x):不是表达式、语句、函数,但它却能执行
10 | x = yield x:迭代过程的“函数式化”
11 | throw 1;:它在“最简单语法榜”上排名第三
从原型到类:JavaScript是如何一步步走向应用编程语言的 (6讲)
12 | 1 in 1..constructor:这行代码的结果值,既可能是true,也可能是false
13 | new X:从构造器到类,为你揭密对象构造的全程
14 | super.xxx():虽然直到ES10还是个半吊子实现,却也值得一讲
15 | return Object.create(new.target.prototype):做框架设计的基本功:写一个根类
16 | [a, b] = {a, b}:让你从一行代码看到对象的本质
17 | Object.setPrototypeOf(x, null):连Brendan Eich都认错,但null值还活着
从粗通到精通的进阶之路:唯一不变的是变化本身 (5讲)
18 | a + b:动态类型是灾难之源还是最好的特性?(上)
19 | a + b:动态类型是灾难之源还是最好的特性?(下)
20 | (0, eval)("x = 100") :一行让严格模式形同虚设的破坏性设计(上)
21 | (0, eval)("x = 100") :一行让严格模式形同虚设的破坏性设计(下)
22 | new Function('x = 100')();:函数的类化是对动态与静态系统的再次统一
不定期加餐 (3讲)
加餐 | 捡豆吃豆的学问(上):这门课讲的是什么?
免费
加餐 | 捡豆吃豆的学问(下):这门课该怎么学?
免费
加餐 | 让JavaScript运行起来
免费
结束语 (1讲)
结束语 | 愿你能做一个真正“懂”的程序员
JavaScript核心原理解析
登录|注册

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

周爱民 2020-01-03
你好,我是周爱民,欢迎回到我的专栏。
今天是专栏最后一讲,我接下来要跟你聊的,仍然是 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/1000字
划线
笔记
复制
© 版权归极客邦科技所有,未经许可不得传播售卖。 页面已增加防盗追踪,如有侵权极客邦将依法追究其法律责任。
该试读文章来自付费专栏《JavaScript核心原理解析》,如需阅读全部文章,
请订阅文章所属专栏。
立即订阅
登录 后留言

精选留言(6)

  • 江南逰子
    今天读完了专栏必学内容的最后一讲,跟着老师的步伐一路走来,很艰辛,同时也收获很多,通过一行极简的代码去洞悉一门语言的核心原理,也是我一直梦想着能做到的事,向老师致敬!

    其实,可以说,看这种底层的东西,每一讲都很吃力,要想有更深入的理解,必须再花时间回过头反复研读;其实,阅读专栏,很多时候也是一种思维的提升,比如以前只知道变量提升,却没想过为什么要提升;知道...运算符,却说不出为什么可以用它来展开对象。。。

    或许专栏短期内对开发能力不会有多么显著的提升,但我相信,因为对语言本质的洞悉而产生的自信以及思想层面的提升,将会使我在前端走的更远。衷心的感谢🙏

    作者回复: 🙏

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

    作者回复: 😀+🤝

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

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

    2020-01-05
    1
  • 许童童
    一路跟着老师走过来,自以为对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
    1
收起评论
6
返回
顶部