共计 10124 个字符,预计需要花费 26 分钟才能阅读完成。
前言
作为程序员,技术的落实与巩固是必要的,因此想到写个系列,名为 why what or how
每篇文章试图解释清楚一个问题。
这次的 why what or how
主题:什么是 JavaScript
?
释义
JavaScript
– 一种解释性脚本语言
解释性脚本语言:一类不具备开发操作系统的能力,而是只用来编写控制其他大型应用程序的“脚本”,但其内容的的执行不需要提前编译的语言。通常作为别的程序的输入。
JavaScript
,是一种用于描述网页逻辑,处理网页业务的解释性脚本语言,纯文本,其内容作为浏览器的输入,浏览器负责解释编译运行其内容。
目前 JavaScript
也成功被应用于服务端,服务端的 JavaScript
用于描述业务逻辑,其内容作为 node
的输入,node
负责解释编译运行其内容。
JavaScript
是伴随着浏览器出现的一门特殊的语言,特殊在哪呢?JavaScript
是唯一一门所有浏览器都支持的脚本语言,也就说如果你的 WEB
程序想在客户端做点事,就一定会用到 JavaScript
。别看现在 JavaScript
炙手可热,但它最开始出现却仅为了验证表单。
历史
-
1990 - 1994
,虽然各种浏览器开始出现,但浏览器仅用作数据展示,并没有客户端逻辑存在。 -
1994
,Netscape
公司计划实现一种浏览器脚本语言进行一些简单的表单验证。 -
1995
,Netscape
公司雇佣了程序员Brendan Eich
开发这种网页脚本语言。 -
1995.5
,Brendan Eich
用了10
天,设计完成了这种语言的第一版。取名为:Mocha
。 -
1995.9
,Netscape
公司将该语言改名为LiveScript
。 -
1995.12
,Netscape
与Sun
公司联合发布了JavaScript
语言。 -
1996.3
,Navigator 2.0
浏览器正式内置了JavaScript
脚本语言。 -
1996.8
,微软发布Internet Explorer 3.0
,同时发布了JScript
,该语言模仿同年发布的JavaScript
。 -
1996.11
,Netscape
公司在浏览器对抗中没落,将JavaScript
提交给国际标准化组织ECMA
,希望JavaScript
能够成为国际标准,以此抵抗微软。 -
1997.7
,ECMAScript 1.0
发布。ECMAScript
是一种标准,而JavaScript
是该标准的一种实现。 -
1997.10
,Internet Explorer 4.0
发布,其中的JScript
基于ECMAScript 1.0
实现。 -
1999
,IE 5
部署了XMLHttpRequest
接口,允许JavaScript
发出HTTP
请求,为后来Ajax
应用创造了条件。 -
1999.12
,ECMAScript 3.0
版发布,得到了浏览器厂商的广泛支持。 -
2005
,Ajax
方法(Asynchronous JavaScript and XML
)正式诞生,Google Maps
项目大量采用该方法,促成了Web 2.0
时代的来临。 -
2006
,jQuery
函数库诞生,作者为John Resig
。jQuery
统一了不同浏览器操作DOM
的不同实现,被广泛使用,极大降低了JavaScript
语言的应用成本,推动了语言的流行。 -
2007.10
,ECMAScript 4.0
草案发布,对3.0
版做了大幅升级,但由于改动幅度过大,遭到了各大浏览器厂商的反对。 -
2008.7
,各大厂商对ECMAScript 4.0
版本的开发分歧太大,ECMA
开会决定,中止ECMAScript 4.0
的开发,对其中一些已经改善的部分发布为ECMAScript 3.1
。不久之后就改名为ECMAScript 5
。 -
2008
,由google
开发的V8
编译器诞生。极大提高了JavaScript
的性能,为之后node
的诞生打下了基础。 -
2009
,Node.js
项目诞生,创始人为Ryan Dahl
,JavaScript
正式应用于服务端,以其极高的并发进入人们的视野。 -
2009.12
,ECMAScript 5.0
正式发布。同时将标准的设想定名为JavaScript.next
和JavaScript.next.next
两类。 -
2010
,NPM
和RequireJS
出现,标准着JavaScript
进入模块化。 -
2011.6
,ECMAscript 5.1
发布,并且成为ISO
国际标准(ISO/IEC 16262:2011
)。 -
2012
,单页面应用程序框架(single-page app framework
)开始崛起,AngularJS
项目出现。 -
2012
,所有主要浏览器都支持ECMAScript 5.1
的全部功能。 -
2012
,微软发布TypeScript
语言。为JavaScript
添加了类型系统。 -
2013
,ECMA
正式推出JSON
的国际标准,这意味着JSON
格式已经变得与XML
格式一样重要和正式了。 -
2013.2
,Grunt.js
前端构建化工具发布,前端进入自动化开发阶段。 -
2013.5
,Facebook
发布UI
框架库React
。 -
2013.8
,Gulp.js 3.0
前端构建工具发布,js
自动化开发变得简单起来。 -
2014
,尤雨溪发布VUE
项目。 -
2015.3
,Facebook
公司发布了React Native
项目,将React
框架移植到了手机端,用来开发手机App
。 -
2015.3
,babel 5.0
发布,ES6
代码正式引用于开发,而不需要考虑兼容问题。 -
2015.6
,ECMAScript 6
正式发布,并且更名为ECMAScript 2015
。 -
2015.6
,Mozilla
在asm.js
的基础上发布WebAssembly
项目。 -
2015.9
,Webpack 1.0
发布,模块系统得到广泛的使用。 -
2016.6
,ECMAScript 2016(ES7)
标准发布。 -
2017.6
,ECMAScript 2017(ES8)
标准发布,引入了async
函数,使得异步操作的写法出现了根本的变化。 -
2017.11
,所有主流浏览器全部支持WebAssembly
,这意味着任何语言都可以编译成JavaScript
,在浏览器中运行。 -
2018.6
,ECMAScript 2018(ES9)
标准发布。 -
2019.6
,ECMAScript 2019(ES10)
标准发布。 - …
纵观发展历史,很容易就能发现其中几个关键点的出现,极大的促进了 JavaScript
的发展。
-
XMLHttpRequest
接口的出现,JavaScript
开始有了与服务器沟通的能力,诞生了ajax
,同时也促进了RestFul API
的发展。 -
jQuery
函数库诞生,极大的降低了网页开发成本。 -
V8
的出现,降低了JavaScript
消耗的资源,加快了编译速度,复杂的JavaScript
程序开始出现。 -
Node.js
项目诞生,JavaScript
开始应用在服务端。 -
NPM
和RequireJS
出现,JavaScript
正式进入模块化。 -
Grunt
Gulp
Webpack
这些自动化构建工具的出现,极大的降低了开发成本,统一了各种内容的构建方式。 -
Babel
的出现彻底让前端程序员放开了手脚,毫无顾忌的使用ES6
而无须担心平台的兼容性问题。 -
Angular
React
Vue
的出现了,进一步降低了网页开发成本,单页应用开始出现。 -
ECMAScript 6
标准的公布以及实现,弥补了JavaScript
的语言缺陷,JavaScript
更好用了。
直到目前,JavaScript
已经成为了 GitHub
上最热门的语言,从前端到后端在到桌面,都有 JavaScript
的身影,但 JavaScript
语言本身的内容并不多,那么 JavaScript
语言本身都有哪些内容?
语法 & 标准库
JavaScript
的语法,这里仅罗列一些概念,具体的可以查看 JavaScript 开发文档。
数据类型
在 JavaScript
中所有的数据都有类型,如下所示:
类型 | 定义 | 示例 | 含义 |
---|---|---|---|
null |
空值 | let a = null |
一个特殊的空值 |
undefined |
该值未定义 | let a |
该值声明了但未定义 |
Number |
数值 | let a = 1 |
包括整数、浮点数、科学计数等所有数字 |
String |
字符串 | let a = '1' |
单个字符也是字符串 |
Boolean |
布尔值 | let a = true |
仅有 true 和 false ,代表真假 |
Symbol |
符号 | let a = Symbol(42) |
一类永远都不会相同的值,常用作对象的 key
|
Object |
对象 | let a = {a: 1} |
一类拥有多个键值对的数据 |
在 JavaScript
中数据类型除了可以按照上述的分类方式,还可以简单的分为:基本数据类型(除了 Object
的其他类型)和引用类型(Object
),这和变量的存储方式有关,这里不过于深入,但 JavaScript
数据是如何存储的?之后会写,请持续关注~
运算符 & 流程 & 声明
- 运算符
- 算术
+
-
*
/
**
%
++
--
- 比较
>
>=
<
<=
==
!=
===
!==
- 布尔运算
!
||
&&
?:
- 二进制运算
|
&
~
^
<<
>>
>>>
在进行算术、比较、布尔运算时,会出现类型转换,会有一些怪异的表现,那么 JavaScript
是如何进行类型转换的?之后会写,请持续关注~
- 流程
for(let i = 0; i < n; i++){...}
for(let key in obj){...}
for(let value of array){...}
while(true){...}
do{...}while(true)
if(true){...}else if(true){...}else{...}
switch(a){case() ... }
和大多数的语言一样,都有类似的流程语句。仅需要注意 for...of
循环即可。
- 声明
- 常量 –
const
- 变量 –
let
orvar
(不建议使用) - 同步函数 –
function
- 异步函数 –
async function
- Generator 函数 –
function*
ok,JavaScript
的基础知识就差不多也就这些,这里仅是罗列,并不过多的深入,有兴趣或是不了解的可以在 JavaScript 教程,进行学习。
标准库
何为标准库?
在了解 JavaScript
发展史后,我们都知道,EcmaScript
是 JavaScript
语言实现的标准,标准除了规定基本的语法外,还定义了一些列 JavaScript
执行环境应该有的对象或是函数,这些与语法无关的其他内容,就被称为标准库。标准库主要包含以下内容(一些已废弃或不推荐使用的就不罗列了):
- 全局函数
- eval()
- isFinite()
- isNaN()
- parseFloat()
- parseInt()
- decodeURI()
- decodeURIComponent()
- encodeURI()
- encodeURIComponent()
- …
- 全局对象
- Number
- String
- Boolean
- Symbol
- Object
- Function
- Error
- Math
- Date
- RegExp
- Array
- Map
- Set
- JSON
- Promise
- Generator
- GeneratorFunction
- AsyncFunction
- Proxy
- Reflect
- WebAssembly
- …
具体内容可以点击查看,这里仅是罗列出一些常用的函数以及对象,一些对象如果不操作视频、音频、图像等内容也用不到,这里就不写了。
宿主环境
JavaScript
作为一门解释性脚本语言,其内容只能作为别的程序的输入,而这个别的程序,就是宿主环境。那么宿主环境为 JavaScript
提供了什么呢?
- 语法支持,宿主环境最重要的就是需要知道
JavaScript
文本内容到底干了什么。 - 标准库支持,
JavaScript
代码会使用EcmaScript
所规定的标准库,因此必须实现。 - 环境实现,不同的宿主环境为了实现不同的功能,会提供了不同的实现,比如浏览器上的
BOM
和DOM
,node
上的fs
path
等模块。
浏览器
作为 JavaScript
最重要的宿主环境,JavaScript
携手已经走过了将近 20
年,浏览器因为 JavaScript
而大放异彩,JavaScript
也因为浏览器露出锋芒。
浏览器为 JavaScript
提供语法和标准库支持外,还实现了两大类 API
:DOM(Document Object Model)
和 BOM(Browser Object Model)
。
DOM
DOM(Document Object Model)
– 文档对象模型。
不同于 EcmaScript
,DOM
的规范化组织为 W3C
,也就是说 DOM
其实是 HTML
标准化的一部分,因此 HTML5
最新标准涵盖一些 DOM API
是正常的。查看 W3C DOM
的最新规范 Document Object Model。
那么何为文档对象模型?
我们都知道 HTML
可以简单的理解为标签的嵌套,嵌套的标签形成了树状结构。DOM
将该树状结构提炼成了 JavaScript
中的一个对象 document
通过该对象,JavaScript
就拥有了操作文档中某个标签的能力,从而改变标签的结构,样式,内容监听标签的事件等等。
DOM
涉及的 API
很多,但其 API
都有一个特点,以 标签
打头,比如 body.addEventListener
document.getElementsByTagName
等等,可以通过以下内容进行学习:
- 什么是 DOM?
- DOM 概述
BOM
BOM(Browser Object Model)
– 浏览器对象模型。
BOM
其实到目前还没有一个规范化组织来制定标准,因此各个浏览器的实现完全按照自己的标准来,MDN
上也没有专门的介绍页面,说实话有点惨,但虽然没有标准,各个浏览器实现的 API
却基本相同。
BOM
可以简单的理解为浏览器实现了一套 API
使得 JavaScript
能与浏览器进行交互。相关的 API
全部放在 window
对象下。
window
对象主要包含以下对象:
-
document
– 对DOM
的引用 -
frames
– 对当前页面中的iframe
的引用 -
history
– 浏览器浏览记录相关API
-
location
– 浏览器地址相关API
-
localStorage
– 本地数据存储 -
sessionStorage
– 会话级别的数据存储 -
navigator
– 浏览器导航信息相关API
-
screen
– 屏幕信息
Node
Node
作为 JavaScript
服务端的宿主环境,除了提供了语法和标准库的支持外,还实现了一系列的模块,每个模块都有具体的作用,具体可以查看 Node 官方文档。
其实异步的编程模式是不容易被应用在服务端的,或者说服务端更偏向于同步模式。
服务端的极大多数代码都需要运行在一个同步的环境下,比如数据库的查找,文件的读取,请求结果的读取等等,如果在都将逻辑写在异步的回调中,代码将变得的难以解读,而且代码编写也会变得复杂起来。比如需要安装顺序读取 10
次外部接口的数据,同步模式下,只要按照顺序从前往后写即可,而异步模式只能嵌套加嵌套(或者 Promise.then
)不仅写出来的代码难以读懂,代码也难以维护。
但是为什么 Node
还是大热呢?个人感觉有以下几个原因:
- 前端自动化打包的出现。
- 虽然异步模式不适合服务端,但却极其符合请求的过程:请求触发任务。
- 主线程仅分发任务,而不需要处理读取文件,数据库等耗时逻辑,不会导致程序堵塞,并发量可以达到很高。
- 其数据结构与前端需要的结构一致(原生支持
JSON
)。 - 语法灵活,直接操作数据,屏蔽或是添加一些字段,处理一些前置逻辑。
- 简单,包括环境搭建简单,启动简单程,序编写简单,还有
NPM
上各种库。 -
ES8 AsyncFunction
异步函数的出现,将异步模式的写法向同步写法趋近。代码编写变得简单起来。 - 前端人直接上手?
对于第 8
点,我持中立态度。大前端的概念层出不穷,我想大家需要冷静冷静,不妨上手写个 Node
程序,感受下前端异步编程是否真正的适合服务端?还有服务端虽然环境统一,但涉及的概念极多,虽然可能在自己写的小项目中并不会涉及,但是真正使用却是会用到的,所以对于 Node
能做什么,我的理解如下:
- 作为前端开发的自动化工具,
webpack
gulp
等,语法一致,都能看懂,无非增加了一些文件读取,字符串解析。 - 爬虫,抓取一些自己敢兴趣的东西。对于自己的小程序,放开了手做,数据量不大,怎么整都行。
- 数据预处理,在大型项目中,作为请求中转站的存在,返回更加适合前端的数据,或是将前端过来的数据更加适合后端程序,也就是数据映射。在往后交给专业的后端大佬们即可,我们就不管了。
Electron
Electron
作为一个跨平台的 GUI
工具,使用 Chromium
和 Node
构建,实现了使用 JavaScript
开发桌面应用程序,也可以算是一个 JavaScript
的宿主环境,但其实相当于实现了一个浏览器,JavaScript
也被分为两部分,这两部分的宿主环境分别为 Node
和浏览器。具体可以查看 Electron 官网。
Event Loop
不管 JavaScript
程序的运行在 Node
端还是运作在浏览器端,都是以单线程的形式运行在宿主环境下,那么 JavaScript
是如何处理多任务的呢?
异步 +
Event Loop
简单描述下:JavaScript
会调用宿主环境提供的 API
处理不同的任务(这些任务运行在别的线程)并设置这些任务的回调,当这些任务完成时,会在事件队列中放入任务对应的回调,而 JavaScript
主线程会不断的去处理事件队列中的任务,这个过程就被称为 Event Loop
。
由于这个过程并不在 ECMAScript
所规定的规范中,因此不同的宿主环境实现是有区别的,具体可以查看我之前写的文章:
- 浏览器下的 Event Loop
- Node 下的 Event Loop
语言特点
每种语言都有自己的特点,JavaScript
也不例外,下面就谈谈 JavaScript
令人着迷,或是苦恼的地方。
数据存储
每种编程语言都需要处理数据和变量的关系,JavaScript
也不例外。但作为脚本,它需要运行在一个特定的宿主环境,那么这个宿主环境储存了 JavaScript
运行时产生的所有数据,又由于闭包的存在,使得这些数据能改被各种各样的变量所引用,而 JavaScript
中变量又是无类型的,各种奇奇怪怪的赋值方式,会导致各种奇奇怪怪的结果,这可以定义为复杂,但也可以定义为灵活。熟悉它你可以畅游在 JavaScript
的世界中,笑谈程序;而霸王硬上弓,却会陷入无尽的 bug
中。
那么 JavaScript
中数据是如何存储的?请持续关注 ~ 争取说清楚。
作用域 & 闭包
作用域大家都很熟悉,由双大括号产生(ES6+
),内层变量可以引用到外层变量,而外层变量却对内层变量无能为力。但函数的闭包却打破了这层限制,可以让外部程序有了改变或获取内部变量的能力,同时由于数据存储的原因,外部程序在一定情况下甚至对内部变量可以为所欲为。
那么 到底何为作用域?闭包在这里扮演的什么角色?请持续关注 ~
函数
JavaScript
是一种函数先行的语言,表现在代码上为:可以直接定义 function
或是直接将 function
赋值给某个变量。
函数,何为函数?函数可以理解为一种行为,一段过程,或是一个任务,而这些都有一个显著的特点,有起始点和终点。对应到函数上即为入参和返回值,在面向对象编程被大肆宣传的情况下,函数式编程却在发挥着独特的魅力,以其自然清晰简洁的编程方式,吸引着众人的眼光。
函数式编程式一个极大的内容,什么是函数式编程光解释是没有用的,只有自己亲自上手体验一把才能感受到它的魅力。
有兴趣的朋友可以翻阅 JS 函数式编程指南。
原型链
前面说到,JavaScript
是一种函数先行的语言,那么 JavaScript
就不能面向对象了?错!ES6 class
语法的出现,标志着 JavaScript
完全是能够面向对象的。查看经过 babel
转码后的 ES5
兼容代码,可以清楚的知道 class
仅仅只是一个语法糖,不然如何进行转码呢?而面向对象实现的关键是 JavaScript
的另外一个特点:原型链(prototype
)。
那么 何为原型链(prototype
)?原型又是什么?请持续关注 ~ 争取说清楚。
this
谈到 this
相信大部分面向对象编程语言的 coder
都很熟悉,常常出现在对象所属的方法中,JavaScript
表现出来的行为和这些语言一致,但其本质却是极大的不同,因为在每一个函数下都有 this
的存在,那么 this
到底是什么?其所代表的又是什么内容?这和 JavaScript
的数据存储有点关系,因此在不弄懂数据存储前,this
往往难以预测。
this
到底是如何取值的,在 JavaScript
中数据是如何存储的?中会有提到,请持续关注 ~
箭头函数
已经说了函数,为何还要单独写个箭头函数?相信大家对于箭头函数的理解仅仅在简化的正常函数上,但箭头函数和用 function
声明的函数是有区别的,试想想,在 VUE
文档中是否有这么一句话:XXX
只接受 function
。这个 function
不代表箭头函数。那么箭头函数到底是什么?和普通函数又有什么区别?什么情况下该使用箭头函数呢?
请看之后的文章:什么是箭头函数?
其他
JavaScript
语言的特点还有很多,ES6 ~ ES10
陆陆续续更新不少内容,包括 Promise
Proxy
Iterator
Generator
async
等很多内容,这些都是语言上的更新,具体的使用查看文档即可。
ECMAScript 6 入门。
当然也推荐推荐之前在看《ECMAScript 6 入门》时,写下的笔记 ecmascript6。欢迎查阅~~
模块化
JavaScript
的模块化由 RequireJs(AMD)
开启,终结于 ES6(Module)
规范,中间经历了 SeaJS(CMD)
和 Node(CommonJS)
。目前常用的为 CommonJS(Node)
和 Module(ES6 + Babel)
,其他的我们可以略过。
CommonJS
CommonJS
为 Node
端的模块系统,内置 3
个变量 require
exports
和 module
。通过 require
引入模块,exports
对外导出,module
保存该模块的信息。如下所示
'use strict';
var x = 5;
function addx (value){console.log( x + value);
}
exports.addx = addx;
相信大家都知道,导出还有一种写法
module.exports.addx = addx;
// or
module.exports = {addx};
其实只要知道 module.exports === exports // true
,那么一切就解释的通了,exports
仅为 module.exports
的一个引用,但是以下写法是有问题的
exports = {addx};
出现问题的原因是 exports
只是一个引用,如果重新赋值的话会导致该变量引用到另一个数据上,这样的结果就是 module.exports === exports // false
,而模块的信息是以 module
为标准的,因此就变成了无导出。那如果究其原因到底为什么会发生引用变化,可以查看 JavaScript
中数据是如何存储的?虽然还没写,但肯定会写的。
Module
Module
为 ES6
推出的模块化规范,目的在于统一各种不同的规范,由 import
导入 export
导出。如下所示
import {stat, exists, readFile} from 'fs';
export const a = '100';
// or
const a = '100';
export {a}
常见的导入语句有
-
import {xxx} from 'a';
– 从a
中导出xxx
的属性或方法,数据由a
的export
导出 -
import {xxx as yyy} from 'a';
– 从a
中导出xxx
的属性或方法并重命名为yyy
,数据由a
的export
导出 -
import xxx from 'a';
– 从a
中导出默认值,并赋值为xxx
,数据由a
的export default
导出
常见的导出语句有
-
export const a = 1;
– 导出一个常量,名字为a
值为1
-
export {a};
– 导出名字为a
的属性或方法,其值为作用域中变量a
的值 -
export {a : b};
– 导出名字为a
的属性或方法,其值为作用域中变量b
的值,其上为export {a : a}
的缩写 -
export default a;
– 导出默认值,其值为作用域中变量a
的值 -
export default 1;
– 导出默认值,其值为1
常见的错误导出语句
export a;
export default const a = 1;
以上是常见的错误导出语句,如何避免?只要记住以下规则即可:
-
export
导出的值,必须要有名字,const
或let
这些声明语句规定了该值的名字,{a: b}
,也规定了名字。 -
export default
仅导出值,因此不需要有特殊的方式规定名字。
总结
惯例,问几个问题
- 什么是解释性脚本语言?
-
EMCAScript
与JavaScript
是什么关系? -
JavaScript
都有哪些数据类型? -
JavaScript
的流程控制语句与运算符都有哪些? -
JavaScript
的语言特点是什么? -
JavaScript
模块是如何定义的?
参考
- JavaScript 语言的历史
- 什么是 JavaScript?
- 在浏览器中使用 ES6 的模块功能 import 及 export
- JavaScript 标准库
最后的最后
该系列所有问题由 minimo
提出,爱你哟~~~