基本类型

数据类型

7 种原始类型(基本类型):

  1. Boolean
  2. String
  3. Number
  4. Null
  5. Undefined
  6. Symbol(新定义)
  7. BigInt(新定义)

引用类型:

  1. Object
  2. Array
  3. Function
  4. String (很多场景下 string 会被强制转化成 String 类型,但其实也是对象类型)

原始类型与引用类型存储的方式也不同,原始数据类型存储在栈中,而引用数据类型存储在堆中。

使用 typeof 能得到哪些类型?

  1. boolean
  2. number
  3. string
  4. undefined
  5. symbol
  6. object
  7. 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 中类型转换只有三种情况,分别是:

  1. 转换为布尔值: 除了 undefined, null, false, NaN, '', 0, -0,其他所有值都转为 true,包括所有对象。
  2. 转换为数字
  3. 转换为字符串

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. 运算中其中一方为字符串,那么就会把另一方也转换为字符串
  2. 如果一方不是字符串或者数字,那么会将它转换为数字或者字符串
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

比较运算符

  1. 如果是对象,就通过 toPrimitive 转换对象
  2. 如果是字符串,就通过 unicode 字符索引来比较
let a = {
  valueOf() {
    return 0;
  },
  toString() {
    return "1";
  },
};
a > -1; // true

在以上代码中,因为 a 是对象,所以会通过 valueOf 转换为原始类型再比较值。

对象到字符串的转换步骤

  1. 如果对象有 toString()方法,javascript 调用它。如果返回一个原始值(primitive value 如:string number boolean),将这个值转换为字符串作为结果
  2. 如果对象没有 toString()方法或者返回值不是原始值,javascript 寻找对象的 valueOf()方法,如果存在就调用它,返回结果是原始值则转为字符串作为结果
  3. 否则,javascript 不能从 toString()或者 valueOf()获得一个原始值,此时 throws a TypeError

对象到数字的转换步骤

  1. 如果对象有 valueOf()方法并且返回元素值,将返回值转换为数字作为结果
  2. 否则,如果对象有 toString()并且返回原始值,将返回结果转换为数字作为结果
  3. 否则,throws a TypeError

<,>,<=,>=的比较规则

所有比较运算符都支持任意类型,但是比较只支持数字和字符串,所以需要执行必要的转换然后进行比较,转换规则如下:

  1. 如果操作数是对象,转换为原始值:如果 valueOf 方法返回原始值,则使用这个值,否则使用 toString 方法的结果,如果转换失败则报错
  2. 经过必要的对象到原始值的转换后,如果两个操作数都是字符串,按照字母顺序进行比较(他们的 16 位 unicode 值的大小)
  3. 否则,如果有一个操作数不是字符串,将两个操作数转换为数字进行比较

+运算符工作流程

  1. 如果有操作数是对象,转换为原始值
  2. 此时如果有一个操作数是字符串,其他的操作数都转换为字符串并执行连接
  3. 否则:所有操作数都转换为数字并执行加法
"100" + 100; // "100100"
100 + "100"; // "100100"
100 + true; // 101
100 + false; // 100
100 + undefined; //NaN
100 + null; // 100
// + 会自动转换后面跟随的值类型

原始类型转化

强制类型转化的顺序是怎么样的?

对一个“对象”进行数学运算操作时候,会涉及到对象 => 基础数据类型的转化问题。当一个对象执行例如加法操作的时候,如果它是原始类型,那么就不需要转换。否则,将遵循以下规则:

  1. 调用实例的valueOf()方法,如果有返回的是基础类型,停止下面的过程;否则继续
  2. 调用实例的toString()方法,如果有返回的是基础类型,停止下面的过程;否则继续
  3. 都没返回原始类型,就会报错

请看下面的测试代码:

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"); // 报错

除了valueOftoString,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 互相不等就可以了

  1. 如果两个值类型相同,按照 === 比较方法进行比较
  2. 如果类型不同,使用如下规则进行比较
  3. 如果其中一个值是 null,另一个是 undefined,它们相等
  4. 如果一个值是数字另一个是字符串,将字符串转换为数字进行比较
  5. 如果有布尔类型,将true 转换为 1,false 转换为 0,然后用 == 规则继续比较
  6. 如果一个值是对象,另一个是数字或字符串,将对象转换为原始值然后用 == 规则继续比较
  7. 其他所有情况都认为不相等

变量声明

函数名与变量名同名

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);
})();

针对这题,在知乎上看到别人的回答说:

  1. 函数表达式与函数声明不同,函数名只在该函数内部有效,并且此绑定是常量绑定。
  2. 对于一个常量进行赋值,在 strict 模式下会报错,非 strict 模式下静默失败。
  3. 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 ]

模块化

使用模块化的好处

  1. 解决命名冲突
  2. 提供复用性
  3. 提高代码可维护性

实现模块化方式:

  1. 立即执行函数:在一个单独的函数作用域中执行代码,避免变量冲突。
  2. CommonJs:同步加载,主要用在服务器端 node。输出的是值拷贝。 正是因为是值拷贝,所以可能会存在性能问题。
  3. AMD:异步加载。requirejs 在推广过程中对模块定义的规范化产出,提前执行,推崇依赖前置
  4. CMD:异步加载。seajs 在推广过程中对模块定义的规范化产出,支持动态引入依赖文件延迟执行,推崇依赖就近。
  5. UMD:兼容 AMD 和 commonJS 规范的同时,还兼容全局引用的方式。能够运行在浏览器或服务器环境。
  6. ES6 Module:ES6 模块。模块输出的是一个值的引用,编译时输出接口。

ES Module 与 CommonJS 的区别

关键点:

  1. ES Module 是值的引用,CommonJS 是值的拷贝。
  2. ES Module 编译时就能确定模块的依赖关系,以及输入和输出的变量,输出接口。CommonJS 运行时加载, require 可以做动态加载,import 语句做不到,import 语句必须位于顶层作用域中。
  3. ES Module 是异步导入,因为用于浏览器避免对渲染有影响。 CommonJS 是同步导入,因为用于服务端,同步导入会很快。
  4. ES6 Module 的 this 是 undefined, CommonJs 的 this 是当前模块,

UMD

UMD 的实现很简单:

  1. 先判断是否支持 Node.js 模块格式(exports 是否存在),存在则使用 Node.js 模块格式。
  2. 再判断是否支持 AMD(define 是否存在),存在则使用 AMD 方式加载模块。
  3. 前两个都不存在,则将模块公开到全局(window 或 global)。
Last Updated:
Contributors: yiliang114