类型、值和变量
1、直接调用函数和 new 一个函数的区别
直接调用:
-
函数中的 this 关键字指向 Windows(在浏览器中);
-
函数的返回值取决于函数本身的实现,若无显式地 return,则会返回 undefined。
使用 new 关键字:
-
将创建一个新的空对象;
-
函数中的 this 关键字指向这个新创建的空对象;
-
函数的返回值为这个新对象的引用,除非显式地返回另一个对象;
-
此时的函数被称为构造函数(
constructor)。
2、算数运算
-
溢出(overflow):大于
Number.MAX_VALUE,或小于-Number.MAX_VALUE时返回±Infinity; -
下溢(underflow):小于
Number.MIN_VALUE,或小于-Number.MIN_VALUE时返回±0; -
0 除任何数结果为 0;正数除 0 结果为
Infinity;负数除 0 结果为-Infinity;0 除 0 没有意义,结果为NaN(Not a Number); -
NaN与任何值都不相等,包括它自身,因此使用x != x来判断一个变量是否为NaN;也可以直接用函数isNaN(); -
±0是严格相等的,这意味着它们除了在运算时改变±号外,几乎一模一样。 -
JavaScript 中的所有数都是用浮点数值表示的(IEEE-754 浮点数表示法),使用实数时往往只是一个真实值的近似表示,这就导致高精度的计算出现误差(例如
.3 - .2 != .2 - .1)。
3、字符串
- 虽然字符串可以像数组一样按索引访问,但 JavaScript 中的字符串是不允许修改的。
replace()和toUpperCase()之类的方法都是返回新的字符串,并不会修改原有字符串。
4、null 与 undefined
-
typeof(null) === "object",typeof(undefined) === "undefined"; -
null == undefined,null !== undefined。
5、全局对象
- JavaScript 解释器(浏览器或 Node.js 中的 V8 引擎之类的)在运行 JavaScript 时,会创建一个全局对象,并赋值一组定义的初始属性。
6、包装对象
JavaScript 只有引用类型的变量上存在属性和方法,那么为什么像 number, string, boolean 这样的原始类型也有属性和方法可以调用呢?
-
以
string为例,调用一个字符串变量上的属性和方法时,JavaScript 会通过new String(s)的方式将其转换为一个临时对象。属性和方法其实是在 String 类实例化出来的这个临时对象上的; -
当属性或方法的引用结束后,这个临时对象就会被回收;
-
包装对象上的属性都是只读的,并且不能定义新的属性;
-
原始类型的变量与其对应的包装对象,相等,但不全等。
7、类型转换
- 虽然
typeof(null) === "object",但将null转换为Object会抛出异常;
- 注意:上述的转换并不意味着两个值相等! 例如
if语句会将undefined转换为false,但在使用==运算符作比较时并不会将undefined转换为false。== 运算符从不试图将其操作数转换为布尔值!
8、对象到原始值的转换
- 转换为布尔值:所有的对象(包括数组和函数)都将转换为
true;同样包括了包装对象,例如new Boolean(false)转换为布尔值也是true。
注意:接下来的结论仅针对于本地对象,不包括宿主对象(JavaScript 运行环境中提供的对象)。
- 转换为字符串:所有的对象都继承了一个
toString()方法,它的作用是返回一个反应这个对象的字符串。很多类定义了不同版本的toString()方法,如数组、函数、正则、日期类......
| 类型 | 规则 | 例子 |
|---|---|---|
| 数组类 | 将每一个数组元素转换为字符串,并在元素之间添加逗号后合并成结果字符串。 | [1, 2, 3] => "1,2,3" |
| 函数类 | 通常是将函数转换为源代码字符串。 | function () { return 0; } => "function () { return 0; }" |
| 正则类 | 直接转换为字符串。 | /\d+/g => "/\d+/g" |
| 日期类 | 经过 JavaScript 解析,返回一个可读的日期和时间字符串。 | new Date(2024, 5, 7) => 'Fri Jun 07 2024 00:00:00 GMT+0800 (中国标准时间)' |
-
转换为数:所有的对象都继承了一个
valueOf()方法,但大多数对象都无法真正地转换为一个原始值,所以它的作用只是简单地返回对象本身。其中,日期类的valueOf()方法会返回对象的一个内部表示:new Date(2024, 5, 7).valueOf() => 1717689600000。 -
对象在转换成字符串或数的底层逻辑:优先调用
toString() / valueOf(),若对象上不存在该方法或没有返回一个原始值,则再调用valueOf() / toString()方法,若仍失败,则抛出一个类型错误异常。举个例子:数组转换成数时,优先调用valueOf()方法,返回数组本身,不满足要求,因此调用toString()方法,再将字符串结果转换为数。
9、声明提前
使用 var 声明的变量在整个函数体中都是存在声明的(仅针对声明,不包括赋值)。
var scope = "global";
function f () {
console.log(scope); // 1
var scope = "local";
console.log(scope); // 2
}
上述代码中,1 将输出 undefined,2 将输出 "local",因为声明提前了,但赋值并不会提前。
上述代码的效果等价于:
var scope = "global";
function f () {
var scope;
console.log(scope);
scope = "local";
console.log(scope);
}
10、作用域、作用域链、词法环境、执行上下文、调用栈
-
JavaScript 是基于词法作用域(也叫 静态作用域)的语言,变量的作用域在代码编写时已经确定了,在代码执行时不会再发生变化。
-
作用域:在代码块中定义变量的区域,决定了变量的可访问性。
var a = 1;
function fnc () {
var b = 2;
for (let i = 0; i < b; i += a) {
console.log(" i ", i);
}
}
fnc();
作用域是对于变量而言的。在上述代码中,变量 a 的作用域为全局作用域,在所有地方都可以访问;变量 b 的作用域为函数作用域,仅在函数 fnc 中可以访问;变量 i 的作用域为块级作用域,仅在 for 循环代码块中可以访问。
-
词法环境:词法环境是对于 JavaScript 代码(全局代码或函数)而言的,在 JavaScript 代码执行之前创建。词法环境的结构分为两部分:1、存储代码内部的函数、变量的声明以及对应的标识符;2、指向外部其它词法环境的引用。
-
作用域链:多个词法环境组成的链式结构便称为作用域链。作用域链是对执行上下文而言的。
仍然以上述代码为例,在 for 循环代码块中引用的变量 a, b,是根据作用域链依次寻找的。首先找到函数 fnc 的词法环境,这里存储了变量 b, i 的声明;然后根据词法环境的引用继续向外部词法环境寻找,找到全局的词法环境,这里存储了变量 a 和函数 fnc 的声明。
-
执行上下文:执行上下文也是对于 JavaScript 代码(全局代码或函数)而言的,在 JavaScript 的执行阶段创建。其中包含了关于代码执行的一些重要信息:当前的词法环境,变量的值,作用域链,
this指向等等。 -
调用栈:调用栈是一个用来管理执行上下文的栈结构,遵循后进先出(LIFO)规则。
仍然以上述代码为例,首先是最外层的全局执行上下文进栈,接下来是函数 fnc 的执行上下文进栈,函数执行完毕后出栈并销毁,最后是全局执行上下文出栈并销毁。
总结:
- 作用域、词法环境是静态的;执行上下文是动态的。
- 作用域链由词法环境构成;执行上下文中包含词法环境,也就包含作用域链。