第3章 3.1 3.2 3.3
今日思考:
- 打印‘hi’
原因:去掉之前的var操作符之后,message就变成了全局变量。只要调用一次函数test(),就会定义这个变量,并且可以在函数外部访问到。
- let声明的范围是块作用域,var声明的范围是函数作用域。
使用var操作符定义的变量会成为包含它的函数的局部变量。
- 打印结果:Matt; undefined
原因:使用var在全局作用域中声明的变量会成为window对象的属性, let则不会
- 打印结果:5、5、5、5、5
原因:在退出循环时,迭代变量保存的是导致循环退出的值:5。在之后执行超时逻辑时,所有的i都是同一个变量,因而输出的都是同一个最终值。
- 使用const声明可以让浏览器运行时强制保持变量不变,也可以让静态代码分析工具提前发现不合法的赋值操作。
参考答案:
https://www.yuque.com/docs/share/a5ceafd0-2816-46c1-a281-4a089164562b?
🇺🇳 需要个人注意的点:
所谓标识符,就是变量、函数、属性或函数参数的名称
for循环中的let声明
在let出现之前,for循环定义的迭代变量会渗透到循环体外部:
改成使用let之后,这个问题就消失了,因为迭代变量的作用域仅限于for循环块内部:
在使用var的时候,最常见的问题就是对迭代变量的奇特声明和修改:
之所以会这样,是因为在退出循环时,迭代变量保存的是导致循环退出的值:5。
在之后执行超时逻辑时,所有的i都是同一个变量,因而输出的都是同一个最终值。
而在使用let声明迭代变量时,JavaScript引擎在后台会为每个迭代循环声明一个新的迭代变量。每个setTimeout引用的都是不同的变量实例,所以console.log输出的是我们期望的值,也就是循环执行过程中每个迭代变量的值。
这种每次迭代声明一个独立变量实例的行为适用于所有风格的for循环,包括for-in和for-of循环。
- const声明
const的行为与let基本相同,唯一一个重要的区别是用它声明变量时必须同时初始化变量,且尝试修改const声明的变量会导致运行时错误。
const声明的限制只适用于它指向的变量的引用。换句话说,如果const变量引用的是一个对象,那么修改这个对象内部的属性并不违反const的限制。
JavaScript引擎会为for循环中的let声明分别创建独立的变量实例,虽然const变量跟let变量很相似,但是不能用const来声明迭代变量(因为迭代变量会自增):
不过,如果你只想用const声明一个不会被修改的for循环变量,那也是可以的。也就是说,每次迭代只是创建一个新变量。这对for-of和for-in循环特别有意义:
第3章 3.4.1-3.4.6
今日思考:
- 如何正确的判断一个变量的类型?(除了typeof)
■ typeof: Number,String,Boolean,Function,undefined,如果想判断这几种类型,那就可以使用
Array,Object,null,Date,RegExp,Error这几个类型都被typeof判断为object
■ instanceof:instanceof运算符需要指定一个构造函数,或者说指定一个特定的类型,它用来判断这个构造函数的原型是否在给定对象的原型链上console.log(123 instanceof Number, //false'dsfsf' instanceof String, //falsefalse instanceof Boolean, //falseundefined instanceof Object, //falsenull instanceof Object, //false
[1,2,3] instanceof Array, //true{a:1,b:2,c:3} instanceof Object, //truefunction(){console.log('aaa');} instanceof Function, //truenew Date() instanceof Date, //true/^[a-zA-Z]{5,20}$/ instanceof RegExp, //truenew Error() instanceof Error //true)
可以发现如下规律:
Number,String,Boolean没有检测出他们的类型,但是如果使用下面的写法则可以检测出来:var num = new Number(123);var str = new String('dsfsf');var boolean = new Boolean(false);
null和undefined都返回了false,这是因为它们的类型就是自己本身,并不是Object创建出来它们,所以返回了false。
■ constructor:是prototype对象上的属性,指向构造函数。根据实例对象寻找属性的顺序,若实例对象上没有实例属性或方法时,就去原型链上寻找,因此,实例对象也是能使用constructor属性的var num = 123;var str = 'abcdef';var bool = true;var arr = [1, 2, 3, 4];var json = {name:'wenzi', age:25};var func = function(){ console.log('this is function'); }var und = undefined;var nul = null;var date = new Date();var reg = /^[a-zA-Z]{5,20}$/;var error= new Error();function Person(){ } var tom = new Person();// undefined和null没有constructor属性console.log(tom.constructor==Person,num.constructor==Number,str.constructor==String,bool.constructor==Boolean,arr.constructor==Array,json.constructor==Object,func.constructor==Function,date.constructor==Date,reg.constructor==RegExp,error.constructor==Error); //所有结果均为true
除了undefined和null之外,其他类型都可以通过constructor属性来判断类型。
■ toString()检测对象类型
可以通过toString() 来获取每个对象的类型.
为了每个对象都能通过 Object.prototype.toString() 来检测,需要以 Function.prototype.call() 或者 Function.prototype.apply() 的形式来调用,传递要检查的对象作为第一个参数,称为thisArg。var toString = Object.prototype.toString;toString.call(123); //"[object Number]"toString.call('abcdef'); //"[object String]"toString.call(true); //"[object Boolean]"toString.call([1, 2, 3, 4]); //"[object Array]"toString.call({name:'wenzi', age:25}); //"[object Object]" toString.call(function(){ console.log('this is function'); }); //"[object Function]"toString.call(undefined); //"[object Undefined]"toString.call(null); //"[object Null]"toString.call(new Date()); //"[object Date]"toString.call(/^[a-zA-Z]{5,20}$/); //"[object RegExp]"toString.call(new Error()); //"[object Error]"
总结:使用Object.prototype.toString.call()的方式来判断一个变量的类型是最准确的方法。
封装一个获取变量准确类型的函数:
参考:https://www.cnblogs.com/hahazexia/p/8886829.html
- 数据类型:
ECMAScript有6种简单数据类型(也称为原始类型): Undefined、Null、Boolean、Number、String和Symbol。Symbol(符号)是ECMAScript 6新增的。
还有一种复杂数据类型叫Object(对象)。Object是一种无序名值对的集合。
- typeof操作符可以确定任意变量的数据类型
特殊情况:
调用typeofnull返回的是”object”。这是因为特殊值null被认为是一个对空对象的引用。
对未初始化的变量调用typeof时,返回的结果是”undefined”
对未声明的变量调用它时,返回的结果仍然是”undefined”
(无论是声明还是未声明,typeof返回的都是字符串”undefined”。逻辑上讲这是对的,因为虽然严格来讲这两个变量存在根本性差异,但它们都无法执行实际操作。)
注:严格来讲,函数在ECMAScript中被认为是对象,并不代表一种数据类型。可是,函数也有自己特殊的属性。为此,就有必要通过typeof操作符来区分函数和其他对象。
- Null类型
Null类型同样只有一个值,即特殊值null。逻辑上讲,null值表示一个空对象指针,这也是给typeof传一个null会返回”object”的原因
在定义将来要保存对象值的变量时,建议使用null来初始化,不要使用其他值。这样,只要检查这个变量的值是不是null就可以知道这个变量是否在后来被重新赋予了一个对象的引用,比如:
undefined值是由null值派生而来的,因此ECMA-262将它们定义为表面上相等,如下面的例子所示:
任何时候,只要变量要保存对象,而当时又没有那个对象可保存,就要用null来填充该变量。这样就可以保持null是空对象指针的语义
- Boolean类型
不同类型与布尔值之间的转换规则:
- Number类型
Number类型使用IEEE 754格式表示整数和浮点值(在某些语言中也叫双精度值)
- 不同的数值类型相应地也有不同的数值字面量格式
eg: 十进制,八进制,十六进制…
- 浮点值
定义浮点值,数值中必须包含小数点,而且小数点后面必须至少有一个数字。虽然小数点前面不是必须有整数,但推荐加上
因为存储浮点值使用的内存空间是存储整数值的两倍,所以ECMAScript总是想方设法把值转换为整数。在小数点后面没有数字的情况下,数值就会变成整数。类似地,如果数值本身就是整数,只是小数点后面跟着0(如1.0),那它也会被转换为整数
对于非常大或非常小的数值,浮点值可以用科学记数法来表示。
科学记数法用于表示一个应该乘以10的给定次幂的数值。
ECMAScript中科学记数法的格式要求是:一个数值(整数或浮点数)后跟一个大写或小写的字母e,再加上一个要乘的10的多少次幂。比如:
注:以3.125作为系数,乘以10的7次幂
0.00000000000000003, 这个数值用科学记数法可以表示为3e-17
特殊例子:
浮点值的精确度最高可达17位小数,但在算术计算中远不如整数精确。例如,0.1加0.2得到的不是0.3,而是0.30000000000000004。由于这种微小的舍入错误,导致很难测试特定的浮点值。(之所以存在这种舍入错误,是因为使用了IEEE 754数值,这种错误并非ECMAScript所独有。其他使用相同格式的语言也有这个问题。)
- 值的范围
ECMAScript可以表示的最小数值保存在Number.MIN_VALUE中,这个值在多数浏览器中是5e-324;
可以表示的最大数值保存在Number.MAX_VALUE中,这个值在多数浏览器中是1.797693134862315 7e+308。
如果某个计算得到的数值结果超出了JavaScript可以表示的范围,那么这个数值会被自动转换为一个特殊的Infinity(无穷)值。任何无法表示的负数以-Infinity(负无穷大)表示,任何无法表示的正数以Infinity(正无穷大)表示。
如果计算返回正Infinity或负Infinity,则该值将不能再进一步用于任何计算。这是因为Infinity没有可用于计算的数值表示形式。
要确定一个值是不是有限大(即介于JavaScript能表示的最小值和最大值之间),可以使用isFinite()函数,如下:
- NaN
有一个特殊的数值叫NaN,意思是“不是数值”(Not a Number),用于表示本来要返回数值的操作失败了(而不是抛出错误)
比如,用0除任意数值在其他语言中通常都会导致错误,从而中止代码执行。但在ECMAScript中,0、+0或-0相除会返回NaN:
如果分子是非0值,分母是有符号0或无符号0,则会返回Infinity或-Infinity:
NaN有几个独特的属性。
首先,任何涉及NaN的操作始终返回NaN(如NaN/10),在连续多步计算时这可能是个问题。
其次,NaN不等于包括NaN在内的任何值。
例如,下面的比较操作会返回false:
为此,ECMAScript提供了isNaN()函数。
该函数接收一个参数,可以是任意数据类型,然后判断这个参数是否“不是数值”。
把一个值传给isNaN()后,该函数会尝试把它转换为数值。
某些非数值的值可以直接转换成数值,如字符串”10”或布尔值。
任何不能转换为数值的值都会导致这个函数返回true。
举例如下:
- 数值转换
有3个函数可以将非数值转换为数值:Number()、parseInt()和parseFloat()。
Number()是转型函数,可用于任何数据类型。
后两个函数主要用于将字符串转换为数值。
对于同样的参数,这3个函数执行的操作也不同。
Number()函数基于如下规则执行转换:
❑ 布尔值,true转换为1,false转换为0。
❑ 数值,直接返回。
❑ null,返回0。
❑ undefined,返回NaN。
❑ 字符串,应用以下规则。
■ 如果字符串包含数值字符,包括数值字符前面带加、减号的情况,则转换为一个十进制数值。因此,Number(“1”)返回1, Number(“123”)返回123, Number(“011”)返回11(忽略前面的零)。
■ 如果字符串包含有效的浮点值格式如”1.1”,则会转换为相应的浮点值(同样,忽略前面的零)。
■ 如果字符串包含有效的十六进制格式如”0xf”,则会转换为与该十六进制值对应的十进制整数值。
■ 如果是空字符串(不包含字符),则返回0。
■ 如果字符串包含除上述情况之外的其他字符,则返回NaN。
❑ 对象,调用valueOf()方法,并按照上述规则转换返回的值。如果转换结果是NaN,则调用toString()方法,再按照转换字符串的规则转换。
例子:
parseInt()函数转换规则:
❑ 转换字符串:
考虑到用Number()函数转换字符串时相对复杂且有点反常规,通常在需要得到整数时可以优先使用parseInt()函数。
parseInt()函数更专注于字符串是否包含数值模式。
字符串最前面的空格会被忽略,从第一个非空格字符开始转换。
如果第一个字符不是数值字符、加号或减号,parseInt()立即返回NaN。这意味着空字符串也会返回NaN(这一点跟Number()不一样,它返回0)。
如果第一个字符是数值字符、加号或减号,则继续依次检测每个字符,直到字符串末尾,或碰到非数值字符。
(比如,”1234blue”会被转换为1234,因为”blue”会被完全忽略。
类似地,”22.5”会被转换为22,因为小数点不是有效的整数字符。)
假设字符串中的第一个字符是数值字符,parseInt()函数也能识别不同的整数格式(十进制、八进制、十六进制)
例子:
❑ 接收第二个参数:
不同的数值格式很容易混淆,因此parseInt()也接收第二个参数,用于指定底数(进制数)。
如果知道要解析的值是十六进制,那么可以传入16作为第二个参数,以便正确解析:
事实上,如果提供了十六进制参数,那么字符串前面的”0x”可以省掉:

在这个例子中,第一个转换是正确的,而第二个转换失败了。区别在于第一次传入了进制数作为参数,告诉parseInt()要解析的是一个十六进制字符串。而第二个转换检测到第一个字符就是非数值字符,随即自动停止并返回NaN。
通过第二个参数,可以极大扩展转换后获得的结果类型。比如:
parseFloat()函数转换规则:
❑ 从位置0开始检测每个字符。解析到字符串末尾或者解析到一个无效的浮点数值字符为止。
这意味着第一次出现的小数点是有效的,但第二次出现的小数点就无效了,此时字符串的剩余字符都会被忽略。
因此,”22.34.5”将转换成22.34。
❑ 始终忽略字符串开头的零。
能识别前面讨论的所有浮点格式,以及十进制格式(开头的零始终被忽略)。十六进制数值始终会返回0。因为parseFloat()只解析十进制值,因此不能指定底数。
例子:
- String类型
❑ 字符字面量字符串数据类型包含一些字符字面量,用于表示非打印字符或有其他用途的字符,如下表所示:
这些字符字面量可以出现在字符串中的任意位置,且可以作为单个字符被解释:
即使包含6个字符长的转义序列,变量text仍然是28个字符长。因为转义序列表示一个字符,所以只算一个字符。
字符串的长度可以通过其length属性获取
❑ 转换为字符串
方法一:toString()方法
这个方法唯一的用途就是返回当前值的字符串等价物。可见于数值、布尔值、对象和字符串值。null和undefined值没有toString()方法。
一般,toString()不接收任何参数。但是,在对数值调用这个方法时,toString()可以接收一个底数参数,即以什么底数来输出数值的字符串表示。
默认情况下,toString()返回数值的十进制字符串表示。
而通过传入参数,可以得到数值的二进制、八进制、十六进制,或者其他任何有效基数的字符串表示,
比如:
方法二:String()转型函数
它始终会返回表示相应类型值的字符串。
String()函数遵循如下规则。
❑ 如果值有toString()方法,则调用该方法(不传参数)并返回结果。
❑ 如果值是null,返回”null”。
❑ 如果值是undefined,返回”undefined”。
这里展示了将4个值转换为字符串的情况:一个数值、一个布尔值、一个null和一个undefined。数值和布尔值的转换结果与调用toString()相同。因为null和undefined没有toString()方法,所以String()方法就直接返回了这两个值的字面量文本。
❑ 模板字面量
与使用单引号或双引号不同,模板字面量保留换行字符,可以跨行定义字符串
在定义模板时特别有用
会保持反引号内部的空格,因此在使用时要格外注意。格式正确的模板字符串看起来可能会缩进不当
❑ 字符串插值
模板字面量最常用的一个特性是支持字符串插值,也就是可以在一个连续定义中插入一个或多个值。
技术上讲,模板字面量不是字符串,而是一种特殊的JavaScript句法表达式,只不过求值后得到的是字符串。
模板字面量在定义时立即求值并转换为字符串实例,任何插入的变量也会从它们最接近的作用域中取值。
字符串插值通过在${}中使用一个JavaScript表达式实现:
第三章 3.4.7 Symbol
Symbol(符号)是ECMAScript 6新增的数据类型。
符号是原始值,且符号实例是唯一、不可变的。
符号的用途是确保对象属性使用唯一标识符,不会发生属性冲突的危险。
1.符号的基本用法
符号需要使用Symbol()函数初始化。因为符号本身是原始类型,所以typeof操作符对符号返回symbol。
❑ 符号没有字面量语法,这也是它们发挥作用的关键。
按照规范,你只要创建Symbol()实例并将其用作对象的新属性,就可以保证它不会覆盖已有的对象属性,无论是符号属性还是字符串属性。
❑ Symbol()函数不能与new关键字一起作为构造函数使用。
这样做是为了避免创建符号包装对象,像使用Boolean、String或Number那样,它们都支持构造函数且可用于初始化包含原始值的包装对象:
2.使用全局符号注册表
使用Symbol.for()方法
Symbol.for()对每个字符串键都执行幂等操作。
第一次使用某个字符串调用时,它会检查全局运行时注册表,发现不存在对应的符号,于是就会生成一个新符号实例并添加到注册表中。
即使采用相同的符号描述,在全局注册表中定义的符号跟使用Symbol()定义的符号也并不等同:
使用Symbol.keyFor()来查询全局注册表,这个方法接收符号,返回该全局符号对应的字符串键。如果查询的不是全局符号,则返回undefined。
如果传给Symbol.keyFor()的不是符号,则该方法抛出TypeError:

3.使用符号作为属性
凡是可以使用字符串或数值作为属性的地方,都可以使用符号。
这就包括了对象字面量属性和Object.defineProperty()/Object.defineProperties()定义的属性。
对象字面量只能在计算属性语法中使用符号作为属性。
4.常用内置符号
ECMAScript 6也引入了一批常用内置符号(well-known symbol),用于暴露语言内部行为,开发者可以直接访问、重写或模拟这些行为。这些内置符号都以Symbol工厂函数字符串属性的形式存在。
所有内置符号属性都是不可写、不可枚举、不可配置的。
5.Symbol.asyncIterator
在ES6中,这个符号作为一个属性表示“一个方法,该方法返回对象默认的AsyncIterator。由for-await-of语句使用”。
换句话说,这个符号表示实现异步迭代器API的函数
6.Symbol.hasInstance
在ES6中,这个符号作为一个属性表示“一个方法,该方法决定一个构造器对象是否认可一个对象是它的实例。由instanceof操作符使用”。instanceof操作符可以用来确定一个对象实例的原型链上是否有原型。
在ES6中,instanceof操作符会使用Symbol.hasInstance函数来确定关系。以Symbol. hasInstance为键的函数会执行同样的操作,只是操作数对调了一下
由于instanceof操作符会在原型链上寻找这个属性定义,就跟在原型链上寻找其他属性一样,因此可以在继承的类上通过静态方法重新定义这个函数
7.Symbol.isConcatSpreadable
根据ECMAScript规范,这个符号作为一个属性表示“一个布尔值,如果是true,则意味着对象应该用Array.prototype.concat()打平其数组元素”。
ES6中的Array.prototype.concat()方法会根据接收到的对象类型,选择如何将一个类数组对象拼接成数组实例。覆盖Symbol.isConcat-Spreadable的值可以修改这个行为。
8.Symbol.iterator
根据ECMAScript规范,这个符号作为一个属性表示“一个方法,该方法返回对象默认的迭代器。由for-of语句使用”。
换句话说,这个符号表示实现迭代器API的函数。
9.Symbol.match
根据ECMAScript规范,这个符号作为一个属性表示“一个正则表达式方法,该方法用正则表达式去匹配字符串。由String.prototype.match()方法使用”。
String.prototype.match()方法会使用以Symbol.match为键的函数来对正则表达式求值。
正则表达式的原型上默认有这个函数的定义,因此所有正则表达式实例默认是这个String方法的有效参数
10.Symbol.replace
根据ECMAScript规范,这个符号作为一个属性表示“一个正则表达式方法,该方法替换一个字符串中匹配的子串。由String.prototype.replace()方法使用”。
String.prototype.replace()方法会使用以Symbol.replace为键的函数来对正则表达式求值。正则表达式的原型上默认有这个函数的定义,因此所有正则表达式实例默认是这个String方法的有效参数
11.Symbol.search
根据ECMAScript规范,这个符号作为一个属性表示“一个正则表达式方法,该方法返回字符串中匹配正则表达式的索引。由String.prototype.search()方法使用”。
String.prototype.search()方法会使用以Symbol.search为键的函数来对正则表达式求值。
正则表达式的原型上默认有这个函数的定义,因此所有正则表达式实例默认是这个String方法的有效参数
12.Symbol.species
根据ECMAScript规范,这个符号作为一个属性表示“一个函数值,该函数作为创建派生对象的构造函数”。
这个属性在内置类型中最常用,用于对内置类型实例方法的返回值暴露实例化派生对象的方法。
用Symbol.species定义静态的获取器(getter)方法,可以覆盖新创建实例的原型定义
13.Symbol.split
根据ECMAScript规范,这个符号作为一个属性表示“一个正则表达式方法,该方法在匹配正则表达式的索引位置拆分字符串。由String.prototype.split()方法使用”。
String.prototype. split()方法会使用以Symbol.split为键的函数来对正则表达式求值。
正则表达式的原型上默认有这个函数的定义,因此所有正则表达式实例默认是这个String方法的有效参数
14.Symbol.toPrimitive
根据ECMAScript规范,这个符号作为一个属性表示“一个方法,该方法将对象转换为相应的原始值。由ToPrimitive抽象操作使用”。
很多内置操作都会尝试强制将对象转换为原始值,包括字符串、数值和未指定的原始类型。对于一个自定义对象实例,通过在这个实例的Symbol.toPrimitive属性上定义一个函数可以改变默认行为。
15.Symbol.toStringTag
根据ECMAScript规范,这个符号作为一个属性表示“一个字符串,该字符串用于创建对象的默认字符串描述。由内置方法Object.prototype.toString()使用”。
通过toString()方法获取对象标识时,会检索由Symbol.toStringTag指定的实例标识符,默认为”Object”。内置类型已经指定了这个值,但自定义类实例还需要明确定义
16.Symbol.unscopables
根据ECMAScript规范,这个符号作为一个属性表示“一个对象,该对象所有的以及继承的属性,都会从关联对象的with环境绑定中排除”。设置这个符号并让其映射对应属性的键值为true,就可以阻止该属性出现在with环境绑定中
注意不推荐使用with,因此也不推荐使用Symbol.unscopables。
第三章 3.4.8 Object类型
ECMAScript中的对象其实就是一组数据和功能的集合。对象通过new操作符后跟对象类型的名称来创建。开发者可以通过创建Object类型的实例来创建自己的对象,然后再给对象添加属性和方法:
每个Object实例都有如下属性和方法:
❑ constructor:用于创建当前对象的函数。在前面的例子中,这个属性的值就是Object()函数。
❑ hasOwnProperty(propertyName):用于判断当前对象实例(不是原型)上是否存在给定的属性。要检查的属性名必须是字符串(如o.hasOwnProperty(“name”))或符号。
❑ isPrototypeOf(object):用于判断当前对象是否为另一个对象的原型。(第8章将详细介绍原型。)
❑ propertyIsEnumerable(propertyName):用于判断给定的属性是否可以使用(本章稍后讨论的)for-in语句枚举。与hasOwnProperty()一样,属性名必须是字符串。
❑ toLocaleString():返回对象的字符串表示,该字符串反映对象所在的本地化执行环境。
❑ toString():返回对象的字符串表示。
❑ valueOf():返回对象对应的字符串、数值或布尔值表示。通常与toString()的返回值相同。
因为在ECMAScript中Object是所有对象的基类,所以任何对象都有这些属性和方法。
第三章 3.5 操作符
3.5.1 一元操作符
只操作一个值的操作符叫一元操作符(unary operator)。一元操作符是ECMAScript中最简单的操作符。
1.递增/递减操作符
前缀版和后缀版。
前缀版:变量的值都会在语句被求值之前改变
后缀版:递增和递减在语句被求值后才发生
递增和递减操作符遵循如下规则:
❑ 对于字符串,如果是有效的数值形式,则转换为数值再应用改变。变量类型从字符串变成数值。
❑ 对于字符串,如果不是有效的数值形式,则将变量的值设置为NaN。变量类型从字符串变成数值。
❑ 对于布尔值,如果是false,则转换为0再应用改变。变量类型从布尔值变成数值。
❑ 对于布尔值,如果是true,则转换为1再应用改变。变量类型从布尔值变成数值。
❑ 对于浮点值,加1或减1。
❑ 如果是对象,则调用其(第5章会详细介绍的)valueOf()方法取得可以操作的值。对得到的值应用上述规则。如果是NaN,则调用toString()并再次应用其他规则。变量类型从对象变成数值。
2.一元加和减
一元加由一个加号(+)表示,放在变量前头,对数值没有任何影响
如果将一元加应用到非数值,则会执行与使用Number()转型函数一样的类型转换:布尔值false和true转换为0和1,字符串根据特殊规则进行解析,对象会调用它们的valueOf()和/或toString()方法以得到可以转换的值
一元减由一个减号(-)表示,放在变量前头,主要用于把数值变成负值。如把1转换为-1
对数值使用一元减会将其变成相应的负值(如上面的例子所示)。在应用到非数值时,一元减会遵循与一元加同样的规则,先对它们进行转换,然后再取负值
一元加和减操作符主要用于基本的算术,但也可以像上面的例子那样,用于数据类型转换。
3.5.2 位操作符
用于数值的底层操作,也就是操作内存中表示数据的比特(位)
ECMAScript中的所有数值都以IEEE 754 64位格式存储,但位操作并不直接应用到64位表示,而是先把值转换为32位整数,再进行位操作,之后再把结果转换为64位。对开发者而言,就好像只有32位整数一样,因为64位整数存储格式是不可见的。既然知道了这些,就只需要考虑32位整数即可。
有符号整数使用32位的前31位表示整数值。第32位表示数值的符号,如0表示正,1表示负。这一位称为符号位(sign bit),它的值决定了数值其余部分的格式。
正值以真正的二进制格式存储,即31位中的每一位都代表2的幂。第一位(称为第0位)表示20,第二位表示21,依此类推
负值以一种称为二补数(或补码)的二进制编码存储。
一个数值的二补数通过如下3个步骤计算得到:
(1)确定绝对值的二进制表示(如,对于-18,先确定18的二进制表示);
(2)找到数值的一补数(或反码),换句话说,就是每个0都变成1,每个1都变成0;
(3)给结果加1。
1.按位非
按位非操作符用波浪符(~)表示,它的作用是返回数值的一补数。按位非是ECMAScript中为数不多的几个二进制数学操作符之一
2.按位与
按位与操作符用和号(&)表示,有两个操作数。本质上,按位与就是将两个数的每一个位对齐,然后基于真值表中的规则,对每一位执行相应的与操作。
按位与操作在两个位都是1时返回1,在任何一位是0时返回0。
3.按位或
按位或操作符用管道符(|)表示,同样有两个操作数。
按位或操作在至少一位是1时返回1,两位都是0时返回0。
4.按位异或
按位异或用脱字符(^)表示,同样有两个操作数。
按位异或与按位或的区别是,它只在一位上是1的时候返回1(两位都是1或0,则返回0)。
5.左移
左移操作符用两个小于号(<<)表示,会按照指定的位数将数值的所有位向左移动。
比如,如果数值2(二进制10)向左移5位,就会得到64(二进制1000000)
注意,左移会保留它所操作数值的符号。比如,如果-2左移5位,将得到-64,而不是正64。
6.有符号右移
有符号右移由两个大于号(>>)表示,会将数值的所有32位都向右移,同时保留符号(正或负)。
有符号右移实际上是左移的逆运算。
比如,如果将64右移5位,那就是2
7.无符号右移
无符号右移用3个大于号表示(>>>),会将数值的所有32位都向右移。
对于正数,无符号右移与有符号右移结果相同。
仍然以前面有符号右移的例子为例,64向右移动5位,会变成2
对于负数,有时候差异会非常大。
与有符号右移不同,无符号右移会给空位补0,而不管符号位是什么。
对正数来说,这跟有符号右移效果相同。但对负数来说,结果就差太多了。
无符号右移操作符将负数的二进制表示当成正数的二进制表示来处理。
因为负数是其绝对值的二补数,所以右移之后结果变得非常之大
3.5.3 布尔操作符
布尔操作符一共有3个:逻辑非、逻辑与和逻辑或。
1.逻辑非
逻辑非操作符由一个叹号(!)表示,始终返回布尔值。
逻辑非操作符首先将操作数转换为布尔值,然后再对其取反。换句话说,逻辑非操作符会遵循如下规则:
❑ 如果操作数是对象,则返回false。
❑ 如果操作数是空字符串,则返回true。
❑ 如果操作数是非空字符串,则返回false。
❑ 如果操作数是数值0,则返回true。
❑ 如果操作数是非0数值(包括Infinity),则返回false。
❑ 如果操作数是null,则返回true。
❑ 如果操作数是NaN,则返回true。
❑ 如果操作数是undefined,则返回true。
同时使用两个叹号(! !),相当于调用了转型函数Boolean()。
2.逻辑与
逻辑与操作符由两个和号(&&)表示,应用到两个值
遵循如下规则:
❑ 如果第一个操作数是对象,则返回第二个操作数。
❑ 如果第二个操作数是对象,则只有第一个操作数求值为true才会返回该对象。
❑ 如果两个操作数都是对象,则返回第二个操作数。
❑ 如果有一个操作数是null,则返回null。
❑ 如果有一个操作数是NaN,则返回NaN。
❑ 如果有一个操作数是undefined,则返回undefined。
逻辑与操作符是一种短路操作符,意思就是如果第一个操作数决定了结果,那么永远不会对第二个操作数求值。
对逻辑与操作符来说,如果第一个操作数是false,那么无论第二个操作数是什么值,结果也不可能等于true
3.逻辑或
逻辑或操作符由两个管道符(||)表示
遵循如下规则:
❑ 如果第一个操作数是对象,则返回第一个操作数。
❑ 如果第一个操作数求值为false,则返回第二个操作数。
❑ 如果两个操作数都是对象,则返回第一个操作数。
❑ 如果两个操作数都是null,则返回null。
❑ 如果两个操作数都是NaN,则返回NaN。
❑ 如果两个操作数都是undefined,则返回undefined。
同样与逻辑与类似,逻辑或操作符也具有短路的特性。只不过对逻辑或而言,第一个操作数求值为true,第二个操作数就不会再被求值了
3.5.4 乘性操作符
3个乘性操作符:乘法、除法和取模。在处理非数值时,它们也会包含一些自动的类型转换。
如果乘性操作符有不是数值的操作数,则该操作数会在后台被使用Number()转型函数转换为数值。这意味着空字符串会被当成0,而布尔值true会被当成1。
1.乘法操作符乘法操作符由一个星号(*)表示,可以用于计算两个数值的乘积
乘法操作符在处理特殊值时也有一些特殊的行为:
❑ 如果操作数都是数值,则执行常规的乘法运算,即两个正值相乘是正值,两个负值相乘也是正值,正负符号不同的值相乘得到负值。如果ECMAScript不能表示乘积,则返回Infinity或-Infinity。
❑ 如果有任一操作数是NaN,则返回NaN。
❑ 如果是Infinity乘以0,则返回NaN。
❑ 如果是Infinity乘以非0的有限数值,则根据第二个操作数的符号返回Infinity或-Infinity。
❑ 如果是Infinity乘以Infinity,则返回Infinity。
❑ 如果有不是数值的操作数,则先在后台用Number()将其转换为数值,然后再应用上述规则。
2.除法操作符
除法操作符由一个斜杠(/)表示,用于计算第一个操作数除以第二个操作数的商
除法操作符针对特殊值也有一些特殊的行为:
❑ 如果操作数都是数值,则执行常规的除法运算,即两个正值相除是正值,两个负值相除也是正值,符号不同的值相除得到负值。如果ECMAScript不能表示商,则返回Infinity或-Infinity。
❑ 如果有任一操作数是NaN,则返回NaN。
❑ 如果是Infinity除以Infinity,则返回NaN。
❑ 如果是0除以0,则返回NaN。
❑ 如果是非0的有限值除以0,则根据第一个操作数的符号返回Infinity或-Infinity。
❑ 如果是Infinity除以任何数值,则根据第二个操作数的符号返回Infinity或-Infinity。
❑ 如果有不是数值的操作数,则先在后台用Number()函数将其转换为数值,然后再应用上述规则。
3.取模操作符
取模(余数)操作符由一个百分比符号(%)表示
取模操作符对特殊值也有一些特殊的行为:
❑ 如果操作数是数值,则执行常规除法运算,返回余数。
❑ 如果被除数是无限值,除数是有限值,则返回NaN。
❑ 如果被除数是有限值,除数是0,则返回NaN。
❑ 如果是Infinity除以Infinity,则返回NaN。
❑ 如果被除数是有限值,除数是无限值,则返回被除数。
❑ 如果被除数是0,除数不是0,则返回0。
❑ 如果有不是数值的操作数,则先在后台用Number()函数将其转换为数值,然后再应用上述规则。
3.5.5 指数操作符
Math.pow()现在有了自己的操作符**,结果是一样的
3.5.6 加性操作符
加性操作符,即加法和减法操作符,一般都是编程语言中最简单的操作符。
加性操作符在后台会发生不同数据类型的转换。只不过对这两个操作符来说,转换规则不是那么直观。
1.加法操作符
加法操作符(+)用于求两个数的和
如果两个操作数都是数值,加法操作符执行加法运算并根据如下规则返回结果:
❑ 如果有任一操作数是NaN,则返回NaN;
❑ 如果是Infinity加Infinity,则返回Infinity;
❑ 如果是-Infinity加-Infinity,则返回-Infinity;
❑ 如果是Infinity加-Infinity,则返回NaN;
❑ 如果是+0加+0,则返回+0;
❑ 如果是-0加+0,则返回+0;
❑ 如果是-0加-0,则返回-0。
如果有一个操作数是字符串,则要应用如下规则:
❑ 如果两个操作数都是字符串,则将第二个字符串拼接到第一个字符串后面;
❑ 如果只有一个操作数是字符串,则将另一个操作数转换为字符串,再将两个字符串拼接在一起。
如果有任一操作数是对象、数值或布尔值,则调用它们的toString()方法以获取字符串,然后再应用前面的关于字符串的规则。对于undefined和null,则调用String()函数,分别获取”undefined”和”null”
2.减法操作符
规则:
❑ 如果两个操作数都是数值,则执行数学减法运算并返回结果。
❑ 如果有任一操作数是NaN,则返回NaN。
❑ 如果是Infinity减Infinity,则返回NaN。
❑ 如果是-Infinity减-Infinity,则返回NaN。
❑ 如果是Infinity减-Infinity,则返回Infinity。
❑ 如果是-Infinity减Infinity,则返回-Infinity。
❑ 如果是+0减+0,则返回+0。
❑ 如果是+0减-0,则返回-0。
❑ 如果是-0减-0,则返回+0。
❑ 如果有任一操作数是字符串、布尔值、null或undefined,则先在后台使用Number()将其转换为数值,然后再根据前面的规则执行数学运算。如果转换结果是NaN,则减法计算的结果是NaN。
❑ 如果有任一操作数是对象,则调用其valueOf()方法取得表示它的数值。如果该值是NaN,则减法计算的结果是NaN。如果对象没有valueOf()方法,则调用其toString()方法,然后再将得到的字符串转换为数值。
3.5.7 关系操作符
关系操作符执行比较两个值的操作,包括小于(<)、大于(>)、小于等于(<=)和大于等于(>=),这几个操作符都返回布尔值,
规则:
❑ 如果操作数都是数值,则执行数值比较。
❑ 如果操作数都是字符串,则逐个比较字符串中对应字符的编码。
❑ 如果有任一操作数是数值,则将另一个操作数转换为数值,执行数值比较。
❑ 如果有任一操作数是对象,则调用其valueOf()方法,取得结果后再根据前面的规则执行比较。如果没有valueOf()操作符,则调用toString()方法,取得结果后再根据前面的规则执行比较。
❑ 如果有任一操作数是布尔值,则将其转换为数值再执行比较。
注:
大写字母的编码都小于小写字母的编码
任何关系操作符在涉及比较NaN时都返回false。在比较NaN时,无论是小于还是大于等于,比较的结果都会返回false
3.5.8 相等操作符
ECMAScript提供了两组操作符。
第一组是等于和不等于,它们在比较之前执行转换。
第二组是全等和不全等,它们在比较之前不执行转换。
1.等于和不等于
ECMAScript中的等于操作符用两个等于号(==)表示,如果操作数相等,则会返回true。
不等于操作符用叹号和等于号(! =)表示,如果两个操作数不相等,则会返回true。
这两个操作符都会先进行类型转换(通常称为强制类型转换)再确定操作数是否相等。
在转换操作数的类型时,相等和不相等操作符遵循如下规则:
❑ 如果任一操作数是布尔值,则将其转换为数值再比较是否相等。false转换为0, true转换为1。
❑ 如果一个操作数是字符串,另一个操作数是数值,则尝试将字符串转换为数值,再比较是否相等。
❑ 如果一个操作数是对象,另一个操作数不是,则调用对象的valueOf()方法取得其原始值,再根据前面的规则进行比较。
在进行比较时,这两个操作符会遵循如下规则:
❑ null和undefined相等。
❑ null和undefined不能转换为其他类型的值再进行比较。
❑ 如果有任一操作数是NaN,则相等操作符返回false,不相等操作符返回true。记住:即使两个操作数都是NaN,相等操作符也返回false,因为按照规则,NaN不等于NaN。
❑ 如果两个操作数都是对象,则比较它们是不是同一个对象。如果两个操作数都指向同一个对象,则相等操作符返回true。否则,两者不相等。
2.全等和不全等
全等和不全等操作符与相等和不相等操作符类似,只不过它们在比较相等时不转换操作数。
全等操作符由3个等于号(===)表示,只有两个操作数在不转换的前提下相等才返回true
不全等操作符用一个叹号和两个等于号(! ==)表示,只有两个操作数在不转换的前提下不相等才返回true
注:
虽然null == undefined是true(因为这两个值类似),但null === undefined是false,因为它们不是相同的数据类型
由于相等和不相等操作符存在类型转换问题,因此推荐使用全等和不全等操作符。这样有助于在代码中保持数据类型的完整性。
3.5.9 条件操作符
条件操作符是ECMAScript中用途最为广泛的操作符之一,三元运算符
3.5.10 赋值操作符
简单赋值用等于号(=)表示,将右手边的值赋给左手边的变量
每个数学操作符以及其他一些操作符都有对应的复合赋值操作符:
❑ 乘后赋值(*=)
❑ 除后赋值(/=)
❑ 取模后赋值(%=)
❑ 加后赋值(+=)
❑ 减后赋值(-=)
❑ 左移后赋值(<<=)
❑ 右移后赋值(>>=)
❑ 无符号右移后赋值(>>>=)
这些操作符仅仅是简写语法,使用它们不会提升性能。
3.5.11 逗号操作符
逗号操作符可以用来在一条语句中执行多个操作
- 在一条语句中同时声明多个变量是逗号操作符最常用的场景。
let num1 = 1, num2 = 2, num3 = 3
- 不过,也可以使用逗号操作符来辅助赋值。
在赋值时使用逗号操作符分隔值,最终会返回表达式中最后一个值:
let num = (1,2,3,4,5) // num值为5
在这个例子中,num将被赋值为5,因为5是表达式中最后一项。
逗号操作符的这种使用场景并不多见,但这种行为的确存在。
今日思考:
解答:
1. the sum of 5 and 10 is 51
原因:变量message中保存的是一个字符串,是执行两次加法操作之后的结果。每次加法运算都是独立完成的。第一次加法的操作数是一个字符串和一个数值(5),结果还是一个字符串。第二次加法仍然是用一个字符串去加一个数值(10),同样也会得到一个字符串。如果想真正执行数学计算,然后把结果追加到字符串末尾,只要使用一对括号即可。用括号把两个数值变量括了起来,意思是让解释器先执行两个数值的加法,然后再把结果追加给字符串。
2.true, 比较字符串”23”和”3”时返回true,因为两个操作数都是字符串,所以会逐个比较它们的字符编码(字符”2”的编码是50,而字符”3”的编码是51)。不过,如果有一个操作数是数值,那么比较的结果就对了。
3.都是NaN,因为任何关系操作符在涉及比较NaN时都返回false。在比较NaN时,无论是小于还是大于等于,比较的结果都会返回false
参考:https://www.yuque.com/docs/share/e521db4a-2c7d-4d0b-ba86-33eeb13af9ae?#
第三章 3.6 3.7
3.6 语句
语句通常使用一或多个关键字完成既定的任务。语句可以简单,也可以复杂。简单的如告诉函数退出,复杂的如列出一堆要重复执行的指令。
3.6.1 if语句
3.6.2 do-while语句
do-while语句是一种后测试循环语句,即循环体中的代码执行后才会对退出条件进行求值。换句话说,循环体内的代码至少执行一次
3.6.3 while语句
while语句是一种先测试循环语句,即先检测退出条件,再执行循环体内的代码。因此,while循环体内的代码有可能不会执行。
3.6.4 for语句
for语句也是先测试语句,只不过增加了进入循环之前的初始化代码,以及循环执行后要执行的表达式。语法如下:
例如:
以上代码在循环开始前定义了变量i的初始值为0。然后求值条件表达式,如果求值结果为true(i
3.6.5 for-in语句
for-in语句是一种严格的迭代语句,用于枚举对象中的非符号键属性,语法如下:
例如:
这个例子使用for-in循环显示了BOM对象window的所有属性。每次执行循环,都会给变量propName赋予一个window对象的属性作为值,直到window的所有属性都被枚举一遍
ECMAScript中对象的属性是无序的,因此for-in语句不能保证返回对象属性的顺序。换句话说,所有可枚举的属性都会返回一次,但返回的顺序可能会因浏览器而异。
如果for-in循环要迭代的变量是null或undefined,则不执行循环体。
3.6.6 for-of语句
for-of语句是一种严格的迭代语句,用于遍历可迭代对象的元素,语法如下:
例子:
我们使用for-of语句显示了一个包含4个元素的数组中的所有元素。循环会一直持续到将所有元素都迭代完
for-of循环会按照可迭代对象的next()方法产生值的顺序迭代元素。
关于可迭代对象,本书将在第7章详细介绍。
如果尝试迭代的变量不支持迭代,则for-of语句会抛出错误。
注:ES2018对for-of语句进行了扩展,增加了for-await-of循环,以支持生成期约(promise)的异步可迭代对象。相关内容将在附录A介绍。
3.6.7 标签语句
标签语句用于给语句加标签,语法如下:
例子:
在这个例子中,start是一个标签,可以在后面通过break或continue语句引用。标签语句的典型应用场景是嵌套循环。
3.6.8 break和continue语句
break和continue语句为执行循环代码提供了更严格的控制手段。
其中,break语句用于立即退出循环,强制执行循环后的下一条语句。
而continue语句也用于立即退出循环,但会再次从循环顶部开始执行。
组合使用标签语句和break、continue能实现复杂的逻辑,但也容易出错。注意标签要使用描述性强的文本,而嵌套也不要太深。
3.6.9 with语句
with语句的用途是将代码作用域设置为特定的对象,其语法是:
使用with语句的主要场景是针对一个对象反复操作,这时候将代码作用域设置为该对象能提供便利,比如:
上面代码中的每一行都用到了location对象。如果使用with语句,就可以少写一些代码:
with语句用于连接location对象。这意味着在这个语句内部,每个变量首先会被认为是一个局部变量。如果没有找到该局部变量,则会搜索location对象,看它是否有一个同名的属性。如果有,则该变量会被求值为location对象的属性。
严格模式不允许使用with语句,否则会抛出错误。
警告:由于with语句影响性能且难于调试其中的代码,通常不推荐在产品代码中使用with语句。
3.6.10 switch语句
switch语句是与if语句紧密相关的一种流控制语句。语法:
每个case(条件/分支)相当于:“如果表达式等于后面的值,则执行下面的语句。
”break关键字会导致代码执行跳出switch语句。如果没有break,则代码会继续匹配下一个条件。
default关键字用于在任何条件都没有满足时指定默认执行的语句(相当于else语句)
switch语句可以用于所有数据类型(在很多语言中,它只能用于数值),因此可以使用字符串甚至对象。其次,条件的值不需要是常量,也可以是变量或表达式
注:switch语句在比较每个条件的值时会使用全等操作符,因此不会强制转换数据类型(比如,字符串”10”不等于数值10)
3.7 函数
函数对任何语言来说都是核心组件,因为它们可以封装语句,然后在任何地方、任何时间执行。ECMAScript中的函数使用function关键字声明,后跟一组参数,然后是函数体。
语法:
严格模式对函数也有一些限制:
❑ 函数不能以eval或arguments作为名称;
❑ 函数的参数不能叫eval或arguments;
❑ 两个命名参数不能拥有同一个名称。
如果违反上述规则,则会导致语法错误,代码也不会执行。
