基本类型
数据类型
7 种原始类型(基本类型):
- Boolean
- String
- Number
- Null
- Undefined
- Symbol(新定义)
- BigInt(新定义)
引用类型:
- Object
- Array
- Function
- String (很多场景下 string 会被强制转化成 String 类型,但其实也是对象类型)
原始类型与引用类型存储的方式也不同,原始数据类型存储在栈中,而引用数据类型存储在堆中。
使用 typeof 能得到哪些类型?
- boolean
- number
- string
- undefined
- symbol
- object
- function
typeof 对于基本类型,除了 null 都可以显示正确的类型。typeof 对于对象,除了函数都会显示 object
typeof []; // 'object'
typeof {}; // 'object'
typeof console.log; // 'function'
对于 null 来说,虽然它是基本类型,但是会显示 object,这是一个存在很久了的 Bug
typeof null; // 'object'
在 JS 的最初版本中,使用的是 32 位系统,为了性能考虑使用低位存储了变量的类型信息,000 开头代表是对象,然而 null 表示为全零,所以将它错误的判断为 object 。虽然现在的内部类型判断代码已经改变了,但是对于这个 Bug 却是一直流传下来。
undefined, null, not defined 的区别
null 是一个赋值,可以赋值给一个表示该值的变量,它没有价值。undefined 表示已经声明了一个变量,但是还没有定义。null 表示一个对象被定义了,值为“空值”。not defined 表示该值未定义。
console.log(y); // Output: ReferenceError: y is not defined
在验证 null 时,一定要使用 === ,因为 == 无法分别 null 和 undefined
0.1 + 0.2 === 0.3
0.1 + 0.2 === 0.3; // false
0.1 + 0.2; // 0.30000000000000004
原因:JS 中的 number 类型是浮点类型。计算机不能精确表示 0.1, 0.2 这样的浮点数,计算时使用的是带有舍入误差的数,但不是所有的浮点数在计算机内部都存在舍入误差,比如 0.5 就没有舍入误差. 为了解决这个问题可以使用 toFixed()
// 最简单的解决问题
(0.1 + 0.2).toFixed(10); // 0.3
// 0.1 + 0.2
// 0.8 - 0.2
// 0.1 + 0.7
为什么 console.log(0.1) 却是正确的呢?因为在输入内容的时候,二进制被转换为了十进制,十进制又被转换为了字符串,在这个转换的过程中发生了取近似值的过程,所以打印出来的其实是一个近似值,你也可以通过以下代码来验证
console.log(0.100000000000000002); // 0.1
解决办法
自定义处理函数,放大指定的位数,最后再缩小。
// f代表需要计算的表达式,digit代表小数位数
Math.formatFloat = function (f, digit) {
// Math.pow(指数,幂指数)
var m = Math.pow(10, digit);
// Math.round() 四舍五入
return Math.round(f * m, 10) / m;
};
console.log(Math.formatFloat(0.3 * 8, 1)); // 2.4
console.log(Math.formatFloat(0.35 * 8, 2)); // 2.8
闭包
闭包就是函数中的函数,里面的函数可以访问外面函数的变量。
使用闭包主要是为了设计私有的方法和变量。闭包的优点是可以避免全局变量的污染,缺点是闭包会常驻内存,会增大内存使用量,使用不当很容易造成内存泄露。闭包参数和变量不会被垃圾回收机制回收,使用完了后,要立即释放资源,将引用变量指向 null。
闭包优缺点
闭包封住了变量作用域,有效地防止了全局污染;但同时,它也存在内存泄漏的风险:
- 在浏览器端可以通过强制刷新解决,对用户体验影响不大
- 在服务端,由于 node 的内存限制和累积效应,可能会造成进程退出甚至服务器沓机
解决方法是显式对外暴露一个接口,专门用以清理变量。
闭包的使用场景
- 封装变量,收敛权限,模拟私有化。
- cached 对象储存。
- 柯里化参数处理
循环中使用闭包解决 var 定义函数的问题
for (var i = 1; i < 5; i++) {
setTimeout(function timer() {
console.log(i);
}, i * 1000);
}
解决方法有 3 种
第一种,使用立即执行函数方式
for (var i = 1; i < 5; i++) {
(function (j) {
setTimeout(function timer() {
console.log(j);
}, j * 1000);
})(i);
}
第二种,使用 ES6 的let, 因为对于 let 来说,他会创建一个块级作用域
for (let i = 1; i < 5; i++) {
setTimeout(function timer() {
console.log(i);
}, i * 1000);
}
相当于
{
// 形成块级作用域
let i = 0;
{
let ii = i;
setTimeout(function timer() {
console.log(ii);
}, i * 1000);
}
i++;
{
let ii = i;
}
i++;
{
let ii = i;
}
// ...
}
第三种,使用 setTimeout 的第三个参数
for (var i = 1; i < 5; i++) {
setTimeout(
function timer(j) {
console.log(j);
},
i * 1000,
i
);
}
类型转换
在 JS 中类型转换只有三种情况,分别是:
- 转换为布尔值: 除了 undefined, null, false, NaN, '', 0, -0,其他所有值都转为 true,包括所有对象。
- 转换为数字
- 转换为字符串
js 中的 boolean number 互转
~~false === 0;
~~true === 1;
~~undefined === 0;
~~!undefined === 1;
~~null === 0;
~~!null === 1;
~~"" === 0;
~~!"" === 1;
对象强制类型转换
对象在转换类型的时候,会调用内置的 ToPrimitive 函数,对于该函数来说,算法逻辑一般来说如下:
- 如果已经是原始类型了,那就不需要转换了
- 调用 x.valueOf(),如果转换为基础类型,就返回转换的值
- 调用 x.toString(),如果转换为基础类型,就返回转换的值
- 如果都没有返回原始类型,就会报错
当然你也可以重写 Symbol.toPrimitive ,该方法在转原始类型时调用优先级最高。
let a = {
valueOf() {
return 0;
},
toString() {
return "1";
},
[Symbol.toPrimitive]() {
return 2;
},
};
1 + a; // => 3
四则运算符
加法运算符不同于其他几个运算符,它有以下几个特点:
- 运算中其中一方为字符串,那么就会把另一方也转换为字符串
- 如果一方不是字符串或者数字,那么会将它转换为数字或者字符串
1 + "1"; // '11'
true + true; // 2
// 所以将数组通过 toString 转为字符串 1,2,3,得到结果 41,2,3
4 + [1, 2, 3]; // "41,2,3"
另外对于加法还需要注意这个表达式 'a' + + 'b'
"a" + +"b"; // -> "aNaN"
因为 + 'b' 等于 NaN,所以结果为 "aNaN",你可能也会在一些代码中看到过 + '1' 的形式来快速获取 number 类型。
// 对于除了加法的运算符来说,只要其中一方是数字,那么另一方就会被转为数字
4 * "3"; // 12
4 * []; // 0
4 * [1, 2]; // NaN
比较运算符
- 如果是对象,就通过 toPrimitive 转换对象
- 如果是字符串,就通过 unicode 字符索引来比较
let a = {
valueOf() {
return 0;
},
toString() {
return "1";
},
};
a > -1; // true
在以上代码中,因为 a 是对象,所以会通过 valueOf 转换为原始类型再比较值。
对象到字符串的转换步骤
- 如果对象有 toString()方法,javascript 调用它。如果返回一个原始值(primitive value 如:string number boolean),将这个值转换为字符串作为结果
- 如果对象没有 toString()方法或者返回值不是原始值,javascript 寻找对象的 valueOf()方法,如果存在就调用它,返回结果是原始值则转为字符串作为结果
- 否则,javascript 不能从 toString()或者 valueOf()获得一个原始值,此时 throws a TypeError
对象到数字的转换步骤
- 如果对象有 valueOf()方法并且返回元素值,将返回值转换为数字作为结果
- 否则,如果对象有 toString()并且返回原始值,将返回结果转换为数字作为结果
- 否则,throws a TypeError
<,>,<=,>=的比较规则
所有比较运算符都支持任意类型,但是比较只支持数字和字符串,所以需要执行必要的转换然后进行比较,转换规则如下:
- 如果操作数是对象,转换为原始值:如果 valueOf 方法返回原始值,则使用这个值,否则使用 toString 方法的结果,如果转换失败则报错
- 经过必要的对象到原始值的转换后,如果两个操作数都是字符串,按照字母顺序进行比较(他们的 16 位 unicode 值的大小)
- 否则,如果有一个操作数不是字符串,将两个操作数转换为数字进行比较
+运算符工作流程
- 如果有操作数是对象,转换为原始值
- 此时如果有一个操作数是字符串,其他的操作数都转换为字符串并执行连接
- 否则:所有操作数都转换为数字并执行加法
"100" + 100; // "100100"
100 + "100"; // "100100"
100 + true; // 101
100 + false; // 100
100 + undefined; //NaN
100 + null; // 100
// + 会自动转换后面跟随的值类型
原始类型转化
强制类型转化的顺序是怎么样的?
对一个“对象”进行数学运算操作时候,会涉及到对象 => 基础数据类型的转化问题。当一个对象执行例如加法操作的时候,如果它是原始类型,那么就不需要转换。否则,将遵循以下规则:
- 调用实例的
valueOf()方法,如果有返回的是基础类型,停止下面的过程;否则继续 - 调用实例的
toString()方法,如果有返回的是基础类型,停止下面的过程;否则继续 - 都没返回原始类型,就会报错
请看下面的测试代码:
let a = {
toString: function () {
return "a";
},
};
let b = {
valueOf: function () {
return 100;
},
toString: function () {
return "b";
},
};
let c = Object.create(null); // 创建一个空对象
console.log(a + "123"); // output: a123
console.log(b + 1); // output: 101
console.log(c + "123"); // 报错
除了valueOf和toString,es6 还提供了Symbol.toPrimitive供对象向原始类型转化,并且它的优先级最高!!稍微改造下上面的代码:
let b = {
valueOf: function () {
return 100;
},
toString: function () {
return "b";
},
[Symbol.toPrimitive]: function () {
return 10000;
},
};
console.log(b + 1); // output: 10001
最后,其实关于instanceof判断是否是某个对象的实例,es6 也提供了Symbol.hasInstance接口,代码如下:
class Even {
static [Symbol.hasInstance](num) {
return Number(num) % 2 === 0;
}
}
const Odd = {
[Symbol.hasInstance](num) {
return Number(num) % 2 !== 0;
},
};
console.log(1 instanceof Even); // output: false
console.log(1 instanceof Odd); // output: true
比较操作符 ===、== 的区别?
- == 相等运算符,比较时会自动进行数据类型转换
- === 严格相等运算符,比较时不进行隐式类型转换
+0 === -0; //true
NaN === NaN; // false
Object.is(+0, -0); // false
Object.is(NaN, NaN); // true
== 运算符判断相等的流程是怎样的
对于 == 来说,如果对比双方的类型不一样的话,就会进行类型转换.
null 只和undefined 相等,有 number 都转 number,有 boolean 也转 number,有 string 都转 string,对象互相不等,NaN 互相不等就可以了
- 如果两个值类型相同,按照
===比较方法进行比较 - 如果类型不同,使用如下规则进行比较
- 如果其中一个值是 null,另一个是 undefined,它们相等
- 如果一个值是数字另一个是字符串,将字符串转换为数字进行比较
- 如果有布尔类型,将true 转换为 1,false 转换为 0,然后用
==规则继续比较 - 如果一个值是对象,另一个是数字或字符串,将对象转换为原始值然后用
==规则继续比较 - 其他所有情况都认为不相等
变量声明
函数名与变量名同名
var foo = 1;
function bar() {
foo = 10;
return;
// function 的定义会提前到当前作用域之前
function foo() {}
}
bar();
alert(foo);
// 1
等同于:
var foo = 1;
function bar() {
function foo() {}
foo = 10;
return;
}
bar();
alert(foo);
// 1
所以,在 foo=10 的时候,foo 是有定义的,属于局部变量,影响不到外层的 foo。
function bar() {
return foo;
foo = 10;
function foo() {}
var foo = 11;
}
alert(typeof bar());
// function
与上题类似,等同于:
function bar() {
function foo() {}
return foo;
// 在 return 之后声明和赋值的 foo 都无效,所以返回了 function。
foo = 10;
var foo = 11;
}
alert(typeof bar());
// function
function SINA() {
return 1;
}
var SINA;
console.log(typeof SINA); // function
变量提升
js 代码在运行前都会进行 AST 解析,函数声明默认会提到当前作用域最前面,变量声明也会进行提升。使用var关键字声明或初始化的变量,会将声明语句“提升”到当前作用域的顶部。但赋值不会得到提升。
// 用 var 声明得到提升
console.log(foo); // undefined
var foo = 1;
console.log(foo); // 1
// 用 let/const 声明不会提升
console.log(bar); // ReferenceError: bar is not defined
let bar = 2;
console.log(bar); // 2
函数声明会使函数体提升,但函数表达式(以声明变量的形式书写)只有变量声明会被提升。
// 函数声明
console.log(foo); // [Function: foo]
foo(); // 'FOOOOO'
function foo() {
console.log("FOOOOO");
}
console.log(foo); // [Function: foo]
// 函数表达式
console.log(bar); // undefined
bar(); // Uncaught TypeError: bar is not a function
var bar = function () {
console.log("BARRRR");
};
console.log(bar); // [Function: bar]
var name = "B";
function name() {}
function log() {
console.log(name);
// let name = 'A';
}
log();
// B
变量声明优先级
- function 声明的优先级高于 var 声明。也就意味着当两个同名变量同时被 function 和 var 声明时,function 声明会覆盖 var 声明
- var 声明并赋值或者单纯赋值优先级高于 function 声明优先于函数声明
总结:
- 在 JavaScript 中,函数声明与变量声明会被 JavaScript 引擎隐式地提升到当前作用域的顶部
- 声明语句中的赋值部分并不会被提升,只有名称被提升
- 函数声明的优先级高于变量,如果变量名跟函数名相同且未赋值,则函数声明会覆盖变量声明
- 如果函数有多个同名参数,那么最后一个参数(即使没有定义)会覆盖前面的同名参数
if (!("sina" in window)) {
var sina = 1;
}
console.log("sina:", sina); // undefined
由于 JavaScript 在编译阶段会对声明进行提升,所以上述代码会做如下处理:
var sina;
if (!("sina" in window)) {
sina = 1;
}
console.log("sina:", sina);
声明被提升后,
window.sina的值就是 undefined,但是!("sina" in window)这段代码的运行结果是true,所以sina = 1;就不会被执行,所以 本题目的输出结果是undefined。
var a = 10;
(function () {
console.log(a);
a = 5;
console.log(window.a);
var a = 20;
console.log(a);
})();
分别为 undefined 10 20,原因是作用域问题,在内部声名 var a = 20;相当于先声明 var a;然后再执行赋值操作,这是在IIFE内形成的独立作用域,如果把 var a=20 注释掉,那么 a 只有在外部有声明,显示的就是外部的A变量的值了。结果A会是 10 5 5
var b = 10;
(function b() {
b = 20;
console.log(b);
})();
var b = 10;
(function b() {
b = 20;
console.log(b);
})();
针对这题,在知乎上看到别人的回答说:
- 函数表达式与函数声明不同,函数名只在该函数内部有效,并且此绑定是常量绑定。
- 对于一个常量进行赋值,在 strict 模式下会报错,非 strict 模式下静默失败。
- IIFE 中的函数是函数表达式,而不是函数声明。
实际上,有点类似于以下代码,但不完全相同,因为使用 const 不管在什么模式下,都会 TypeError 类型的错误
const foo = (function () {
foo = 10;
console.log(foo);
})(foo)(); // Uncaught TypeError: Assignment to constant variable.
我的理解是,b 函数是一个相当于用 const 定义的常量,内部无法进行重新赋值,如果在严格模式下,会报错"Uncaught TypeError: Assignment to constant variable." 例如下面的:
var b = 10;
(function b() {
"use strict";
b = 20;
console.log(b);
})(); // "Uncaught TypeError: Assignment to constant variable."
题目
var arr = [];
arr[0] = "a";
arr[1] = "b";
arr.foo = "c";
alert(arr.length);
// 2,数组的原型是 Object,所以可以像其他类型一样附加属性,不影响其固有性质。
function foo(a) {
arguments[0] = 2;
alert(Object.prototype.toString.call(arguments));
}
foo(1);
// 2,实参可以直接从 arguments 数组中修改。
function sayHi() {
console.log(name);
console.log(age);
var name = "Lydia";
let age = 21;
}
sayHi();
// undefined 和 ReferenceError
let name = "ConardLi";
{
console.log(name); // Uncaught ReferenceError: name is not defined
// Uncaught ReferenceError: Cannot access 'name' before initialization
let name = "code秘密花园";
}
var employeeId = "abc123";
function foo() {
employeeId = "123bcd";
return;
function employeeId() {}
}
foo();
console.log(employeeId);
// abc123
var employeeId = "abc123";
function foo() {
employeeId();
return;
function employeeId() {
console.log(typeof employeeId);
}
}
foo();
// function
function foo() {
employeeId();
// 函数声明会提升,但是函数执行的时候,变量需要已经声明,否则就 undefined 了。
var product = "Car";
return;
function employeeId() {
console.log(product);
}
}
foo();
// undefined
(function () {
var objA = Object.create({
foo: "foo",
});
var objB = objA;
objB.foo = "bar";
// delete 只能删除当前对象上的属性,没办法删除原型链上的
delete objA.foo;
// 从原型链上获取
console.log(objA.foo);
console.log(objB.foo);
})();
// foo foo
(function () {
var objA = {
foo: "foo",
};
var objB = objA;
objB.foo = "bar";
delete objA.foo;
console.log(objA.foo);
console.log(objB.foo);
})();
// undefined undefined
(function () {
var array = new Array("100");
console.log(array);
console.log(array.length);
})();
// ["100"] 1
(function () {
var array1 = [];
var array2 = new Array(100);
var array3 = new Array(["1", 2, "3", 4, 5.6]);
console.log(array1);
console.log(array2);
console.log(array3);
console.log(array3.length);
})();
// [] [] [Array[5]] 1
(function () {
var array = new Array("a", "b", "c", "d", "e");
array[10] = "f";
delete array[10];
console.log(array.length);
})();
// 11
function funcA() {
console.log("funcA ", this);
(function innerFuncA1() {
console.log("innerFunc1", this);
(function innerFunA11() {
console.log("innerFunA11", this);
})();
})();
}
console.log(funcA());
// funcA Window {...}
// innerFunc1 Window {...}
// innerFunA11 Window {...}
var obj = {
message: "Hello",
innerMessage: !(function () {
console.log(this.message);
})(),
};
console.log(obj.innerMessage);
// undefined
// true
var obj = {
message: "Hello",
innerMessage: function () {
return this.message;
},
};
console.log(obj.innerMessage());
// Hello
var obj = {
message: "Hello",
innerMessage: function () {
// 直接是 window 调用的该函数
(function () {
console.log(this.message);
})();
},
};
console.log(obj.innerMessage());
// undefined
var obj = {
message: "Hello",
innerMessage: function () {
var self = this;
(function () {
console.log(self.message);
})();
},
};
console.log(obj.innerMessage());
// Hello
function myFunc(param1, param2) {
console.log(myFunc.length);
}
console.log(myFunc());
console.log(myFunc("a", "b"));
console.log(myFunc("a", "b", "c", "d"));
// 2 2 2
function myFunc() {
console.log(arguments.length);
}
console.log(myFunc());
console.log(myFunc("a", "b"));
console.log(myFunc("a", "b", "c", "d"));
// 0 2 4
function Person(name, age) {
this.name = name || "John";
this.age = age || 24;
this.displayName = function () {
console.log(this.name);
};
}
Person.name = "John";
Person.displayName = function () {
// this 指向 Person 对象(函数)的函数名: Person
console.log(this.name);
};
var person1 = new Person("John");
person1.displayName();
Person.displayName();
// John Person
变量与函数同名的情况
var a = 10;
(function () {
console.log(a);
a = 5;
console.log(window.a);
var a = 20;
console.log(a);
})();
// undefined 10 20
var b = 10;
(function b() {
b = 20;
console.log(b);
})(); //[Function b]
var b = 10;
(function b() {
"use strict";
b = 20;
console.log(b);
})(); // "Uncaught TypeError: Assignment to constant variable."
函数表达式与函数声明不同,函数名只在该函数内部有效,并且此绑定是常量绑定。 在严格模式下 b 函数相当于常量,无法进行重新赋值,在非严格模式下函数声明优先变量声明
简单改造下面的代码,使之分别打印 10 和 20
var b = 10;
(function b() {
b = 20;
console.log(b);
})();
- 打印 20
var b = 10;
(function b() {
var b = 20;
console.log(b);
})(); // 20 在自执行函数中重新定义一个变量,
var b = 10;
(function a() {
b = 20;
console.log(b);
})();
var b = 10;
(function () {
b = 20;
console.log(b);
})();
- 打印 10
var b = 10;
(function b() {
b = 20;
console.log(window.b);
})();
在自执行函数中访问 window,window 中的 b 值为 10 2.
var b = 10;
(function () {
console.log(b);
b = 20;
})();
var b = 10;
(function b(b) {
console.log(b);
b = 20;
})(b);
作用域
var foo = "Hello";
(function () {
var bar = " World";
alert(foo + bar);
})();
alert(foo + bar);
// "Hello World" 和 ReferenceError: bar is not defined
for (var i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 1);
}
for (let i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 1);
}
// 3 3 3 and 0 1 2
function getAge() {
"use strict";
age = 21;
console.log(age);
}
getAge();
// ReferenceError
构造函数
class Chameleon {
static colorChange(newColor) {
this.newColor = newColor;
}
constructor({ newColor = "green" } = {}) {
this.newColor = newColor;
}
}
const freddie = new Chameleon({ newColor: "purple" });
freddie.colorChange("orange");
// TypeError
function Person(firstName, lastName) {
this.firstName = firstName;
this.lastName = lastName;
}
const member = new Person("Lydia", "Hallie");
// 非原型链
Person.getFullName = () => this.firstName + this.lastName;
// member.getFullName === undefined
console.log(member.getFullName());
// TypeError
function Person(firstName, lastName) {
this.firstName = firstName;
this.lastName = lastName;
}
const lydia = new Person("Lydia", "Hallie");
const sarah = Person("Sarah", "Smith");
console.log(lydia);
console.log(sarah);
// Person {firstName: "Lydia", lastName: "Hallie"} and undefined
String.prototype.giveLydiaPizza = () => {
return "Just give Lydia pizza already!";
};
const name = "Lydia";
name.giveLydiaPizza();
("Just give Lydia pizza already!");
函数
function getPersonInfo(one, two, three) {
console.log(one);
console.log(two);
console.log(three);
}
const person = "Lydia";
const age = 21;
getPersonInfo`${person} is ${age} years old`;
// ["", " is ", " years old"] Lydia 21
对象
const a = {};
const b = { key: "b" };
const c = { key: "c" };
a[b] = 123;
a[c] = 456;
console.log(a[b]);
// 456
return value
function getNumber() {
return 2, 4, 5;
}
var numb = getNumber();
console.log(numb);
// 5 最后一个值就是 return 回的值
(function () {
function sayHello() {
var name = "Hi John";
return;
{
fullName: name;
}
}
console.log(sayHello().fullName);
})();
// 需要在同一行
// Uncaught TypeError: Cannot read property 'fullName' of undefined
sort 函数
(function () {
var arrayNumb = [2, 8, 15, 16, 23, 42];
arrayNumb.sort();
console.log(arrayNumb);
})();
// [ 15, 16, 2, 23, 42, 8 ]
模块化
使用模块化的好处
- 解决命名冲突
- 提供复用性
- 提高代码可维护性
实现模块化方式:
- 立即执行函数:在一个单独的函数作用域中执行代码,避免变量冲突。
- CommonJs:同步加载,主要用在服务器端 node。输出的是值拷贝。 正是因为是值拷贝,所以可能会存在性能问题。
- AMD:异步加载。
requirejs在推广过程中对模块定义的规范化产出,提前执行,推崇依赖前置 - CMD:异步加载。
seajs在推广过程中对模块定义的规范化产出,支持动态引入依赖文件延迟执行,推崇依赖就近。 - UMD:兼容 AMD 和 commonJS 规范的同时,还兼容全局引用的方式。能够运行在浏览器或服务器环境。
- ES6 Module:ES6 模块。模块输出的是一个值的引用,编译时输出接口。
ES Module 与 CommonJS 的区别
关键点:
- ES Module 是值的引用,CommonJS 是值的拷贝。
- ES Module 编译时就能确定模块的依赖关系,以及输入和输出的变量,输出接口。CommonJS 运行时加载, require 可以做动态加载,import 语句做不到,import 语句必须位于顶层作用域中。
- ES Module 是异步导入,因为用于浏览器避免对渲染有影响。 CommonJS 是同步导入,因为用于服务端,同步导入会很快。
- ES6 Module 的 this 是 undefined, CommonJs 的 this 是当前模块,
UMD
UMD 的实现很简单:
- 先判断是否支持 Node.js 模块格式(exports 是否存在),存在则使用 Node.js 模块格式。
- 再判断是否支持 AMD(define 是否存在),存在则使用 AMD 方式加载模块。
- 前两个都不存在,则将模块公开到全局(window 或 global)。