这是我参与11月更文挑战的第26天,活动详情查看:2021最后一次更文挑战
重学前端-JavaScript(2)
1, 前言
在学习JavaScript之前,先想想下面的几个问题
1,为什么有的编程规范要求用 void 0 代替 undefined?
2,字符串有最大长度吗?
3,0.1 + 0.2 不是等于 0.3 么?为什么 JavaScript 里不是这样的?
4,ES6 新加入的 Symbol 是个什么东西?
5,为什么给对象添加的方法能用在基本类型上?
如果你答起来还有些犹豫的地方,这就说明你对这部分知识点,还是有些遗漏之处的。我在前面提到过,我们的 JavaScript 模块会从运行时、文法和执行过程三个角度去剖析 JS 的知识体系,本篇我们就从运行时的角度去看 JavaScript 的类型系统
2,类型
JavaScript 语言规定了 7 种语言类型。语言类型广泛用于变量、函数参数、表达式、函数返回值等场合
Undefined;
Null;
Boolean;
String;
Number;
Symbol;
Object。
(1) 为什么有的编程规范要求用 void 0 代替 undefined?
Undefined 类型表示未定义,它的类型只有一个值,就是 undefined。
任何变量在赋值前是 Undefined 类型、值为 undefined,
一般我们可以用全局变量 undefined(就是名为 undefined 的这个变量)来表达这个值,
或者 void 运算来把任意一个表达式变成 undefined 值。
但是呢,因为 JavaScript 的代码 undefined 是一个变量,而并非是一个关键字,这是 JavaScript 语言公认的设
计失误之一,所以,我们为了避免无意中被篡改,我建议使用 void 0 来获取 undefined 值。
Undefined 跟 Null 有一定的表意差别,Null 表示的是:“定义了但是为空”。所以,在实际编程时,我们一般不会
把变量赋值为 undefined,这样可以保证所有值为 undefined 的变量,都是从未赋值的自然状态。
Null 类型也只有一个值,就是 null,它的语义表示空值,与 undefined 不同,null 是 JavaScript 关键字,所
以在任何代码中,你都可以放心用 null 关键字来获取 null 值。
(2) 字符串有最大长度吗?
String 用于表示文本数据。String 有最大长度是 2^53 - 1
(3) 0.1 + 0.2 不是等于 0.3 么?为什么 JavaScript 里不是这样的?
浮点型运算由于有精度,就导致0.1+0.2!=0.3,可以用Math.abs(0.1+0.2-0.3)<=Number.EPSILON
(4) ES6 新加入的 Symbol 是个什么东西?
Symbol是ES6引入的新类型,它是一切非字符串的对象key的集合,不支持new方法,特点是返回的symbol值都是唯一的
(5)为什么给对象添加的方法能用在基本类型上?
每一个基本类型都在对象中有相应的类(除了symbol,但是可以装箱转换),因为运算符提供了装箱操作,它会根据基
本类型构造一个临时对象,所以在基础类型上可以调用对应对象的方法
3,类型转换
因为 JS 是弱类型语言,所以类型转换发生非常频繁,大部分我们熟悉的运算都会先进行类型转换。大部分类型转换符合人类的直觉,但是如果我们不去理解类型转换的严格定义,很容易造成一些代码中的判断失误。其中最为臭名昭著的是 JavaScript 中的“ == ”运算,因为试图实现跨类型的比较,它的规则复杂到几乎没人可以记住。这里我们当然也不打算讲解 == 的规则,它属于设计失误,并非语言中有价值的部分,很多实践中推荐禁止使用“ ==”,而要求程序员进行显式地类型转换后,用 === 比较
除了这七种语言类型,还有一些语言的实现者更关心的规范类型。
- List 和 Record: 用于描述函数传参过程。
- Set:主要用于解释字符集等。
- Completion Record:用于描述异常、跳出等语句执行过程。
- Reference:用于描述对象属性访问、delete 等。
- Property Descriptor:用于描述对象的属性。
- Lexical Environment 和 Environment Record:用于描述变量和作用域。
- Data Block:用于描述二进制数据。
4,面向对象
- 对象具有唯一标识性:即使完全相同的两个对象,也并非同一个对象。
- 对象有状态:对象具有状态,同一对象可能处于不同状态之下。
- 对象具有行为:即对象的状态,可能因为它的行为产生变迁。
(1)唯一标志性
我们先来看第一个特征,对象具有唯一标识性。一般而言,各种语言的对象唯一标识性都是用内存地址来体现的, 对象具有唯一标识的内存地址,所以具有唯一的标识。所以,JavaScript 程序员都知道,任何不同的 JavaScript 对象其实是互不相等的
在 JavaScript 中,对象的状态和行为其实都被抽象为了属性。
在实现了对象基本特征的基础上, 我认为,JavaScript 中对象独有的特色是:对象具有高度的动态性,这是因为 JavaScript 赋予了使用者在运行时为对象添改状态和行为的能力。
(2)两类属性
先来说第一类属性,数据属性。它比较接近于其它语言的属性概念。数据属性具有四个特征。
value:就是属性的值。
writable:决定属性能否被赋值。
enumerable:决定 for in 能否枚举该属性。
configurable:决定该属性能否被删除或者改变特征值。
在大多数情况下,我们只关心数据属性的值即可。
第二类属性是访问器(getter/setter)属性,它也有四个特征
getter:函数或 undefined,在取属性值时被调用。
setter:函数或 undefined,在设置属性值时被调用。
enumerable:决定 for in 能否枚举该属性。
configurable:决定该属性能否被删除或者改变特征值。
访问器属性使得属性在读和写时执行代码,它允许使用者在写和读属性时,得到完全不同的值,它可以视为一种函数的语法糖。我们通常用于定义属性的代码会产生数据属性,其中的 writable、enumerable、configurable 都默认为 true。
我们可以使用内置函数 getOwnPropertyDescriptor 来查看,如以下代码所示:
var o = { a: 1 };
o.b = 2;
//a和b皆为数据属性
Object.getOwnPropertyDescriptor(o,"a")
// {value: 1, writable: true, enumerable: true, configurable: true}
Object.getOwnPropertyDescriptor(o,"b")
// {value: 2, writable: true, enumerable: true, configurable: true}
如果我们要想改变属性的特征,或者定义访问器属性,我们可以使用 Object.defineProperty,示例如下:
var o = { a: 1 };
Object.defineProperty(o, "b", {value: 2, writable: false, enumerable: false, configurable: true});
//a和b都是数据属性,但特征值变化了
Object.getOwnPropertyDescriptor(o,"a");
// {value: 1, writable: true, enumerable: true, configurable: true}
Object.getOwnPropertyDescriptor(o,"b");
// {value: 2, writable: false, enumerable: false, configurable: true}
o.b = 3;
console.log(o.b); // 2
这里我们使用了 Object.defineProperty 来定义属性,这样定义属性可以改变属性的 writable 和 enumerable。
我们同样用 Object.getOwnPropertyDescriptor 来查看,发现确实改变了 writable 和 enumerable 特征。因为 writable 特征为 false,所以我们重新对 b 赋值,b 的值不会发生变化。
在创建对象时,也可以使用 get 和 set 关键字来创建访问器属性,代码如下所示:
var o = { get a() { return 1 } };
console.log(o.a); // 1
5, 总结
要想理解 JavaScript 对象,必须清空我们脑子里“基于类的面向对象”相关的知识,回到人类对对象的朴素认知和面向对象的语言无关基础理论,我们就能够理解 JavaScript 面向对象设计的思路。