探究 JavaScript 语法特性:为什么函数是「一等公民」?
本文最后更新于:2023-04-08 17:09:22 UTC+08:00
什么是 JavaScript
定义
JavaScript(JS)是一种具有函数优先 (First-class Function) 特性的轻量级、解释型或者说即时编译型的编程语言。虽然作为 Web 页面中的脚本语言被人所熟知,但是它也被用到了很多非浏览器环境中,例如 Node.js、Apache CouchDB、Adobe Acrobat 等。进一步说,JavaScript 是一种基于原型(Prototype-based)、多范式、单线程的动态语言,并且支持面向对象、命令式和声明式(如函数式编程)风格。
这里的名词有点多……后面我们一个一个来解释。
JavaScript 与 ECMAScript
JavaScript 的核心语言是 ECMAScript,是一门由 ECMA TC39 委员会标准化的编程语言。
1996 年 11 月,JavaScript 的创造者 Netscape 公司,决定将 JavaScript 提交给标准化组织 ECMA,希望这种语言能够成为国际标准。次年,ECMA 发布 262 号标准文件(ECMA-262)的第一版,规定了浏览器脚本语言的标准,并将这种语言称为 ECMAScript,这个版本就是 1.0 版。
在 ECMAScript 第 6 版(称为 ES6)之前,规范是几年发布一次,通常用它们的主要版本号来指代(ES3、ES5 等)。在 ES6 之后,规范以发布年份命名(ES2017、ES2018 等)。ES6 是 ES2015 的代名词[1]。ESNext 是一个动态名称,指的是撰写本文时的下一个版本。ESNext 中的特性更准确地称为提案,因为根据定义,规范尚未最终确定。
任何人都可以向 ECMA TC39 委员会提案,要求修改语言标准。提案仓库:https://github.com/tc39/proposals
一种新的语法从提案到变成正式标准,需要经历五个阶段。每个阶段的变动都需要由 TC39 委员会批准。
- Stage 0 - Strawman(展示阶段)
- Stage 1 - Proposal(征求意见阶段)
- Stage 2 - Draft(草案阶段)
- Stage 3 - Candidate(候选人阶段)
- Stage 4 - Finished(定案阶段)
一个提案只要能进入 Stage 2,就差不多肯定会包括在以后的正式标准里面。
讲 JavaScript 就不得不讲 ES6,下文也会穿插 ES6 的一些特性。
First-class Function 函数优先
函数是 JavaScript 的一等公民。
函数优先定义:是一种把函数视为变量的编程语言,函数可以被当作参数传递给其他函数,可以作为另一个函数的返回值,还可以被赋值给一个变量。
在 JavaScript 中,函数即对象 (Object
),每个 JavaScript 函数实际上都是一个 Function 对象。 1
console.log((function(){}).constructor === Function); // true
1 |
|
上面例子中 func()
函数返回了一个「匿名函数」,匿名函数还有另外一种创建方式:
1 |
|
第二个括号用于调用该匿名函数。
ES6 中,可以使用箭头函数使代码更简洁。
1 |
|
匿名函数大量用于「回调函数」。回调函数指被传递给另一个函数作为参数的函数叫作回调函数。
1 |
|
JavaScript 中典型的全局方法 setTimeout()
就使用了回调函数,该方法会在给定的时间段(以毫秒为单位)后调用函数或计算表达式。
1 |
|
Prototype-based 基于原型
定义:基于原型编程是面向对象编程的一种风格和方式。在原型编程中,行为重用(在基于类的语言通常称为继承),是通过复制已经存在的原型对象的过程实现的。在这种风格中,我们不会显式地定义类 ,而会通过向其他类的实例(对象)中添加属性和方法来创建类,甚至偶尔使用空对象创建类。
简单来说,这种风格是在不定义 class
的情况下创建一个 object
。
1 |
|
当我们调用一个对象的属性时,如果对象没有该属性,JavaScript 解释器就会从对象的原型对象上去找该属性,如果原型上也没有该属性,那就去找原型的原型,直到最后返回 null
为止,null
没有原型。这种属性查找的方式被称为原型链 (prototype chain)。
所有 JavaScript 中的对象都是位于原型链顶端 Object
的实例。
1 |
|
若此时再实例化一个新的变量:
1 |
|
严格相等比较 ===
(strict equality) 用于比较两个值是否全等,包括值与类型。
Dynamic Typing 动态类型
动态类型定义:解释器在运行时根据变量当时的值为变量分配类型的语言。
动态编程语言定义:一类在运行时可以改变其结构的语言,例如新的函数、对象、甚至代码可以被引进,已有的函数可以被删除或是其他结构上的变化。动态语言目前非常具有活力。
JavaScript 是一种具有动态类型的动态语言。
1 |
|
JavaScript 也是一种弱类型语言,这意味着当操作涉及不匹配的类型时它允许隐式类型转换,而不是抛出类型错误。
1 |
|
null
与 undefined
从概念上讲,undefined
表示没有任何值,null
表示没有任何对象。以下使一些 undefined
的情况:
- 没有值的 return 语句(即
return;
),隐式返回undefined
。 - 访问不存在的对象属性(
obj.iDontExist
),返回undefined
。 - 变量声明时没有初始化(
var x;
),隐式初始化为undefined
。 - 许多如
Array.prototype.find()
和Map.prototype.get()
的方法,当没有发现元素时,返回 undefined。
null
在核心语言中使用频率少得多。最重要的地方是原型链的末端,其次是与原型交互的方法 (methods that interact with prototypes),如 Object.getPrototypeOf()
、Object.create()
等,接受或返回 null
而不是 undefined
。
可选链运算符 ?.
可选链运算符 ?.
允许读取位于连接对象链深处的属性的值,而不必明确验证链中的每个引用是否有效。?.
运算符的功能类似于链式运算符 .
,不同之处在于,在引用为空(null
或者 undefined
)的情况下不会引起错误,该表达式短路返回值是 undefined
。与函数调用一起使用时,如果给定的函数不存在,则返回 undefined
。
当尝试访问可能不存在的对象属性时,可选链运算符将会使表达式更短、更简明。在探索一个对象的内容时,如果不能确定哪些属性必定存在,可选链运算符也是很有帮助的。
1 |
|
JavaScript 中的相等性判断
JavaScript 提供三种不同的值比较操作:
- 严格相等比较 ("strict equality", "identity", "triple equals"),使用
===
。全等操作符比较两个值是否相等,两个被比较的值在比较前都不进行隐式转换。如果两个被比较的值具有不同的类型,这两个值是不全等的。否则,如果两个被比较的值类型相同,值也相同,并且都不是number
类型时,两个值全等。最后,如果两个值都是number
类型,当两个都不是NaN
,并且数值相同,或是两个值分别为+0
和-0
时,两个值被认为是全等的。 - 抽象相等比较 ("loose equality", "double equals") ,使用
==
。相等操作符比较两个值是否相等,在比较前将两个被比较的值转换为相同类型。在转换后(等式的一边或两边都可能被转换),最终的比较方式等同于全等操作符===
的比较方式。相等操作符满足交换律。 Object.is
(ES6),几乎与===
相同,仅在比较+0
和-0
,Nan
和NaN
时有区别。
x | y | == |
=== |
Object.is |
---|---|---|---|---|
undefined |
undefined |
true |
true |
true |
null |
null |
true |
true |
true |
true |
true |
true |
true |
true |
false |
false |
true |
true |
true |
"foo" |
"foo" |
true |
true |
true |
0 |
0 |
true |
true |
true |
+0 |
-0 |
true |
true |
false |
0 |
false |
true |
false |
false |
"" |
false |
true |
false |
false |
"" |
0 |
true |
false |
false |
"0" |
0 |
true |
false |
false |
"17" |
17 |
true |
false |
false |
[1,2] |
"1,2" |
true |
false |
false |
new String("foo") |
"foo" |
true |
false |
false |
null |
undefined |
true |
false |
false |
null |
false |
false |
false |
false |
undefined |
false |
false |
false |
false |
{ foo: "bar" } |
{ foo: "bar" } |
false |
false |
false |
new String("foo") |
new String("foo") |
false |
false |
false |
0 |
null |
false |
false |
false |
0 |
NaN |
false |
false |
false |
"foo" |
NaN |
false |
false |
false |
NaN |
NaN |
false |
false |
true |
Promise
同步:顺序执行,执行完一个再执行下一个,需要等待、协调运行。
异步:异步和同步是相对的,异步就是彼此独立,在等待某事件的过程中继续做自己的事,不需要等待这一事件完成后再工作。
注意:
- 线程是实现异步的一个方式。可以在主线程创建一个新线程来做某件事,此时主线程不需等待子线程做完而是可以做其他事情。
- 异步和多线程并不是一个同等关系。异步是最终目的,多线程只是我们实现异步的一种手段。
所谓 Promise
,简单说就是一个容器,里面保存着某个未来才会结束的事件(通常是一个异步操作)的结果。从语法上说,Promise
是一个对象,从它可以获取异步操作的消息。Promise
提供统一的 API,各种异步操作都可以用同样的方法进行处理。
Promise
对象有以下两个特点。
对象的状态不受外界影响。
Promise
对象代表一个异步操作,有三种状态:pending
(进行中)、fulfilled
(已成功)和rejected
(已失败)。只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态。这也是Promise
这个名字的由来,它的英语意思就是「承诺」,表示其他手段无法改变。一旦状态改变,就不会再变,任何时候都可以得到这个结果。
Promise
对象的状态改变,只有两种可能:从pending
变为fulfilled
和从pending
变为rejected
。只要这两种情况发生,状态就凝固了,不会再变了,会一直保持这个结果,这时就称为resolved
(已定型)。如果改变已经发生了,你再对Promise
对象添加回调函数,也会立即得到这个结果。
春招笔试题手搓了一个非常绕的题...
1 |
|
流程:
Promise
新建后会立刻执行,所以分别输出3
、5
reslove()
和reject()
会等待该函数执行完毕后再返回结果,所以6
也会直接输出- 接下来
.then
和.catch
并没有直接被执行,而只是定义了Promise
返回后的操作(设置了回调函数),所以直接输出7
- 500ms 后
p1
响应,输出2
,此时p1
的状态从pending
转到rejected
- 1000ms 后
p2
响应,输出4
,resolve(p1)
表示返回p1
状态结果,所以也为rejected
- 最后
p3
等到p2
返回rejected
而返回rejected
延伸阅读
- https://llh911001.gitbooks.io/mostly-adequate-guide-chinese/content/
- https://developer.mozilla.org/en-US/docs/Web/JavaScript
- https://es6.ruanyifeng.com/
- https://ts.xcatliu.com/
参考
- https://developer.mozilla.org/zh-CN/docs/Web/JavaScript
- https://es6.ruanyifeng.com/#docs/intro
- https://www.freecodecamp.org/chinese/news/javascript-callback-functions/
- https://zh.wikipedia.org/wiki/%E5%9F%BA%E4%BA%8E%E5%8E%9F%E5%9E%8B%E7%BC%96%E7%A8%8B
- https://zhuanlan.zhihu.com/p/35458229
- https://zh.wikipedia.org/wiki/%E5%8A%A8%E6%80%81%E8%AF%AD%E8%A8%80
- https://developer.mozilla.org/en-US/docs/Web/JavaScript/Data_structures
- https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Equality_comparisons_and_sameness
- https://cloud.tencent.com/developer/article/1829301
- https://es6.ruanyifeng.com/#docs/promise
- 也有说法称 ES6 是一个泛指,指 ES5.1 版以后的 JavaScript 的下一代标准,涵盖了 ES2015、ES2016、ES2017。 ↩︎