JavaScript核心原理解析
周爱民
《JavaScript语言精髓与编程实践》作者,南潮科技(Ruff)首席架构师
立即订阅
3529 人已学习
课程目录
已更新 16 讲 / 共 21 讲
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是如何一步步走向应用编程语言的 (1讲)
12 | 1 in 1..constructor:这行代码的结果值,既可能是true,也可能是false
不定期加餐 (3讲)
加餐 | 捡豆吃豆的学问(上):这门课讲的是什么?
免费
加餐 | 捡豆吃豆的学问(下):这门课该怎么学?
免费
加餐 | 让JavaScript运行起来
免费
JavaScript核心原理解析
登录|注册

01 | delete 0:JavaScript中到底有什么是可以销毁的

周爱民 2019-11-11
你好,我是周爱民,感谢你来听我的专栏。
今天是这个系列的第一讲,我将从 JavaScript 中最不起眼的、使用率最低的一个运算——delete 讲起。
你知道,JavaScript 是一门面向对象的语言。它很早就支持了 delete 运算,这是一个元老级的语言特性。但细追究起来,delete 其实是从 JavaScript 1.2 中才开始有的,与它一同出现的,是对象和数组的字面量语法。
有趣的是,JavaScript 中最具恶名的 typeof 运算其实是在 1.1 版本中提供的,比 delete 运算其实还要早。这里提及 typeof 这个声名狼籍的运算符,主要是因为 delete 的操作与类型的识别其实是相关的。

习惯中的“引用”

早期的 JavaScript 在推广时,仍然采用传统的数据类型的分类方法,也就是说,它宣称自己同时支持值类型和引用类型的数据,并且,所谓值类型中的字符串是按照引用来赋值和传递引用(而不是传递值)的。这些都是当时“开发人员的概念集”中已经有的、容易理解的知识,不需要特别地解释。
但是什么是引用类型呢?
在这件事上,JavaScript 偷了个懒,它强行定义了“Object 和 Function 就是引用类型”。这样一来,引用类型和值类型就给开发人员讲清楚了,对象和函数呢,也就可以理解了:它们按引用来传递和使用。
取消
完成
0/1000字
划线
笔记
复制
© 版权归极客邦科技所有,未经许可不得传播售卖。 页面已增加防盗追踪,如有侵权极客邦将依法追究其法律责任。
该试读文章来自付费专栏《JavaScript核心原理解析》,如需阅读全部文章,
请订阅文章所属专栏。
立即订阅
登录 后留言

精选留言(48)

  • 潇潇雨歇
    1、如果x根本不存在,delete x什么也不做,返回true
    2、如果x只读,delete object.x不能删除掉x属性,返回false;如果在严格模式下,会报错:TypeError: Cannot delete property 'c'

    作者回复: 赞的!+1

    其实第1个问题的潜在问题是:这种情况下,x是什么呢?它显然是语法可以识别的东西,但如果这样,在语法上它是什么,且在执行环境中它又是什么?

    而第二个问题的答案,其实也会回到第一个问题上。如果是在严格模式上,第一个问题的答案是什么?并且,为什么它们不同?

    所以,呵呵,其实细一点的看,这两个问题还可以挖更多的呢。^^.

    2019-11-11
    2
    20
  • 海绵薇薇
    老师好,我又来了:-)

    1.

    delete 0

    这里的0是一个值(就当前情况),而不是引用是吗?

    2.

    delete x (x不存在)

    返回true

    x 表达式返回的应该是一个引用,并且环境中并没有表示这个引用不能被删除,这个理解对吗?

    但是文章中有提到delete只能删除属性这一种引用,糊涂了,估计这里的理解还是有问题。

    3.

    delete null 返回true

    delete undefined 返回false 为啥啊?不都是值吗?

    4. 还想知道昨天提问的1和2两条是不是漏洞百出啊,就想知道个结果😁。

    作者回复: Oh~ 哈哈,你是说昨天有一个问题我只回复了3,没有回复1和2两条吗?那两条,是全对的,所以……嗯嗯,我只是没有回复确认而已。你对ECMAScript中的“引用规范类型”的使用场景和过程推演都是正确的。

    关于今天的前3个问题,1是正确的。

    2你也是对的。但是有一点,这个x的确会得到一个引用,称为(UnresolvableReference)。而这一段逻辑在ECMAScript里面写的是“if IsUnresolvableReference, then return true”。也就是说,ECMAScript约定对于这种情况就是这么返回的,这属于规范约定(并且如果在这时发现是严格模式,就抛异常了)。关于这里,你可以看一看:
    https://tc39.es/ecma262/#sec-delete-operator-runtime-semantics-evaluation

    不过问题3,你倒是提到了一个“少有人知”问题,哈哈,这个问题我是漏讲了,而且其实还挺有趣、挺关键的。

    是这样,早期的JavaScript中,undefined是一个特殊值,是在运行期中通过void运算,或者不返回值的函数,又或者一个声明了但未赋值的变量,等等类似这样的情况来“计算得到”的。所以在JavaScript的早期版本中,你没有办法直接判断“undefined是undefined”,例如无法写出“x === undefined”这样的代码,而你只能写类似“typeof(x) ==='undefined'”这样的代码。

    后来(其实也没有太久),规范就约定把undefined作为可以缺省访问的名字,类似于null。但是这个时候就带来了一个矛盾,因为这个undefined很重要,早期的绝大多数框架或引擎都把它作为一个“全局名字”给声明了。也就是说,ECMAScript现在既没有办法将它规范成一个keyword,也没有办法处理成保留字等等,它看起来像null,但又没有办法在规范层面强制它。所以……ECMAScript就搞了一个“奇招”:

    > 我们把undefined声明成全局的属性,怎么样?!

    嗯嗯,很好。所以你看,现在的引擎上面undefined看起来长得跟null值差不多,而且在ECMAScript规范中它们都还是平级的(是原始值),而且它们的作用也很接近,最后他们都还是从最初的JavaScript 1.x中就存在的概念,但是undefined/null两者却在实现上完全不同:undefined是一个全局属性,而null是一个关键字。

    由于undefined是全局属性,所以`delete undefined`其实就是`delete global.undefined`,是删除引用,而不是删除值。而这个属性是只读的,所以就返回false了。

    例如你可以试试下面的代码:

    > Object.getOwnPropertyDescriptor(global, 'undefined')
    { value: undefined,
      writable: false,
      enumerable: false,
      configurable: false }

    2019-11-19
    1
    13
  • 潇潇雨歇
    关于delete的知识,大家可以看下MDN的讲解:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Operators/delete
    以及这篇深入delete博客:http://perfectionkills.com/understanding-delete/

    作者回复: 谢谢 @潇潇雨歇

    2019-11-11
    4
    12
  • 乍一读,云里雾里。翻了文档并做测试,总结如下:

    delete 操作符用于删除对象的属性,它接收一个表达式,该表达式应返回对象属性的引用。

    1. 如果表达式返回的结果是引用:
    当该引用是 let 或 const 定义的,delete 执行结果总是 false;
    当引用作为对象的属性不存在时,delete 对象的属性,执行结果为 true,表示未处理;
    当该引用为 window 对象的属性且是 var 定义的,delete window 对象的属性,执行结果为 false,表示处理失败(获取属性描述符时为不可配置);
    如果在全局环境下显示定义一个属性描述符为可配置的全局属性,执行 delete,结果是 true,表示操作成功;
    当该引用为非 window 对象的属性且是 var 定义的,delete 非 window 对象的属性,执行结果为 true,表示处理成功(获取属性描述符时为可配置)。

    2. 如果表达式返回的结果是值,如数字、字符串等,delete 执行结果为 true,表示未处理。

    作者回复: 其实这一讲的核心是关于“引用/值”在ECMAScript规范类型中的使用与理解,而不是(不仅仅是)delete的使用。所以呢,解释delete这个操作的种种现象,最好是在ECMAScript规范所讨论的语言模型中来叙述,这样更容易讲得清楚。

    比如说,`x`如果是一个属性(包括是global的属性),那么`delete x`的是否成功就取决于属性描述符,以及属性存取的过程(是否在严格模式中等等)。这样就Ok了,而不需要细致地列举每一种情况。

    2019-11-11
    2
    9
  • 隔夜果酱
    既然delete这么鸡肋,只能删除对象的成员.
    那么后来的版本中为什么不进行改进呢?
    比如限定其只能用delete obj.x这种语法格式.
    或者加入trycatch,对删除value的操作直接报错呢?

    作者回复: 这个问题就牵扯得大了。

    最早javascript中是没有明确、显式的global这个对象的,在宿主环境(例如浏览器中)你可以用window.x去访问它,这算是宿主在实现引擎的时候的约定。但是,仅只从引擎的角度上来说,既没有window,也没有global,更没有Global。所以,全局的变量虽然是作为全局属性名存在着,却没有办法写成global.x这样的引用。

    而global这个全局名字,直到现在在ECMAScript中都还是个没被规范的东西。TC39有一个提案(https://github.com/tc39/proposal-global)专门来定义它,现在到了stage3,应该不会被否决了。但即使如此,这个东东也不叫global,而改名成了globalThis。——原本提案阶段是叫global的,但应用中有问题,所以就改了。

    关于globalThis这个说法,又得是一段历史了。因为早期的JavaScript约定普通函数在“不作为对象方法调用”的时候,this值默认指向这个全局的global。所以,这也就是著名的代码“global = (new Function('return this')()”,或“global = Function('return this')()”的由来。

    ^^.

    2019-11-11
    9
  • 海绵薇薇
    hello 老师好,感谢老师之前的回答:)
    突然想到,访问不存在的变量x报ReferenceError错误,其实是对x表达式的的Result引用做getValue的时候报的错误,然后为啥typeof x和delete x不报错,因为这两个操作没有求值。

    作者回复: 强烈点赞!你这个就属于一通百通的例子。弄明白了Result用来做引用和值的方法/原理,一些具体现象就迎刃而解了!

    ^^.

    2019-11-22
    4
    7
  • SOneDiGo
    想问下老师如何理解用delete处理array element实际上在底层是如何操作的?
    例如:array = [1,2,'1']
    为什么 delete array[2] 后数组就成了[1,2,undefined/empty]?

    作者回复: 对于array来说,你理解为一个普通对象就可以了,只是一些array原型上的方法能帮助你处理array.length这个属性而已。
    有多少个有效的element,那么就有多少个同名的(数字下标的)属性;而array.length记录着这个最大值。除了这一点,没有任何与其它对象不同。
    所以你用array.pop()或array.push()等操作,甚至直接使用array[i]都可以影响到array.length这个属性——因为这些操作内部都会处理它。但是,你用delete去根本不会处理这个属性——因为delete是把array[i]当一个一般属性处理的,根本不知道array.length的存在。

    例如:
    ```
    > x = new Array(8)
    > x.length
    8
    > x[x.length] = 8 // add to last
    > x.length
    9
    > x.push(10) // push
    10

    > x.pop() // pop
    > x.length
    9

    > delete x[8]
    true
    > x.length
    9
    ```

    至于删除delete array[2],则array[2]位置上是undefined,这个与delete操作无关。而是因为你“读取一个不存在的属性,它的值就是undefined”。

    2019-11-22
    5
  • 余文郁
    老师,JS是基于对象的语言,不是面象对象的语言吧,感觉第二段这有点不妥,虽然ES6增加了class语法,但只是原型的语法糖而已

    作者回复: 在后面我会再着重地讲到JavaScript对面向对象的理解。

    如今我们对OOP的理解其实添加了太多应用的色彩。事实上,JavaScript对OOP的理解是很精彩、很学术,以及很完整的。不过这些内容大概要到第11讲之后了。

    至于“面向对象”还是“基于对象”,其实JavaScript 1.0是有类而无继承的,而JavaScript 1.1才开始使用原型来实现继承,这个时候它又抛弃了(严格意义上的)类。

    当然,上面看起来有点儿绕着你的问题在讲。所以,如果再确切地、准确无误地回复你的问题,那么应该是说:所谓面向对象的三个原则(封装、继承与多态),在严格意义上,后两者是多余的。所以不必过度去强调这些性质之于面向对象的重要性。

    2019-11-11
    3
    5
  • 仰望星空
    老师的英语发音delete偏差的有点多

    作者回复: 这个这个,惭愧呀惭愧~

    我的口语不是一点半点的糟糕(当然,其实不仅仅只是口语糟糕)。我尽量……注意……后面的课程~ 多谢多谢~

    惭愧呀~

    :(~

    2019-11-11
    5
  • 潇潇雨歇
    看的第三遍。还是要去看看规范加深理解。
    如果x根本不存在,delete x操作时,x首先是一个表达式,语义上是一个引用,然后去寻找该引用的result,但是x根本不存在,是找不到的。也就做不了什么,返回ture。
    如果obj.x是只读的或者不可配置的,表示他是不能删除的,但是他是实实在在的引用,是可以求值得到Result的,所以返回false。表示不能删除。

    作者回复: :)
    +1

    2019-11-16
    4
  • Wiggle Wiggle
    即便 obj.x 是一个 function,当 obj.x 作为右手端时,也会被 GetValue 方法抽取出值来,而这个“值”并不是直觉上的数字或字符串。这里是有恍然大悟的感觉的,“值”和“引用”应当从严格的规范定义层面理解,而不能从直觉上来理解,只要满足定义,那就是“值”/“引用”。

    作者回复: 赞的!就是这样!

    2019-11-12
    4
  • 渭河
    这句话要怎么理解呀
    所谓值类型中的字符串是按照引用来赋值和传递引用(而不是传递值)的

    作者回复: 这就是“传统中的‘引用’”用来解释这类现象的时候出现的麻烦。很典型的一个例子,话表达的是正确的,内容是正确的,说法也正确,就是特别特别难于理解。

    首先,“值类型中的字符串”是指什么呢?是指typeof(x) === 'string'中的那个`x`。在传统的javaScript概念中,这样的x是值类型,而不是引用类型。

    那么值“该怎么赋值和传递”呢?如果x的值是1,那么y = x的话,就是把1这个值“抄写”到y里面去。这是“正常的值”的处理方法,但是如果“字符串值”也这么处理,就完蛋了,因为字符串可能无数多个字符,那么当`y = x`按照“正常的值处理方法”来实现的话,这个“值的复制”的开销就受不了。

    所以:
    1. “值类型中的字符串”,是指照
    2. “引用来赋值和传递引用”的;且,
    3. 它是只传递引用(而不是传递值)的。

    如上。只是说起来特别麻烦而已。

    2019-11-21
    3
  • 海绵薇薇
    感谢老师指点😁

    ref:语法上的引用

    我又看了几遍文章并根据提供的连接,得出如下结论:

    1.

    var x

    x = 0
    console.log(x)

    x 表达式返回的是一个ref({referencedName: 'x', base: Environment Record}),然后计算值getValue(ref)得到具体的值,具体的值会分为传统意义上的基本类型和引用类型

    2. 衍生出下面的猜想

    var obj

    obj = {a: 1}

    console.log(obj.a)

    obj.a 也是一个ref({referencedName: 'a', base: obj}),然后计算值的时候getValue(ref)得到具体的值1

    3. 关于表达式的结果Result的疑问。

    文中说:表达式的值,在 ECMAScript 的规范中,称为“引用”。(表达式的结果(Result)是引用。)

    但是后文说Result可能是引用/值。

    这里的值我不能很好的理解。值指的是另一种引用的格式吗?例如链接文档中提到的base其实有很多种值 undefined, Object, a Boolean, a String, a Symbol, a Number。值指的是{base: 0}这种引用吗?如果不是这样的话base的Boolean等基本值类型有啥用啊?

    还是说 0 这个表达式的Result就是0这个值?

    期待老师的指点😁

    作者回复: 关于3,我一般是用Result来表达它是表达式执行结果的“未决状态”。就是执行出结果来了,但没确定是作为lrs还是rhs,所以这种情况下,它就是未决的。

    当你确定了一个Result用作lrs,那么它就是引用;如果确定它用作rhs,那么它就是值(将由引擎隐式地调用`GetValue()`)。

    2019-11-18
    3
  • Mr_Liu
    思考题1:delete x x不存在返回的是true
    2: 删除会返回false,严格模式会报错
    第一遍听感觉有些云里雾里的感觉,又听了一遍加实践。但是有一点不理解或者不知道理解的对不对,希望老师解答一下
    问题一:
    例如var a = '123' delete a 返回的是false ,
    再次输入a 得到结果依然是 ‘123’,
    这是说明delete 没有起作用,其没有起作用的原因是因为 var a = '123' 中的a 是基本数据类型,不是引用类型,所以删除a 元素失败,借此印证了所讲的delete 删除的是表达式或者引用类型的结果。印证这句话的另一个例子是:
    var obj = {
       a: '123'
    },
    var b = obj.a
    delete b 返回false , 因为b = obj.a 属于一个赋值语句,b 也是个基本数据类型,所以也不起作用
    那么修改成
    var obj = {
    a: '123',
    b: {
    name: '123'
    }
    }
    var val = obj.b
    deletet val 返回的依然是false 后来会读了一下,有这样一句话:delete 其实只能删除一种引用,即对象的成员(Property)
    那么 delete x 还有什么存在的意义么。

    问题二:
    接着我使用delete obj.a 返回的是true ,再次输入 obj.a 返回的就是undefined
    但如果我使用
    var val = obj.b
    delete obj.b 返回的是true
    然后打印 obj.b 为undefined; val 为 {name: '123'}
    ,那老师的那句delete实际上是删除一个表达式的、引用类型的结果(Result),而不是在删除 x 表达式,或者这个删除表达式的值(Value)。是否可以理解为实际是是删除一直引用呢。

    作者回复: 问题1中,你的思考方向错了。`delete a`不起作用的原因是`var`声明导致的,而不是因为`a 是基本数据类型`。举例来说,

    `with (x = {a: 100}) delete a;`

    这个例子的结果中x.a是不存在的,但`a 也是基本数据类型`呀。所以是无关的。

    “delete x 还有什么存在的意义么”这个问题我之前回复过另一个留言,你找找。

    关于问题二,关键在于你所理解的“引用与值”,跟JavaScript内部所理解的“引用与值”是不一样的。也正是因此,我在这一讲的一开始用大量文字讨论了二者的区别。简单地来说,如果有表达式`x = x`,那么同一个变量`x`,在上述表达式中,左侧的这个是它的引用,左侧的是它的值。如果放在代码中看:

    x = 5; // 在JavaScript语言中,'5'是“值类型”
    x = x; // 在ECMAScript规范中,左侧是“引用x”,右侧是“值x”。

    我一直用“结果(Result)”来强调表达式“表达式计算的结果”,就是因为对于`x = x`来说,左侧和右侧都是表达式,左侧的结果是“lhs/引用(reference)”,而右侧的结果是“rhs/值(value)”。

    所以所谓“结果(Result)”,在不明确它的手性或用处之前,是二个意思都包含的。

    2019-11-12
    1
    3
  • 半橙汁
    在《你不知道JavaScript-上》中,看到过关于lhs和rhs的相关介绍,涉及到很多编译,语法解析的知识,真的很难肯...
    希望通过对老师专栏的学习,能够更加顺畅地去啃另外的中、下两本😂😂😂

    作者回复: 那三本书很不错!值得一读。^^.

    2019-11-11
    3
  • Smallfly
    在 ECMAScript 规范中,引用的构成至少需要 base value、referenced name、strict reference flag,具体引擎实现应该会把它们封装成一种数据结构来,从而来操作引用。

    而文中把 x = x 中的 x 叫做一个引用,应该不是很精确,x 只是引用的 referenced name。

    因为我们代码层面无法获取引用,也就没有名字,所以这里用 x 指代规范中的引用。

    请问老师这样理解对么?

    作者回复: > 在 ECMAScript 规范中,引用的构成至少需要 base value、referenced name、strict reference flag,具体引擎实现应该会把它们封装成一种数据结构来,从而来操作引用。
    在ECMAScript中,它就是一种数据结构啊。我们称byte/word/array/map为数据结构,为什么“Specification Types”就不是呢?你看ECMAScript规范里面,“Specification Types”就是在“Data Types”这一章中的一节啊。

    > 而文中把 x = x 中的 x 叫做一个引用,应该不是很精确,x 只是引用的 referenced name。
    不对。左侧的x就是引用,而不仅仅是referenced name。只是代码文本(在静态读代码的情况下)它是个名字x而已。

    > 因为我们代码层面无法获取引用,也就没有名字,所以这里用 x 指代规范中的引用。
    代码层面是可以获取所谓“引用”的,而且是“规范类型”中的引用。例如delete obj.x中的`obj.x`整体上就是一个引用。

    2019-11-27
    2
  • 杨尚坤
    写的很好

    作者回复: 多谢。我努力~努力~持续努力~

    ^^.

    2019-11-11
    2
  • 文全
    作为一个工作几年的前端,急需这部分底层原理
    2019-11-11
    1
    2
  • Smallfly
    看了其他同学的答案和老师的回答,感觉挺疑惑的,属性能否被 delete 是由 configurable 决定的,跟是否只读没有关系吧?

    const object1 = {};

    Object.defineProperty(object1, 'p', {
      value: 42,
      writable: false,
      configurable: true,
    });

    delete object1.p

    console.log(object1.p); // undefined
     

    作者回复: 是。writable不决定是否删除。属性表像一个数据库的表一样,可以增删查改,显然writable决定的是“改”,而不是“删”。

    2019-11-27
    1
  • 穿秋裤的男孩
    老师,undefined作为window的属性不可删除,然后delete undefined返回false我理解了;但是为什么delete null会返回true呢?null不是一个值吗?

    作者回复: `delete null`和`delete 0`在性质上没什么区别呀。所以返回true不是正常的么?

    2019-11-27
    1
    1
收起评论
48
返回
顶部