这篇文章主要是winter提出的一些问题加上一些我的思考,如有不对,希望大家可以指出。
一、为什么有的编程规范要求使用void 0 代替 undefined?
这篇文章主要介绍JavaScript中的属性描述符、ES6中的module、箭头函数。undefined类型的值为undefined,当一个变量声明未赋值时,它的值就是undefined。undefined是全局对象的一个属性,也就是说它是全局作用域的一个##变量##。它##不是一个关键字和保留字##,这是JavaScript语言公认的设计失误之一。undefined在全局作用域中不能被篡改,但在非全局作用域(如函数)中可以被当做标识符(变量名)来重新赋值。而void运算可以把任意一个表达式变成undefiend,所以为避免无意中被篡改,建议使用void 0 来获取undefined值。
void运算符: 对给定的表达式进行求值,然后返回 undefined。用法 void expression 。一般使用void(0)(等同于void 0)。
表达式: 表达式是由运算符构成,并运算产生结果的语法结构。每个表达式都会产生一个值,它可以放在任何需要一个值的地方。
var a = 3 + 2; // 3 + 2是表达式
var b = (function () {return 25;})(); // (function () {return 25;})() 是表达式
语句: 语句可以理解成一个行为,循环语句和if语句就是经典的语句,一个程序是有一系列语句组成的。语句是由";(分号)"分隔的句子或命令。它表明"只有表达式,而没有其他语法元素的语句"。如果在表达式后面加一个";"分隔符,这句被称为"表达式语句"。
一般JavaScript中的语句分为以下几种:
(1)声明语句: 变量声明和函数声明
(2)赋值语句
(3)控制语句: 能够对语句执行顺序发生改变,包括条件语句和循环语句,还有比较特殊的标签语句
(4)表达式语句: 这些语句去掉最后的分号,都也可当表达式用的。常见的有: 对象(new、delete)、函数调用(函数执行,必有返回值)等
参考来自:
MDN--undefined
MDN--void
二、字符串有最大长度吗?
String 有最大长度是 2^53 - 1,这个所谓最大长度不是字符数,而是字符串的 UTF16 编码,我们字符串的操作 charAt、charCodeAt、length 等方法针对的都是 UTF16 编码。所以,字符串的最大长度,实际上是受字符串的编码长度影响的。
三、0.1 + 0.2 为什么不等于 0.3?
解释一:根据双精度浮点数的定义,Number 类型中有效的整数范围是-0x1fffffffffffff 至 0x1fffffffffffff,所以 Number 无法精确表示此范围外的整数。浮点数运算的精度问题会导致等式左右的结果并不是严格相等,而是相差了个微小的值。
解释二:对于计算机而言,两个数字在相加时是以二进制形式进行的,在呈现结果时才转换成为十进制。先按照 IEEE 754 双精度64位浮点数 转成相应的二进制,然后进行对阶运算。当十进制小数的二进制表示的有限数字超过52位时,在JavaScript里是不能精确存储的,这时候就存在舍入误差。所以,精度可能损失在进制之间的转换和对阶运算过程中,只要这两步中产生了精度损失,计算结果就会出现偏差。
进一步提问:
1.如何让 0.1 + 0.2 == 0.3?
在不进行转换的情况下,使用 Math.abs(0.1 + 0.2 - 0.3) <= Number.EPSILON
,检测等式左右两边差的绝对值是否小于最小精度。
Number.EPSILON: ES6 在Number对象上面,新增一个极小的常量Number.EPSILON。根据规格,它表示 1 与大于 1 的最小浮点数之间的差。 (来自 ECMAScript6入门)
2.为什么 0.2 + 0.3 = 0.5 ?
这个可以看参考文章的第一篇,主要是因为在进行计算的时候满足了精度运算的要求。
扩展: JavaScript使用Number类型表示数字(整数和浮点数),遵循 IEEE 754 标准 通过64位来表示一个数字。在内存中,第0位:符号位,0表示正数,1表示负数(s)。第1位到第11位:储存指数部分(e)。第12位到第63位:储存小数部分(即有效数字)f。
注:当数字 大于9007199254740992( Math.pow(2, 53) )进行加法时,精度也会发生丢失。
解释二的参考文章:
0.1 + 0.2不等于0.3?为什么JavaScript有这种“骚”操作?
JS 0.1+0.2的理解
0.1 + 0.2 = 0.30000000000000004怎样理解
四、为什么给对象添加的方法能够用在基本类型上?
为了方便操作基本类型值,ECMAScript提供了几个特殊的引用类型: Boolean、Number、String、Symbol。每当读取一个基本类型值的时候,后台就会创建一个对应的基本包装类型的对象,从而让我们能够调用一些方法来操作这些数据。
基本包装类型的运行过程:
let s1 = 'some text';
let s2 = 's1.substring(2)';
后台自动完成的处理:
1) 创建String类型的一个实例
2) 在实例上调用指定的方法
3) 销毁这个实例
基本包装类型和引用类型的主要区别是对象的生存期。使用 new 操作符创建的引用类型的实例,在执行流离开当前作用域之前都一直保存在内存中。而自动创建的基本包装类型的对象,则只存在于一行代码的执行瞬间,然后立即被销毁。所以不能给运行时为基本类型的值添加属性和方法。
来源: JavaScript高级程序设计(第3版) 基本包装类型
进一步提问: 实现 if(a== 1 && a== 2 && a== 3)为true
let a = {
val: 1,
toString() {
return this.val++;
},
valueOf() {
return this.val++;
}
}
if (a == 1 && a == 2 && a == 3) {
console.log('hello world');
}
装箱转换: 每一种基本类型 Number、String、Boolean、Symbol 在对象中都有对应的类,所谓装箱转换,正是把基本类型转换为对应的对象,它是类型转换中一种相当重要的种类。
拆箱转换: 在 JavaScript 标准中,规定了 ToPrimitive 函数,它是对象类型到基本类型的转换。对象到 String 和 Number 的转换都遵循“先拆箱再转换”的规则。通过拆箱转换,把对象变成基本类型,再从基本类型转换为对应的 String 或者 Number。拆箱转换会尝试调用 valueOf 和 toString 来获得拆箱后的基本类型。如果 valueOf 和 toString 都不存在,或者没有返回基本类型,则会产生类型错误 TypeError。
五、为什么12.toString会报错?
因为在JS中十进制的Number可以带小数,小数点前后部分都可以省略,但不能同时省略。因此一个小数可以表现为 '.01'、'12.'、'12.01',当使用12.toString时,12.会被当做省略了小数点后面部分的数字而看成一个整体。所以想让这个表达式正确执行,需要加入一个空格('12 .toString()')或者再加一个.('12..toString()')。
六、JavaScript到底要不要写分号?
结论是加不加分号都可以,如果不加分号有些地方需要注意一下。
需要注意的地方:
1) 如果下一行的第一个token是 ( , [ , / , + , - 这五个字符之一的话, Javascript 将不会自动在上一行句尾加上分号。所以,当下一行的第一个token是以上字符时,应当在上一行或者行首加上分号
2) continue、return、break、throw后自动插入分号。所以当 return 出一个值,千万不要将那个值与 return 不在同一行,不然返回的结果是 undefined 而不是你期望的值。
3) ++ , -- 后缀表达式作为新行开始,会在行首自动插入分号,
参考链接:
知乎--JavaScript 语句后应该加分号么?
JavaScript ASI 机制详解
七、在script标签写export为什么会抛错?
因为没有在script上设置type="module"属性。在JavaScript中有两种源文件,一种是脚本,一种是ES6的模块。在模块中可以使用import和export,而脚本中不行,会报错。现代浏览器可以支持script标签引入模块或脚本。script标签的默认type属性为type="application/javascript",表示脚本,它可以省略。ES6的模块要想加载,需要设置type="module"属性,不然浏览器就会以为是在脚本中写了export,因此而报错。
这篇文章就是JavaScript系列的最后一篇了,也是重学前端这个系列的最后一篇文章了。以上内容如有不对,希望大家指出,谢谢。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。