现在起开始快速过一遍Javascript(看菜鸟教程基本够用了)。若要深入,要准确,还得看ECMAScript标准文档。本文力图在菜鸟教程和ECMAScript标准两方面抓要点抓重点。正文中,红色的文字表示存疑待解。
1 前言
JavaScript 是一种弱类型的、面向对象的动态语言。JavaScript(简称“JS”)是一种具有函数优先的轻量级,解释型或即时编译型的编程语言。虽然它是作为开发Web页面的脚本语言而出名,但是它也被用到了很多非浏览器环境中,JavaScript基于原型编程、多范式的动态脚本语言,并且支持面向对象、命令式、声明式、函数式编程范式。
1.1 JavaScript的来龙去脉
我的这部分笔记整理于:JavaScript起源 → ECMAScript标准 → NodeJS语法_万物智能相联的博客-CSDN博客
1.2 Node.js与ECMAScript的版本关系
Node.js ES2015/ES6, ES2016 and ES2017 support 说明了node.js对es6的支持情况(包括详细的功能支持说明):
nodejs v8开始的运行时支持大约99%的es6,截至目前的V21仍不能全部支持ES6,就更不要说es7~14了。
由于es5出现于2011年,且相对于es3升级不大,而node.js创始于2009,所以现在的node.js应该是支持es5全部特性的。各处查不到这个问题,似乎更说明了支持es5全部特性。
1.3 Node.js与TypeScript的关系
typescript 是 javascript 的当前最受欢迎的中间语言,提供了强大灵活的类型系统,typescript 提供一个编译器 tsc 可以将 typescript 编写的代码编译成 javascript。除了 typescript,你可能还听过 coffescript, flow, dart 等 javascript 的中间语言。它们都可以通过它们的编译器编译成原生 js,原生 js 也叫 vanilla js。它俩没啥强耦合的关系,node 只能运行原生 js 文件,但是你可以使用 typescript 编写 node 项目,再通过 tsc 编译成 js 运行。原文链接:TypeScript和Node.js到底是什么关系? - 知乎
TypeScript 由微软开发的自由和开源的编程语言,是 JavaScript 的一个超集。
TypeScript为JavaScript 添加了静态类型检查和类、接口等面向对象编程的特性。其最大的优势在于,它可以在代码编写阶段就进行类型检查,从而减少了在代码运行阶段出现类型错误的可能性。此外,TypeScript 还提供了丰富的工具支持,例如自动补全、重构等功能,大大提高了开发效率。Node.js 支持 TypeScript,可以使用 npm 安装 TypeScript,然后使用 tsc 命令将 TypeScript 编译成 JavaScript。原文链接:Node.js与TypeScript:优雅的后端开发方式_node+typescript_与墨学长的博客-CSDN博客
使用TypeScript,可以支持es2019等高于es2015的ECMAScript标准。
1.4 ESM和CJS
es5没有模块的概念。故基于es5的node.js自己搞了一套标准,用require()加载和module.exports导出实现了多模块的协作。以此为基础构建出了Node.js 专用的 CommonJS 模块,简称 CJS。。
es6加入了模块的概念,用流行的import/export语法实现了多模块的协作。以此为基础构建的是ES6 模块,简称 ESM。
但是我们不能认为:CJS使用的语法是ES5的,ESM使用的语法是ES6的。事实上,在npm包中,package.json中一般都声明了"type": "commonjs",但是包内的js文件常常存在用class声明的类,而这种方法是es6标准的。CJS与ESM的根本区别就是加载模块方式的区别。
CJS和ESM这两种模块不兼容。CJS模块推荐用后缀.cjs,ESM推荐用后缀.mjs。尽管如此,一般的代码对于文件后缀没有限制,并不是非要用.js不可。实测.txt仍然可以用node命令来执行。
node默认用的是CJS。要使用esm,需要安装esm包:npm install esm。执行es6的脚本时,一般要用node -r esm 脚本文件。如果采用.js后缀,还得在package.json中用"type": "module"进行描述。实践上,这个问题有些复杂,还与打包软件有关,例如boardgame.io教程用parcel打包,教程代码是es6语法的,但是没有在package.json中用"type": "module"进行描述。
1.5 关于class
es5中说:
ECMAScript does not use classes such as those in C++, Smalltalk, or Java.
es6中说:
Even though ECMAScript includes syntax for class definitions, ECMAScript objects are not fundamentally classbased such as those in C++, Smalltalk, or Java.
即es5没有提供class语法。es6虽然提供了class语法,但ECMA脚本对象的本质与面向对象语言中class的实例对象object是完全不同的。
1.6 关于global对象
标准中的全局对象是什么?是一个虚构的东西,可以说没有实际变量名称,相当于把C++中的C函数和全局变量视作位于一个global对象之下。这也正是一切皆对象的思想的应用。
ECMAScript 5 —— 单体内置对象之Global对象-CSDN博客说:Global(全局)对象可以说是 ECMAScript 中最特别的一个对象了,因为不管从什么角度上看,这个对象都是不存在的。ECMAScript 中的 Global 对象在某种意义上是作为一个终极的“兜底儿对象” 来定义的。换句话说,不属于任何其他对象的属性和方法,最终都是它的属性和方法。事实上,没有全局变量或全局函数;所有在全局作用域中定义的属性和函数,都是 Global 对象的属性。诸如 isNaN()、isFinite()、parseInt()以及 parseFloat(),实际上全都是 Global对象的方法。除此之外,Global 对象还包含其他一些方法。
精读ECMAScript规范:全局对象_ecmascript 的全局对象-CSDN博客 说:全局函数应该是不通过任何对象即可调用的,SetTimeout不是全局函数(实际上根本原因是EMCA标准里没这个函数,其实也可以视作引擎/宿主的扩展全局函数)
global是node中的一个变量,但EMCA标准中没这个变量,全局变量的成员是直接用的。
什么是 globalThis,为什么要学会使用它?-CSDN博客 指出:globalThis旨在通过定义一个标准的全局属性来整合日益分散的访问全局对象的方法。该提案目前处于第四阶段,这意味着它已经准备好被纳入ES2020标准。所有流行的浏览器,包括Chrome 71+、Firefox 65+和Safari 12.1+,都已经支持这项功能。你也可以在Node.js 12+中使用它。
2 菜鸟JavaScript教程快速过
菜鸟教程网站的很多教程比较好,JavaScript教程也不例外: JavaScript 教程 | 菜鸟教程 。推荐首先学习这个,然后再对照ECMAScript标准看。
快速过是基于C#/Java等语言的编程基础而言,是基于我们的学习目标而言。那些通用的语言特征不需要关心,那些HTML有关内容不需要关心。快速过的含义,是仅关注JavaScript独有特征。
在这个菜鸟教程中,左边的目录树分了好几部分。主体部分从“ JavaScript Chrome 中运行”到“JavaScript 代码规范”,中间有几个与HTML相关的不用看;在这之后,“JS函数”、“JS类”、“JS高级教程”也要看,然后就剩下一个,即“JS 参考手册”中的JavaScript 对象。
主体部分
JavaScript Chrome 中运行:浏览一遍,掌握Chrome的这些功能,很实用。
JavaScript 输出:记住console.log() 就行,不用看了。
JavaScript 语法:(1)习惯“字面量”这个术语,其实就是常量,在标准文档中的英语是literals,不是constant。大家之所以统一称字面量而非常量,我猜是因为标准提到了对象字面量和函数字面量,这两个概念在其它语言中没有涉及。(2)因为JavaScript是弱类型语言,故统一使用关键字 var 来定义变量,不像C#/JAVA等强类型语言使用int、string之类的类型来定义变量。(3)JavaScript对大小写敏感,如function和Function是不同的东西。(4)JavaScript 使用 Unicode 字符集,这个需要注意另外学习。
JavaScript 语句:(1)语句标识符的概念,我不知道它的英语,故没有在标准文档中找到。(2)多行字符串使用\进行折行,这是C++的办法。明显不如C#使用@、JAVA使用3引号好用。但是JavaScript也有更高级的,就是模板字符串。
JavaScript 注释:不用看了。
JavaScript 变量:(1)以var定义(前节已学),命名规则,大小写敏感(2)不像C#/JAVA,而是单引号与双引号作用相同。(3)重新以var声明变量,相当于没这条语句(重声明毫无意义,要它干什么?)(4)未初始化的变量的值是undefined,也可以令一个变量等于undefined,相当于回到未初始化的状态。(为什么在null之外要增加一个undefined?)(5)变量作用域和未声明变量在函数一节讲,这节也没讲用let声明的变量。
JavaScript 数据类型:(1)可以分两大类:值类型(基本类型)(英语是primitive value):字符串(String)、数字(Number)、布尔(Boolean)、空(Null)、未定义(Undefined)、Symbol。引用类型(对象类型):对象(Object)、数组(Array)、函数(Function),正则(RegExp)和日期(Date)。(2)动态类型:一个变量可用作不同的类型,可以随意改变变量的类型。(3)对象定义形式:var person={firstname:"John", lastname:"Doe", id:5566};中间可以折行。
JavaScript 对象:(1)可以通过两种方式访问对象属性,person.lastName或者person["lastName"]。(2)属性的值是一个函数时,这个属性叫方法。可以用person.fullName()和person.fullName来访问fullName方法,前者表示函数调用,后者表示函数对象(对象方法与普通函数都是函数对象,toString()将得到函数完整定义,ECMAScript这一点是很牛的)。
JavaScript 函数:(1)不同于C#/JAVA的函数定义方式(因为变量都是动态类型),JavaScript这样定义:function myFunction(a,b) { return a*b; },再次注意大小写敏感。(2)变量的作用域和生存期:局部变量是函数内声明的变量(使用 var),在函数运行以后被删除,只能在函数内访问。全局变量是函数外声明的变量(使用 var),一直存在(浏览器上作用域是一个网页,node.js多模块时需要导入导出),任何函数内外都可访问。(3)在node.js也可以使用未声明变量。
JavaScript 作用域:这节不用看。
JavaScript 字符串:(1)前面学过了,单引号与双引号作用相同;\用作转义符,与Java/C#相同;.length属性可以得到字符串长度(2)值类型,现在这里也像标准文档翻译那样称原始值,还是称值类型比较通用。(3)字符串既可以是值类型var x = "John"; ,也可以是引用类型var y = new String("John");!这里typeof x与typeof y的结果不一样。这让我们想到了其它值类型如boolean,情况也是一样。(4)值类型本来没有属性和方法,但是会被转化为引用类型以访问对应对象的属性和方法,如"abc".length。(5)字符串的属性和方法速过,它们也存在于JavaScript对象参考手册一节中。(6)=== 为绝对相等,即数据类型与值都必须相等。菜鸟教程没有把这个放到运算符一节去讲,而是例子用到啥需要讲的就先讲啥。
JavaScript 模板字符串:(1)使用反引号 `` 而非单双引号 也非@ 来定界。(2)支持多行文本。(3)在字符串中用${expression}嵌入表达式,这点与C#相同(有可能是C#模仿了JavaScript)。
JavaScript 运算符:仅讲了常用的,不用看。
JavaScript 比较:这节仍然讲常用的运算符,不用看。注意===和!==的含义即可。
JavaScript 条件语句:讲if...else...的,不用看。
JavaScript switch 语句:switch...case,不用看。
JavaScript for 循环:for和for...in,不用看。
JavaScript while 循环:while和do...while,不用看。有个例子用for (;cars[i];)或while (cars[i])来循环,要注意有些人会采用这些写法:cars=["BMW","Volvo","Saab","Ford"]; var i=0; while (cars[i]){ ... }
JavaScript break 和 continue 语句:除了普通的用法,JavaScript增加了与标签语句共用的用法,这种用法非常类似于C语言的goto,所以是不推荐的用法。菜鸟教程没有明确把break标签和continue标签的区别说出来,只是说:break 的作用是跳出代码块, 所以可用于循环和 switch 等;continue 的作用是进入下一次迭代, 所以只能用于循环。
JavaScript typeof, null 和 undefined:(1)typeof返回字符串,一共是6种结果,菜鸟教程没讲全。(2)null是一个只有一个值的特殊类型。表示一个空对象引用,typeof 检测 null 返回是"object"。(3)undefined 表示变量没有初始化,typeof的结果还是"undefined"。(4)null 和 undefined 的值相等,但类型不等:
nnull === undefined // false
null == undefined // true
(4)关于null 和 undefined,尤其是undefined,与其它C#/JAVA迥异,要注意这俩的各种特性。
JavaScript 类型转换:(1)前面讲过有哪些类型,现在重新分了一下类,看看即可。(2)constructor 属性返回所有 JavaScript 变量的构造函数,这里讲如何利用constructor 属性检查变量的数据类型,其实还可以用instanceof。(3)全局方法 String() 可以将数字转换为字符串,测试了一下,typeof new String(3)返回"object",typeof String(3)返回"string"。Number转成字符串,可参考JavaScript对象参考手册,这里提一下,有toString()、toExponential()、toFixed()、toPrecision()。其它类型依此类推。(4)自动类型转换,给出了一张表,应该看看。这里划重点:null可以自动转化为数字0或者字符串"null",undefined可以自动转化为数字NaN或者字符串"undefined"或者false,空字符串转成0或false,对象、函数转成数字NaN或者true,NaN转成false,无穷转成true;数组比较特殊,均转成true,但是空数组转成0,只有一个数字成员的数组转成该数字,其它数组转成NaN。
JavaScript 正则表达式:(1)/runoob/i instanceof RegExp返回true。即正则表达式将会变成RegExp。(2)通过str.search()和replace()函数使用正则表达式,如var txt = str.replace(/microsoft/i,"Runoob");,也可以通过RegExp的方法来使用正则表达式,如/e/.test("The best things in life are free!")
从JavaScript 错误到JavaScript this:写得好好的,CTRL+Z,居然丢失了,CSDN有毛病,用CSDN要慎重,还是先自己在本地写成WORD文档再上传可靠。
....心累,不想重写,于是暂时也不写后续了。有时间还是把这篇文章拆分成几个方面的内容分别说明。例如单独说明JavaScript的函数、但是说明ES标准导读或者部分章节意译。
JavaScript 调试JavaScript 变量提升JavaScript 严格模式JavaScript 使用误区JavaScript 表单JavaScript 表单验证JavaScript 验证 APIJavaScript 保留关键字JavaScript thisJavaScript let 和 constJavaScript JSONJavaScript voidJavaScript 异步编程JavaScript PromiseJavaScript 代码规范
JS 函数
JavaScript 函数定义JavaScript 函数参数JavaScript 函数调用JavaScript 闭包
JS 类
JavaScript 类JavaScript 类继承JavaScript 静态方法
JS 高级教程
JavaScript 对象JavaScript prototypeJavaScript Number 对象JavaScript StringJavaScript Date(日期)JavaScript Array(数组)JavaScript Boolean(布尔)JavaScript Math(算数)JavaScript RegExp 对象
JS 参考手册
3 ES5标准快速过
得找个中文翻译,用浏览器即时翻译(360浏览器支持,Chrome需要翻墙)或者找个中文版。我找到了ECMA-262_5.1中文版参考:文本说明ecma26251中文版.pdf-原创力文档,但是翻译得很烂,仅仅比浏览器即时翻译要好一点。后来又找了个更好的:介绍_w3cschool——ECMAScript 5.1标准,但它竟然有章节缺漏,例如漏掉了加法运算符。
标准第3章及前
ECMAScript标准说明讲了标准的由来和变化,读读很好。
接下来是范围、一致性、参考文献,扫过。
标准“4 概述”
标准4.2语言概述
要义翻译/整理如下:
ECMAScript 是基于对象的。ECMAScript 程序是由相互交互的对象协作而成。ECMAScript 的对象 (objects) 是属性 (properties)的集合,每个属性有零个或多个 特性 (attributes),它们确定怎样使用此属性。例如,当设置一个属性的 Writable 特性为 false 时,任何试图更改此属性的ECMAScript 代码都会运行失败。属性是拥有其他对象,原始值(primitive values), 函数 (functions) 的容器(为什么是容器?)。原始值(primitive是原始、低等、远古的意思,primitive value type对应着java中的基本数据类型)是以下内置类型之一:Undefined, Null, Boolean, Number, String; 对象是除此而外的内置类型(为什么要提内置?扩充的不行吗?);函数是可调用对象 (callable object)。
ECMAScript 定义一组内置对象 (built-in objects)。 这些内置对象包括 全局对象 (global object) , Object 对象 , Function 对象 ,Array 对象 ,String 对象 ,Boolean 对象 ,Number 对象 ,Math 对象 ,Date 对象 ,RegExp 对象 ,JSON 对象,和 Error 对象: Error ,EvalError ,RangeError ,ReferenceError ,SyntaxError ,TypeError ,URIError 。
ECMAScript 中还定义一组内置运算符 (operators)。ECMAScript 运算符包括一元运算符 ,乘法运算符 ,加法运算符 ,按位移位运算符,关系运算符 ,相等运算符 , 二进制位运算符 , 二进制逻辑运算符 , 赋值运算符 , 逗号运算符。
ECMAScript 语法有意设计成与 Java 语法类似。
对象可以通过各种方式创建,包括文本符号(literal notation)(这个是什么?),或构造器(constructors)。
每个构造器是一个拥有名为“prototype”的属性的函数。此属性用于实现基于原型的继承 和 共享属性 。对象通过在new 表达式中使用构造器来创建:例如,new Date(2009,11) 创建一个新 Date
对象。不使用 new 调用一个构造器的结果取决于构造器本身。例如,Date() 产生一个表示当前日期时间的字符串,而不是一个对象。
Each constructor is a function that has a property named "prototype" that is used to implement prototype-based inheritance and shared properties. Objects are created by using constructors in new expressions; for example, new Date(2009,11) creates a new Date object. Invoking a constructor without using new has consequences that depend on the constructor. For example, Date() produces a string representation of the current date and time rather than an object.
原型和原型链,下节重点看。
严格模式 (strict mode),标准关于它的说明散落于各个章节。整理麻烦,统一阅读这篇应该够用了:JavaScript 严格模式(use strict) | 菜鸟教程。
标准4.3术语和定义
类型、原始值、对象、构造器、原型,扫过。
3种对象:原生对象、内置对象、宿主对象。标准有一句原话:任何对象,不是原生对象就是宿主对象。宿主对象是标准没有定义的,由宿主环境扩充出来的对象。因此,原生对象就是标准定义的对象。那么原生对象与内置对象有什么区别?(1)一切内置对象都是原生对象。(2)内置对象在ECMAScript 程序开始执行时就存在,即非内置的原生对象是在程序执行过程中动态创建的。原生对象与内置对象区别非常小。es6标准没有再提原生对象的概念,所以不必纠结原生对象这个概念。
接下来是5类原始值(Undefined, Null, Boolean, Number, String)的值、类型、对象的定义。一般编程语言只提类型type和值value,这里多提了个对象。不妨认为标准是在强调一切皆对象,一切对象都有属性由方法,所以5类原始值的对象也会有内置的属性和方法。
不知道为什么接下来把Infinity和NaN列出来。
接下来将函数、内置函数、方法、内置方法、属性、自身属性、继承属性、特性这几个概念。又提到内置,不妨就认为是标准规定了的东西。还是回到前面的话题,对象是属性的集合,特性描述属性。特性具体是什么东西?在C#中,可以为一个字段附上[Serializable]指明该字段可以被序列化。想来JavaScript也差不多,只是用法与C#不同,特性怎么用以后应该关注一下。方法与函数的区别是什么,标准在这里其实没有说清楚,它说:“方法是作为属性值的函数。当一个函数被作为一个对象的方法调用,此对象将作为 this 值传递给函数。”,标准4.2说:“A function that is associated with an object via a property is a method.” 因为对象属性的集合,所以这两处的意思是:作为对象的成员的函数,就是方法。那么除此而外的函数是什么,独立函数?既然一切皆对象,那么这些独立的函数只能是全局对象的函数,按理说也是方法。但标准既然这么说了,那么我们不妨认为,不是全局函数的函数,就是某个对象的成员,就是方法。
再提点标准未列出的术语:(1)ECMAScript implementation:ECMA脚本实现,不妨理解为JavaScript、JScript、Action Script这种从ECAMScript派生出来的具体实现,ECMAScript相当于C++中的纯虚类或者接口。(2)ECMAScript program:ECMA程序,类比JAVA程序、C++程序即可。(3)host environment,宿主环境:正如4.1所说“A web browser provides an ECMAScript host environment”,即web浏览器就是一种宿主环境,所以Node.JS也是。
标准第5-7章 约定
这几章关系着能不能读懂后面各章的内容。不理解这几章内容,就难以看懂后面各章。
中文翻译很烂,不大看得懂。把重点术语提出来,中英文对照看。
第5章是Notational Conventions(符号约定),第7章是Lexical Conventions(词法约定),第6章是Source Text(源代码文本)。这3章讲什么呢?本书的符号系统还是ECMAScript符号系统?
关于语法的含义,谈点个人理解:Grammar语法,Syntactic Grammars(句法),Lexical Grammars(词法)。类比汉语有字词句篇4个层次,由字成词的规则叫词法,由词成句的规则叫句法,由句成篇的规则叫章法😄,词法句法章法都是语法。任何语言概莫如是。语法Grammar的核心是词法Lexical Grammars和句法Syntactic Grammars。
标准第8-9章 Type
这两章是关于类型Type的。
ECMAScript language type语言类型包括5种原始值类型和Object类型,共6种。
标准第10章 代码执行环境
标准“11 Expressions表达式”
11.1讲Primary Expressions。
11.2讲Left-Hand-Side Expressions
11.3讲作为后缀的++(自增)和--(自减)
11.4一元运算符Unary Operators,包括:delete、void、typeof、++ 、-- 、+ 、- 、~ 、!
11.5乘法运算符Multiplicative Operators,包括:*、/、%。
11.6加法运算符Additive Operators ,包括:+、-
11.7按位移位运算符Bitwise Shift Operators,包括:<<、>>、>>>
11.8关系运算符Relational Operators ,<、>、<=、>=、instanceof、in
11.9相等运算符Equality Operators ,包括:==、!=、===、!==
11.10 二进制位运算符Binary Bitwise Operators ,包括:&、|、 ^
11.11 二进制逻辑运算符Binary Logical Operators ,包括&&、||
11.12 条件运算符Conditional Operator ,?:
11.13赋值运算符Assignment Operators
11.14逗号运算符Comma Operator。
个人不习惯标准的归类方式,重新梳理整理了一下,放在下一章说明。
标准“12 Statements语句”
12.1块控制 {}括起来的一段是块。与对象定义表达式非常相似。对于单独的{a:1},将视作块定义,而非对象定义。
12.2 变量声明语句,例如 var v;
12.3空语句 ;
12.4表达式语句。例如delete a.b;
分支语句:12.5讲if...else,12.11讲switch...case,12.8讲break。
循环语句:12.6讲do...while循环、while循环、for循环、for...in循环,12.7讲continue,12.8讲break。
12.10 with语句。参考:Javascript with语句_js with 语句-CSDN博客
12.12 Labelled Statements。标签语句在C语言中也有,往往与goto连用。而在ECMAScript中,标签语句是与break或continue一起使用的。详情可以参考:JavaScript系列之 break 和 continue_js break continue-CSDN博客
12.13~14 讲异常捕获语句throw...try...catch
12.15 debugger语句。这一句的作用是在源代码中增加断点,用不上,vsCode、Chrome等调试器不需要这个语句也能增加断点。
标准第13~14章 函数与程序
先扫过。放到本文下一章重点学习。
标准第15章:标准内置对象
这章相当于一本API速查手册了。标准内置对象包括12种:
关键是要搞清楚es5标准的叙述方式,才能查出用法来。
每一种对象基本都是按把构造器当作函数调用、构造器、构造器属性、原型属性、实例属性的结构讲解。
构造器既可以用var v=new Date()这样的方式调用,也可以用var v=Date()这样的方式调用,后者就是把构造器当作函数调用。这是ECMAScript迥异于其它编程语言之处。
关于构造器属性、原型属性、实例属性,放到本文下一章重点学习。
标准剩余章节
“16 Errors”,说明ECMAScript实现如何报告错误。
附件A:语法摘要,不用看。
附件B:兼容性,不用看。
附件C:严格模式,单独学习,不用看。
附件D~F:一些声明、澄清什么的。
最后是参考文献。
所以快过时这剩下的部分都不用看了。
4 es5标准重点问题
下面这些内容需要实践检验。因为代码一般都非常简单,所以可以直接利用Chrome的Snippets。结果呈现在console面板中,检查起来非常方便。我们甚至可以直接在console面板中输入一些表达式来得到结果,就像调试中的监视:
蓝色右箭头输入表达式,灰色左箭头出结果。
ECMAScript的运算符
按待操作的表达式/操作数的个数,各编译语言一般分我一元、二元、三元3种运算符,而且不同语言的运算符功能相似。 这里重点关注ECMAScript的特殊之处。下面不按es5标准,而仿照C语言按功能和形式区分运算符。首先按形式分为符号类和缀词类,然后把符号类分为算术、位操、逻辑、比较、其它符号这5类。
算术运算符和位运算符一般在作用于非Number时,会转化为Number进行(自增自减不转),Number的内部存储形式是64位双精度浮点数。需要注意的是它们要转换成什么目标,有的是转化成浮点数,有的是转化成32位有符号整数,有的是转化成32位无符号整数。
关于+号,能按数字运算就按数字运算,不能按数字运算就转换为字符串连接。具体可以参考:JavaScript加法运算符(+号)详解_js +号-CSDN博客
算术运算符
包括:++ 、-- 、+ 、- 、* 、/、%
++、--:自增自减,可以置前置后,与其它语言一致。
+、-:一元的取正/反,或者二元的加减。操作对象转化成浮点数。
*、/:。操作对象转化成浮点数。
%:ECMAScript 语言定义浮点操作%为与 Java 取余运算符一致。操作对象转化成浮点数。
位运算符
包括7种:&、|、 ^、 ~、>>、<<、>>>。相比C语言增加了>>>这一种。
前6种会将操作对象转化成32 位有符号整数。>>>则将操作对象转化成32 位无符号整数。详见后文“关于整数”一节。
逻辑运算符
包括&&、||、!
比较运算符
包括:>、<、==、>=、<=、!=、===、!==
大于小于等于既可作算术比较,也可作字符串比较。等不等于还可用作对象的比较。
其它符号类运算符
包括:+(字符串连接)、[]、{}、=(赋值)、?:(条件)、,(逗号)、.(点号)
缀词类运算符
把以单词而非符号表达的运算符归集到这里,包括:new、delete、void、typeof、instanceof、in
delete 操作符:用于删除对象的某个属性。
void 操作符:用于计算一个表达式,并且返回 undefined。因为undefined不是保留字关键字,在源码中用void 0代替undefined是一个好习惯。
typeof:见后文。
ECMAScript的对象
有别于其它语言的对象概念
有人说,JS不仅仅支持面向对象这种编程范式,它还支持许多种编程范式,比如函数式、过程式、事件驱动式,等等。总之,各方意见表明ECMAScript与普通面向对象的编程语言区别巨大。
在C#、JAVA等语言中,对象Object是类Class的实例,class-object是一种特殊的type-value。ECMAScript不这么考虑,而是以对象为中心,但也不得不在第6版引入class。正是因为ECMAScript企图抛弃type而只关心value,结果反而导致与type有关的概念比较混乱,不想要类型,结果处处要提到类型。例如,一个明显有碍软件开发的问题是,我们常常不知道我们从某个函数取得的对象,它是什么类型的?又如一个与类型相关的重要概念是:ECMAScript中所有的变量/对象都可以分作两大类型:原始值类型和引用类型,引用类型都是从Objec的子孙;原始值类型就是普通语言中的值类型、基本类型,它不会有什么属性、方法,就是一个值;要在这种类型的数据上附加方法,必须存在C#中的装箱操作,把它重新装配成一个Object的子孙。总之,ECMAScript标准要强调的是,这里的class与一般面向对象中的class不一样,至少有很大的不一样。
总之,在ECMAScript程序中,一切皆对象,一切对象都有属性由方法。不能被通常对象包纳的东西也被虚构的全局对象global(见1.6节)包纳了。全局变量和全局函数是全局对象的可配置属性。
对象是属性的集合。例如,对于:var person = {firstName:"John", lastName:"Doe", age:50, eyeColor:"blue"}; (这正是数学中可列集的写法),在对象person中,firstName就是一个属性,age是另一个属性。属性的名称和值构成键值对,故也可认为对象是键值对的集合。事实上,ECMAScript支持直接按字典的方式来访问属性。例如对于前面的变量person,可以用person['firstName']来访问属性firstName。
既然一切都是对象,那么原始值类型的变量如var car = "Fiat";,或者函数,应该都可以化作这个集合的形式。当然不能直接在ECMAScript程序中把它们改写成可列集的形式。不过这说明我们以后应该重点关注任何一个对象,它是由哪些属性组成的。
ECMAScript中的继承不是class的继承,而是对象的继承。C#、Java等语言中继承是类对类/接口的继承。ECMAScript不是这样,而是对象B对对象A的继承(具体应该是实现于原型链上)。如何理解?若对象B继承于对象A,则A有的属性,B也有,但B还有扩充;A和B都是对象,不是类型,不是类/接口;如果修改了A的属性,则B从A继承来的属性也同时被修改了。继承不是克隆,不是赋值。从某种意义上看,原型可以理解为class,所以继承也可以看作是class的继承或者class的实例化。
对象的类型与typeof、instanceof
如前所述,尽管es5没有class,但避免不了type的概念,也必须要用typeof/instanceof来检查对象的类型。
我们按类型归纳一下es5中的对象:(1)普通的可以视作class实例的对象(2)特殊的global对象(3)函数(4)原始值。前两种的类型是Object,函数的类型是function,5类原始值(Undefined, Null, Boolean, Number, String)本来应该是各对各的,但null比较特殊地划作了Object。这是typeof的归类。
typeof是一个运算符,有2种使用方式:typeof(表达式)和typeof 变量名,第一种是对表达式做运算,第二种是对变量做运算。
typeof运算符的返回类型为字符串(尽管ECMAScript是弱类型语言),值包括如下几种:
val类型 | 结果 |
---|---|
Undefined | "undefined" |
Null | "object" |
Boolean | "boolean" |
Number | "number" |
String | "string" |
Object(原生,且没有实现 [[call]]) | "object" |
Object(原生或者宿主且实现了 [[call]]) | "function" |
Object(宿主且没实现 [[call]]) | 由实现定义,但不能是 "undefined", "boolean", "number", or "string" |
与前文归纳所述一致。实现[[call]]的就是函数。注意这里提到宿主环境扩充的Object可能是“object",也可能是"function"。
ES11新增了BigInt以区别于普通的Number。
ES6新增了Symbol以区别于string。
总之,typeof对原始值的处理是比较特殊的,其它要不是object,要不就是function。
typeof仅仅的作大概检查,更精确的检查要用instanceof。instanceof 运算符可用于检测构造函数的 prototype 属性是否出现在某个实例对象的原型链上。即若对象B的父亲/祖宗是A,则B instance of A返回true;若对象B不是对象A的子孙,则 B instance of A返回false。
Object是不是一切对象的始祖?
虽然typeof区分了不同的对象类型,但是归根结底一切对象都是Object,所以Object应该可以视作一切对象的始祖。但是原始值这种东西太特殊了,尽管ECMAScript再三强调一切皆对象的思想,仍然逃不掉原始值必须经过装配才能变成Object的子孙(null和undefined除外),不经过装配的真正“原始”的值就仅仅是一个值!
globalThis instanceof Object返回true(浏览器中肯定如此,因为globalThis的本质是window;node.js测试也是true)。Function instanceof Object返回true。a={},则a instanceof Object返回true。
原始值对象不是Object的子孙!例如3 instanceof Object返回false。null instanceof Object返回false。undefined instanceof Object返回false。'3' instanceof Object返回false。就算令一个变量v等于原始值,v instanceof Object仍然返回false!
一切对象继承了Object的属性。用Object 的 原型属性(标准15.2.4)可以检验这一点。
例如用属于原型属性之一的constructor属性:
function func() {
this.a = 1;
console.log("hello");
}
console.log(func.constructor);
var v = NaN; // NaN、Infinity都是Number
console.log(v.constructor);//但是不能直接用3.constructor,NaN.constructor等。
v = 'abc';
console.log(v.constructor);//可以直接用'abc'.constructor
v = new Object();
console.log(v.constructor);
运行结果是:
虽然原始值对象不是Object的子孙,但我们用标准中规定于Object而没有规定于原始值如布尔对象的原型属性,作用于原始值对象,仍然是有效的。这是因为语言自动进行了装箱操作,把null和undefined以外的原始值装配成了一个同种类的原始值对象。
下面的代码可以印证:
var v = true;// v = '3' // v = 3;
console.log(v instanceof Object);
console.log(v.hasOwnProperty('a'));
console.log(v.isPrototypeOf('b'));
若v是布尔对象、数字对象、字符串对象,则Object的原型属性可以访问。
但若v是null或者undefined,则不行。
也就是说,布尔、数字、字符串不是Object的子孙却貌似Object的子孙,而null和undefined坚决不是Object的子孙。null是最奇怪的,typeof null的结果是“object”,这是历史遗留问题。
构造器属性、原型属性、实例属性
标准在描述各种对象的属性时,是按构造器属性(Properties of the Object Constructor)、原型属性(Properties of the Object Prototype Object)、实例属性(Properties of Object Instances)分章节的。但也特例,如Math是个单一的对象,没有构造器,标准就按值属性和函数属性分类。
什么是构造器属性?对于var v=new Number(3);Number就是构造器。标准规定Number有个构造器属性是Number.MAX_VALUE,则我们可以用Number.MAX_VALUE取得一个值,而不能用v.MAX_VALUE来取得这个值。又比如一般的对象都有个构造器属性prototype,于是我们可以访问Object.prototype,Date.prototype,但不能访问v.prototype。
什么是原型属性?仍以var v=new Number(3);举例,标准规定Number有个原型属性是Number.prototype.constructor,则我们可以用v.constructor取得这个东西。标准规定Number有个原型属性是Number.prototype.toString(),则我们可以用v.toString()取得这个东西。但是我们仍然能够访问Number.constructor、Number.toString(),这是为什么呢?是因为Number被看作了函数对象(typeof Number返回'function',Number instanceof Function返回true)(注意Number.constructor === Number的结论是false,),因此可以想v.那样访问其原型属性。
什么是实例属性?应该是依附实例而存在的属性,与原型属性比较像。多数都是不可访问的。一个可访问的例子是数组的length,我们当然不能在源代码中写Array.length,只能用v.length(假设v是数组)。
列举对象的属性
在 JavaScript 中枚举对象属性_js 枚举-CSDN博客 说有3种内置的办法来列举(标题用列举以防止与枚举类型的概念混淆)对象的属性:for...in、object keys和 object.entries。但是只能列出可枚举属性(即这3种办法要列的属性都是相同的),且不能枚举Symbol对象的属性(es6及后的标准)。什么是可枚举属性?一个属性的Enumerable特性为true,则此属性可枚举。
从这3个方法我们更能看出对象是属性的集合,属性是键值对key-value,对象是键值对的集合。对象就像是一个字典,属性名称是key,属性值就是key所对应的value。字典的key-value可以动态增减,故对象的属性也可以动态增减,这可能就是ECMAScript动态语言的生成机制之一。
我尝试用下面的代码测试了一下:
var b={a:2, b:'c'};
var v = Object.entries(b); //这里把b换成Object等进行测试
for (const [propName, propValue] of v) {
console.log(`${propName}=${propValue}`);
}
发现Object、Number以及自己定义的函数,都没有可枚举属性。但是上面代码中的变量b有。这可能意味着内置的属性基本上都不可以枚举。
ECMAScript的函数
见另一篇JS的函数-CSDN博客。
ECMAScript的原始值
原始值本身比较简单,不用多说。但它们在ECMAScript中还有些特异之处,需要注意。例如,用3.toString()会报错,但是用var v=3;v.toString()则不会报错;用(3).toString()也不会报错,可以得到正确结果。
下面尝试列出特异之处,也一并把一些简单常用但需要速查的内容列一下。
前文说了,原始值对象不是Object的子孙,instanceof Object返回false。布尔、数字、字符串貌似Object的子孙,可以调用Object的原型属性;null和undefined不调用Object的原型属性。
为什么存在Value、Type、Object三种概念
Value和Type是针对值类型而言,Object是针对引用类型而言。例如,true.toString(),true是值类型的一种value,布尔是值类型,而toString()是Boolean的对象方法,这里存在一个隐式的从值类型到引用类型的转换。
null与undefined
为什么会有两种?null专用于Object类型?undefined可用于一切类型。为什么要搞出两种类型,而不是合并为一种?这个问题仁者见仁智者见智,没有答案。
关于整数
ECMAScript 中所有的Number都是以64位双精度浮点数实现的。浮点数是无法严格相等的,例如0.1+0.2≠0.3。
在处理整数问题如二进制位操作时,宿主环境要将浮点数转换为整数,再进行整数操作,完了再转换为浮点数存下来。所以程序员看到的始终是浮点数。
在浮点数转换为整数的过程中,按整数操作的数值范围取模。例如整数操作对象是32位无符号数,则原来的number将被转换为取整后的number % 2^32。
ECMAScript 中的整数操作默认是按有符号整数进行的。 如果处理的对象是无符号整数,则需要注意:
(1)toString()输出二进制、八进制、十六进制时,是带符号的。例如:(-0x3c).toString(16)将得到字符串"-3c"。
(2)>>的操作对象是32位有符号整数,>>>的操作对象是32位无符号整数。
(3)~的操作对象是32位有符号整数。~x的结果,是将x转化成32位有符号整数y,再将y的二进制补码的所有bit(32个bit连同符号位)取反,取反完成后的结果再视作32位有符号整数。等效结果是,在32位有符号整数有效表达范围内,一个数x与自身的取反值~x相加,等于-1。
我们要怎么得到单个字节0x3C按位取反的结果字符串呢?((~0x3c) & 0xff).toString(16)即可。其实Java也是这样的。
ECMAScript标准中,整数的最大值为2^53-1,这是因为双精度浮点数只能精确表示15位十进制有效数字,超过这个范围就会出现精度丢失问题。因此,在处理超出这个范围的大整数时,JavaScript会出现异常。过去的开发人员不得不使用第三方库或自己实现大整数运算。为了更好,ECMAScript 2020标准引入了BigInt数据类型。
关于数的进制
JavaScript中的多种进制与进制转换_普通网友的博客-CSDN博客 提到,进制有四种:十进制、二进制、十六进制、八进制,用不同的前缀来区分:十进制不用前缀,二进制前缀 0b 或 0B,十六进制前缀 0x 或 0X,八进制前缀 0o 或 0O (ES6规定)。
参考
ECMA-262标准文档:ECMA-262 - Ecma International(全部历史版本在这个网页的下方)
es5标准:ECMAScript Language Specification - ECMA-262 Edition 5.1
ECMA-262_5.1中文版参考:文本说明ecma26251中文版.pdf-原创力文档
es6标准:ECMAScript 2015 Language Specification – ECMA-262 6th Edition