前言

作为程序员,技术的落实与巩固是必要的,因此想到写个系列,名为 why what or how 每篇文章试图解释清楚一个问题。

这次的 why what or how 主题:什么是 JavaScript

释义

JavaScript - 一种解释性脚本语言

解释性脚本语言:一类不具备开发操作系统的能力,而是只用来编写控制其他大型应用程序的“脚本”,但其内容的的执行不需要提前编译的语言。通常作为别的程序的输入。

JavaScript,是一种用于描述网页逻辑,处理网页业务的解释性脚本语言,纯文本,其内容作为浏览器的输入,浏览器负责解释编译运行其内容。

目前 JavaScript 也成功被应用于服务端,服务端的 JavaScript 用于描述业务逻辑,其内容作为 node 的输入, node 负责解释编译运行其内容。

JavaScript 是伴随着浏览器出现的一门特殊的语言,特殊在哪呢? JavaScript 是唯一一门所有浏览器都支持的脚本语言,也就说如果你的 WEB 程序想在客户端做点事,就一定会用到 JavaScript 。别看现在 JavaScript 炙手可热,但它最开始出现却仅为了验证表单。

历史

  • 1990 - 1994,虽然各种浏览器开始出现,但浏览器仅用作数据展示,并没有客户端逻辑存在。
  • 1994Netscape 公司计划实现一种浏览器脚本语言进行一些简单的表单验证。
  • 1995Netscape 公司雇佣了程序员 Brendan Eich 开发这种网页脚本语言。
  • 1995.5Brendan Eich 用了 10 天,设计完成了这种语言的第一版。取名为:Mocha
  • 1995.9Netscape 公司将该语言改名为 LiveScript
  • 1995.12NetscapeSun 公司联合发布了 JavaScript 语言。
  • 1996.3Navigator 2.0 浏览器正式内置了 JavaScript 脚本语言。
  • 1996.8,微软发布 Internet Explorer 3.0 ,同时发布了 JScript ,该语言模仿同年发布的 JavaScript
  • 1996.11Netscape 公司在浏览器对抗中没落,将 JavaScript 提交给国际标准化组织 ECMA ,希望 JavaScript 能够成为国际标准,以此抵抗微软。
  • 1997.7ECMAScript 1.0 发布。ECMAScript 是一种标准,而 JavaScript 是该标准的一种实现。
  • 1997.10Internet Explorer 4.0 发布,其中的 JScript 基于 ECMAScript 1.0 实现。
  • 1999IE 5 部署了 XMLHttpRequest 接口,允许 JavaScript 发出 HTTP 请求,为后来 Ajax 应用创造了条件。
  • 1999.12ECMAScript 3.0 版发布,得到了浏览器厂商的广泛支持。
  • 2005Ajax 方法(Asynchronous JavaScript and XML)正式诞生,Google Maps 项目大量采用该方法,促成了 Web 2.0 时代的来临。
  • 2006jQuery 函数库诞生,作者为 John ResigjQuery 统一了不同浏览器操作 DOM 的不同实现,被广泛使用,极大降低了 JavaScript 语言的应用成本,推动了语言的流行。
  • 2007.10ECMAScript 4.0 草案发布,对 3.0 版做了大幅升级,但由于改动幅度过大,遭到了各大浏览器厂商的反对。
  • 2008.7,各大厂商对 ECMAScript 4.0 版本的开发分歧太大,ECMA 开会决定,中止 ECMAScript 4.0 的开发,对其中一些已经改善的部分发布为 ECMAScript 3.1。不久之后就改名为 ECMAScript 5
  • 2008,由 google 开发的 V8 编译器诞生。极大提高了 JavaScript 的性能,为之后 node 的诞生打下了基础。
  • 2009Node.js 项目诞生,创始人为 Ryan DahlJavaScript 正式应用于服务端,以其极高的并发进入人们的视野。
  • 2009.12ECMAScript 5.0 正式发布。同时将标准的设想定名为 JavaScript.nextJavaScript.next.next 两类。
  • 2010NPMRequireJS 出现,标准着 JavaScript 进入模块化。
  • 2011.6ECMAscript 5.1 发布,并且成为 ISO 国际标准(ISO/IEC 16262:2011)。
  • 2012,单页面应用程序框架(single-page app framework)开始崛起,AngularJS 项目出现。
  • 2012,所有主要浏览器都支持 ECMAScript 5.1 的全部功能。
  • 2012,微软发布 TypeScript 语言。为 JavaScript 添加了类型系统。
  • 2013ECMA 正式推出 JSON 的国际标准,这意味着 JSON 格式已经变得与 XML 格式一样重要和正式了。
  • 2013.2Grunt.js 前端构建化工具发布,前端进入自动化开发阶段。
  • 2013.5Facebook 发布 UI 框架库 React
  • 2013.8, Gulp.js 3.0 前端构建工具发布,js 自动化开发变得简单起来。
  • 2014,尤雨溪发布 VUE 项目。
  • 2015.3Facebook 公司发布了 React Native 项目,将 React 框架移植到了手机端,用来开发手机 App
  • 2015.3babel 5.0 发布,ES6 代码正式引用于开发,而不需要考虑兼容问题。
  • 2015.6ECMAScript 6 正式发布,并且更名为 ECMAScript 2015
  • 2015.6Mozillaasm.js 的基础上发布 WebAssembly 项目。
  • 2015.9Webpack 1.0 发布,模块系统得到广泛的使用。
  • 2016.6ECMAScript 2016(ES7) 标准发布。
  • 2017.6ECMAScript 2017(ES8) 标准发布,引入了 async 函数,使得异步操作的写法出现了根本的变化。
  • 2017.11,所有主流浏览器全部支持 WebAssembly,这意味着任何语言都可以编译成 JavaScript,在浏览器中运行。
  • 2018.6ECMAScript 2018(ES9) 标准发布。
  • 2019.6ECMAScript 2019(ES10) 标准发布。
  • ...

纵观发展历史,很容易就能发现其中几个关键点的出现,极大的促进了 JavaScript 的发展。

  1. XMLHttpRequest 接口的出现,JavaScript 开始有了与服务器沟通的能力,诞生了 ajax ,同时也促进了 RestFul API 的发展。
  2. jQuery 函数库诞生,极大的降低了网页开发成本。
  3. V8 的出现,降低了 JavaScript 消耗的资源,加快了编译速度,复杂的 JavaScript 程序开始出现。
  4. Node.js 项目诞生,JavaScript 开始应用在服务端。
  5. NPMRequireJS 出现,JavaScript 正式进入模块化。
  6. Grunt Gulp Webpack 这些自动化构建工具的出现,极大的降低了开发成本,统一了各种内容的构建方式。
  7. Babel 的出现彻底让前端程序员放开了手脚,毫无顾忌的使用 ES6 而无须担心平台的兼容性问题。
  8. Angular React Vue 的出现了,进一步降低了网页开发成本,单页应用开始出现。
  9. 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 仅有 truefalse ,代表真假
Symbol 符号 let a = Symbol(42) 一类永远都不会相同的值,常用作对象的 key
Object 对象 let a = { a: 1 } 一类拥有多个键值对的数据

JavaScript 中数据类型除了可以按照上述的分类方式,还可以简单的分为:基本数据类型(除了 Object 的其他类型)和引用类型(Object),这和变量的存储方式有关,这里不过于深入,但 JavaScript 数据是如何存储的?请查看:JS 变量存储?栈 & 堆?NONONO!

运算符 & 流程 & 声明

  • 运算符
分类 包含
算术 + - * / ** % ++ --
比较 > >= < <= == != === !==
布尔运算 ! || && ?:
二进制运算 | & ~ ^ << >> >>>

在进行算术、比较、布尔运算时,会出现类型转换,会有一些怪异的表现,那么 JavaScript 是如何进行类型转换的?之后会写,请持续关注~

  • 流程
  1. for(let i = 0; i < n; i++){ ... }
  2. for(let key in obj){ ... }
  3. for(let value of array){ ... }
  4. while(true){ ... }
  5. do{ ... }while(true)
  6. if(true){ ... }else if(true){ ... }else{ ... }
  7. switch(a){ case() ... }

和大多数的语言一样,都有类似的流程语句。仅需要注意 for...of 循环即可。

  • 声明
分类 包含
常量 const
变量 let / var(不建议使用)
同步函数 function () => {}
异步函数 async function async () => {}
Generator 函数 function*

JavaScript 的基础知识就差不多也就这些,这里仅是罗列,并不过多的深入,有兴趣或是不了解的可以在JavaScript 教程,进行学习。

标准库

何为标准库?

在了解 JavaScript 发展史后,我们都知道,EcmaScriptJavaScript 语言实现的标准,标准除了规定基本的语法外,还定义了一些列 JavaScript 执行环境应该有的对象或是函数,这些与语法无关的其他内容,就被称为标准库。标准库主要包含以下内容(一些已废弃或不推荐使用的就不罗列了):

  • 全局函数
  1. eval()
  2. isFinite()
  3. isNaN()
  4. parseFloat()
  5. parseInt()
  6. decodeURI()
  7. decodeURIComponent()
  8. encodeURI()
  9. encodeURIComponent()
  10. ...
  • 全局对象
  1. Number
  2. String
  3. Boolean
  4. Symbol
  5. Object
  6. Function
  7. Error
  8. Math
  9. Date
  10. RegExp
  11. Array
  12. Map
  13. Set
  14. JSON
  15. Promise
  16. Generator
  17. GeneratorFunction
  18. AsyncFunction
  19. Proxy
  20. Reflect
  21. WebAssembly
  22. ...

具体内容可以点击查看,这里仅是罗列出一些常用的函数以及对象,一些对象如果不操作视频、音频、图像等内容也用不到,这里就不写了。

宿主环境

JavaScript 作为一门解释性脚本语言,其内容只能作为别的程序的输入,而这个别的程序,就是宿主环境。那么宿主环境为 JavaScript 提供了什么呢?

  1. 语法支持,宿主环境最重要的就是需要知道 JavaScript 文本内容到底干了什么。
  2. 标准库支持, JavaScript 代码会使用 EcmaScript 所规定的标准库,因此必须实现。
  3. 环境实现,不同的宿主环境为了实现不同的功能,会提供了不同的实现,比如浏览器上的 BOMDOMnode 上的 fs path 等模块。

浏览器

作为 JavaScript 最重要的宿主环境,JavaScript 携手已经走过了将近 20 年,浏览器因为 JavaScript 而大放异彩,JavaScript 也因为浏览器露出锋芒。

浏览器为 JavaScript 提供语法和标准库支持外,还实现了两大类 APIDOM(Document Object Model)BOM(Browser Object Model)

DOM

DOM(Document Object Model) -  文档对象模型。

不同于 EcmaScriptDOM 的规范化组织为 W3C,也就是说 DOM 其实是 HTML 标准化的一部分,因此 HTML5 最新标准涵盖一些 DOM API 是正常的。查看 W3C DOM 的最新规范 Document Object Model

那么何为文档对象模型?

我们都知道 HTML 可以简单的理解为标签的嵌套,嵌套的标签形成了树状结构。DOM 将该树状结构提炼成了 JavaScript 中的一个对象 document 通过该对象,JavaScript 就拥有了操作文档中某个标签的能力,从而改变标签的结构,样式,内容监听标签的事件等等。

DOM 涉及的 API 很多,但其 API 都有一个特点,以 标签 打头,比如 body.addEventListener document.getElementsByTagName 等等,可以通过以下内容进行学习:

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 还是大热呢?个人感觉有以下几个原因:

  1. 前端自动化打包的出现。
  2. 虽然异步模式不适合服务端,但却极其符合请求的过程:请求触发任务。
  3. 主线程仅分发任务,而不需要处理读取文件,数据库等耗时逻辑,不会导致程序堵塞,并发量可以达到很高。
  4. 其数据结构与前端需要的结构一致(原生支持 JSON)。
  5. 语法灵活,直接操作数据,屏蔽或是添加一些字段,处理一些前置逻辑。
  6. 简单,包括环境搭建简单,启动简单程,序编写简单,还有 NPM 上各种库。
  7. ES8 AsyncFunction 异步函数的出现,将异步模式的写法向同步写法趋近。代码编写变得简单起来。
  8. 前端人直接上手?

对于第 8 点,我持中立态度。大前端的概念层出不穷,我想大家需要冷静冷静,不妨上手写个 Node 程序,感受下前端异步编程是否真正的适合服务端?还有服务端虽然环境统一,但涉及的概念极多,虽然可能在自己写的小项目中并不会涉及,但是真正使用却是会用到的,所以对于 Node 能做什么,我的理解如下:

  1. 作为前端开发的自动化工具,webpack gulp 等,语法一致,都能看懂,无非增加了一些文件读取,字符串解析。
  2. 爬虫,抓取一些自己敢兴趣的东西。对于自己的小程序,放开了手做,数据量不大,怎么整都行。
  3. 数据预处理,在大型项目中,作为请求中转站的存在,返回更加适合前端的数据,或是将前端过来的数据更加适合后端程序,也就是数据映射。在往后交给专业的后端大佬们即可,我们就不管了。

Electron

Electron 作为一个跨平台的 GUI 工具,使用 ChromiumNode 构建,实现了使用 JavaScript 开发桌面应用程序,也可以算是一个 JavaScript 的宿主环境,但其实相当于实现了一个浏览器, JavaScript 也被分为两部分,这两部分的宿主环境分别为 Node 和浏览器。具体可以查看 Electron 官网

Event Loop

不管 JavaScript 程序的运行在 Node 端还是运作在浏览器端,都是以单线程的形式运行在宿主环境下,那么 JavaScript 是如何处理多任务的呢?

异步 + Event Loop

简单描述下:JavaScript 会调用宿主环境提供的 API 处理不同的任务(这些任务运行在别的线程)并设置这些任务的回调,当这些任务完成时,会在事件队列中放入任务对应的回调,而 JavaScript 主线程会不断的去处理事件队列中的任务,这个过程就被称为 Event Loop

由于这个过程并不在 ECMAScript 所规定的规范中,因此不同的宿主环境实现是有区别的,具体可以查看我之前写的文章:

语言特点

每种语言都有自己的特点,JavaScript 也不例外,下面就谈谈 JavaScript 令人着迷,或是苦恼的地方。

数据存储

每种编程语言都需要处理数据和变量的关系,JavaScript 也不例外。但作为脚本,它需要运行在一个特定的宿主环境,那么这个宿主环境储存了 JavaScript 运行时产生的所有数据,又由于闭包的存在,使得这些数据能改被各种各样的变量所引用,而 JavaScript 中变量又是无类型的,各种奇奇怪怪的赋值方式,会导致各种奇奇怪怪的结果,这可以定义为复杂,但也可以定义为灵活。熟悉它你可以畅游在 JavaScript 的世界中,笑谈程序;而霸王硬上弓,却会陷入无尽的 bug 中。

那么 JavaScript 中数据是如何存储的?请查看:JS 变量存储?栈 & 堆?NONONO!

作用域 & 闭包

作用域大家都很熟悉,由双大括号产生(ES6+),内层变量可以引用到外层变量,而外层变量却对内层变量无能为力。但函数的闭包却打破了这层限制,可以让外部程序有了改变或获取内部变量的能力,同时由于数据存储的原因,外部程序在一定情况下甚至对内部变量可以为所欲为。

那么 到底何为作用域?闭包在这里扮演的什么角色?请持续关注 ~

函数

JavaScript 是一种函数先行的语言,表现在代码上为:可以直接定义 function 或是直接将 function 赋值给某个变量。

函数,何为函数?函数可以理解为一种行为,一段过程,或是一个任务,而这些都有一个显著的特点,有起始点和终点。对应到函数上即为入参和返回值,在面向对象编程被大肆宣传的情况下,函数式编程却在发挥着独特的魅力,以其自然清晰简洁的编程方式,吸引着众人的眼光。

函数式编程式一个极大的内容,什么是函数式编程光解释是没有用的,只有自己亲自上手体验一把才能感受到它的魅力。

有兴趣的朋友可以翻阅 JS 函数式编程指南

原型链

前面说到,JavaScript 是一种函数先行的语言,那么 JavaScript 就不能面向对象了?错!ES6 class 语法的出现,标志着 JavaScript 完全是能够面向对象的。查看经过 babel 转码后的 ES5 兼容代码,可以清楚的知道 class 仅仅只是一个语法糖,不然如何进行转码呢?而面向对象实现的关键是 JavaScript 的另外一个特点:原型链(prototype)。

那么 何为原型链(prototype)?原型又是什么?请查看:JavaScript 对象 & 原型

this

谈到 this 相信大部分面向对象编程语言的 coder 都很熟悉,常常出现在对象所属的方法中,JavaScript 表现出来的行为和这些语言一致,但其本质却是极大的不同,因为在每一个函数下都有 this 的存在,那么 this 到底是什么?其所代表的又是什么内容?这和 JavaScript 的数据存储有点关系,因此在不弄懂数据存储前,this 往往难以预测。

this 到底是如何取值的,之后会有提到,请持续关注 ~

箭头函数

已经说了函数,为何还要单独写个箭头函数?相信大家对于箭头函数的理解仅仅在简化的正常函数上,但箭头函数和用 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

CommonJSNode 端的模块系统,内置 3 个变量 require exportsmodule。通过 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 为标准的,因此就变成了无导出。那如果究其原因到底为什么会发生引用变化,请查看:JS 变量存储?栈 & 堆?NONONO!

Module

ModuleES6 推出的模块化规范,目的在于统一各种不同的规范,由 import 导入 export 导出。如下所示

import { stat, exists, readFile } from 'fs';

export const a = '100'; 
// or
const a = '100';
export {
    a
}

常见的导入语句有

导入语句 含义
import { xxx } from 'a' a 中导出 xxx 的属性或方法,数据由 aexport 导出
import { xxx as yyy } from 'a' a 中导出 xxx 的属性或方法并重命名为 yyy,数据由 aexport 导出
import xxx from 'a' a 中导出默认值,并赋值为 xxx,数据由 aexport 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

常见的错误导出语句

  1. export a;
  2. export default const a = 1;

以上是常见的错误导出语句,如何避免?只要记住以下规则即可:

  1. export 导出的值,必须要有名字,constlet 这些声明语句规定了该值的名字,{a: b},也规定了名字。
  2. export default 仅导出值,因此不需要有特殊的方式规定名字。

总结

惯例,问几个问题

  1. 什么是解释性脚本语言?
  2. EMCAScriptJavaScript 是什么关系?
  3. JavaScript 都有哪些数据类型?
  4. JavaScript 的流程控制语句与运算符都有哪些?
  5. JavaScript 的语言特点是什么?
  6. JavaScript 模块是如何定义的?

参考

最后的最后

该系列所有问题由 minimo 提出,爱你哟~~~