内置类型
(1)基本数据类型
number
- 浮点型,基于IEEE 754标准的长浮点数(共64位:1位数符,11位阶码_移码,52位尾数_原码)实现。
string
boolean
undefined
null
symbol
(2)引用数据类型
Object
function
array
null和undefined的异同点
相同点
- 转为布尔值都是false。
undefined
- 定义了但未赋值。
- typeof(undefined)=‘undefined’
- Number(undefined)=NaN
null
- 定义了并且赋值了,值为null。
- typeof(null)=‘Object’
- Number(null)=0
判断数据类型
(1)typeof(obj)
以字符串的形式返回一个值的数据类型。
- 可以判断number/string/boolean/undefined/symbol/function。
- 不能判断null(返回object)、array(返回object)。
- NaN的类型是number。
- null的类型是object。因为1995年的 JavaScript 语言第一版,只设计了五种数据类型(对象、整数、浮点数、字符串和布尔值),没考虑
null
,只把它当作object
的一种特殊值。后来null
独立出来,作为一种单独的数据类型,为了兼容以前的代码,typeof null
返回object
就没法改变了。
(2)A instanceof B
检查一个对象是否是一个类的实例(检测构造函数B的
prototype
属性是否出现在某个实例对象A的原型链上)。
- 所有对象都是Object的后代。
- 可以判断array。
function instanceOf(a, b){
//获取构造函数的显式原型
let bp = b.prototype;
//获取实例的隐式原型
let ap = a.__proto__;
//判断是否在ap的原型链上
while(ap){
if(ap === bp){
return true;
}
ap = ap.__proto__;
}
return false;
}
(3)Object.prototype.toString.call(obj)
判断一个变量是不是undefined
//undefined不是保留字,在低版本浏览器能被赋值。用以下方法判断会出错。
a === undefined
//void后面随便加上一个表达式,返回是undefined。
a === void 0
类型转换
(1)转Boolean
//数值=>布尔值
//0(-0)和NaN为false,其他都为true
Boolean(0) // false
Boolean(-0) // false
Boolean(NaN) // false
//字符串=>布尔值
//空串为false,其他都为true
Boolean('') // false
//null=>布尔值
Boolean(null) // false
//undefined=>布尔值
Boolean(undefined) // false
(2)转Number
//布尔值=>数值
//字符串=>数值
//空串、全是空格为0
Number('') // 0
Number(' ') // 0
//字符串含有非数值的内容为NaN
Number('a') // NaN
//null=>数值
Number(null) // 0
//undefined=>数值
Number(undefined) // NaN
(3)转String
(4)对象转基本类型
对象转基本类型时,先尝试调用Symbol.toPrimitive,再调用valueOf()和toString()。
Symbol.toPrimitive
- 返回该对象对应的原始类型值。
valueOf()
- 默认返回对象自身。
toString()
- 默认返回字符串。
let obj = {
//hint表示当前运算的模式
[Symbol.toPrimitive](hint){
switch(hint){
//需要转成数值
case 'number':
return 123;
//需要转成字符串
case 'string':
return 'str';
//可以转成数值,也可以转成字符串
case 'default':
return 'default';
default:
throw new Error();
}
}
};
let a = {
valueOf() {
return 0;
},
toString() {
return '1';
},
[Symbol.toPrimitive]() {
return 2;
}
}
1 + a // => 3
'1' + a // => '12'
(5)运算符
隐式转换规则
- 转为string
+(字符串连接符:一方是字符串or都不是字符串或数值时)- 转为number
++、–(自增自减运算符)
±*/%(算术运算符)
==、!=、===、!===(关系运算符)- 转为boolean
!(逻辑非运算符)
1 + '1' // '11'
[1, 2] + [2, 1] // '1,22,1'
//[1, 2].toString() => '1,2'
//[2, 1].toString() => '2,1'
'a' + + 'b'
//先一元运算,+'b' => NaN
//'a' + NaN => 'a' + 'NaN' => 'aNaN'
2 * '2' // 4
+
- 一元运算符。
- 加号 + 应用于单个值,对数字没有任何作用。但是如果运算元不是数字,加号 + 则会将其转化为数字。
[] == ![] // true
//![] => 布尔值false => 数值0
//[] => ''(对象转基本类型调用[Symbol.toPrimitive],然后toString())
// => 数值0
(6)运算符运算顺序
一元运算符>二元运算符。
a==1&&a==2&a==3
//对象类型转换
//当使用==进行比较时,会先类型转换,再进行比较。当Object和Number类型进行比较时,Object类型会转换为Number类型;转换时,会尝试调用Object.valueOf()和Object.toString()来获取对应的数字基本类型。
var a = {
i: 1,
toString: function(){
return a.i++;
}
};
console.log(a==1&&a==2&&a==3)//true
//数组类型转换
//当使用==进行比较时,会先类型转换,再进行比较。当Array和Number类型进行比较时,Array类型会转换为Number类型;转换时,会先调用Array.toString(),Array.toString()会隐含调用Array.join()方法。此时我们让Array.join()等于Array.shift()方法即可。
var a = [1, 2, 3];
a.join = a.shift;
//defineProperty
var val = 0;
Object.defineProperty(window, 'a', {
get: function(){
return val++;
}
});
var、let、const的区别
var
- 存在变量、函数提升。
- 全局作用域。
- 可以重复声明。
let
- 不存在变量、函数提升。
- 块级作用域。
- 不可以重复声明。
const
- 不存在变量、函数提升。
- 块级作用域。
- 不可以重复声明。
- 定义时需赋值。
- 赋值后一般不可修改。如果赋的是基本数据类型,不可修改。如果赋的是引用数据类型,可以修改。
函数的length
//没有指定默认值,length=参数个数。
//指定了默认值,length=没有指定默认值的参数个数。
(function (a) {}).length // 1
(function (a = 5) {}).length // 0
(function (a, b, c = 5) {}).length // 2
//不计入rest参数。
(function (...args){}).length // 0
//不计入设置了默认值的参数后面的参数个数。
(function (a = 0, b, c){}).length // 0
(function f(name, age=18, gender, ...args){}).length // 1
//面试题
123['toString'].length + 123//124
//123相当于new Number()
//顺着隐式原型链,new Number()=>Number.prototype,找到toString属性(方法是特殊的属性),toString.length=1
对象创建模式
//new Object()创建
//语句太多了
var obj = new Object();
obj.name = 'tom';
obj.age = 12;
obj.setName = function(name){
this.name = name;
}
//字面量创建
//如果要创建多个对象,存在重复代码
var obj = {
name: 'Tome',
age: 12,
setName: function(name){
this.name = name;
}
};
//工厂模式创建
//无法区分类型
function createPerson(name, age){
var obj = {
name: name,
age: age,
setName: function(name){
this.name = name;
}
};
return obj;
}
var p = createPerson('tom', 12);
//构造函数创建
//每个数据都有相同数据(函数),浪费内存
function Person(name, age){
this.name = name;
this.age = age;
this.setName = function(name){
this.name = name;
};
}
var person = new Person('tom', 12);
//构造函数+原型
function Person(name, age){
this.name = name;
this.age = age;
}
Person.prototype.setName = function(name){
this.name = name;
};
手写new
function myNew(fn, ...args){
//创建空对象
let obj = {};
//实例的隐式原型指向构造函数的显式原型
obj.__proto__ = fn.prototype;
//绑定this,执行构造函数
fn.apply(obj, args);
return obj;
}
let o1 = new fn();
//等价于
let o2 = myNew(fn);
this
根据函数的调用方式不同,this会指向不同的对象。
//以函数的形式调用:this指向全局对象window。(在strict模式下,不会指向全局对象,而是undefined)
fun();
//以方法的形式调用:this指向调用方法的对象。
obj.fun();
//以构造函数的形式调用时,this指向新创建的实例对象。
function Person(name){
this.name = name;
console.log(this);
}
let person = new Person('abc');
//使用call、apply、bind时,this指向指定的对象。
闭包
定义
- 引用了另一个函数作用域中变量的函数。
产生的条件
- 函数嵌套。
- 内部函数引用了外部函数的数据(变量/函数)。
作用
- 读取外部函数内部的变量。
- 让这些变量始终保持在内存中。
外层函数执行后,其执行上下文会被清除。但是由于闭包用到了外层函数的活动对象,导致外层函数的活动对象不能从内存释放。只要闭包没有被垃圾回收机制清除,就一直保持着对外层函数的活动对象的引用,外层函数的活动对象就一直保存在内存中。(至于是全部保存还是只保存被引用的,取决于JS引擎使用的GC机制。https://www.iteye.com/blog/justjavac-1465169)应用
- 实现JS模块。
- 防抖节流。
- 回调函数(事件监听器、ajax回调、定时器……
优点
- 希望一个变量长期存储在内存中。
- 避免全局变量污染。
- 私有成员的存在。
缺点
- 常驻内存,增加内存使用量。
- 使用不当会造成内存泄露。
http://cavszhouyou.top/JavaScript%E6%B7%B1%E5%85%A5%E7%90%86%E8%A7%A3%E4%B9%8B%E9%97%AD%E5%8C%85.html
//实现每隔1s打印1, 2, 3, 4,5
//闭包:让i的值常驻内存,当输出j时,引用的是外部函数循环时的i值。
for(var i = 1; i <= 5; i++){
(function(j){
setTimeout(()=>{
console.log(j);
}, j * 1000);
})(i);
}
//let:利用块级作用域
for(let i = 1; i <= 5; i++){
setTimeout(()=>{
console.log(i);
}, i * 1000);
}
//setTimeout第三个参数:一旦定时器到期,它们会作为参数传递给回调函数。
//每次循环中将i值变成setTimeout的第三个参数传入。
for(var i = 1; i <= 5; i++){
setTimeout((j)=>{
console.log(j);
}, i * 1000, i);
}
深浅拷贝
浅拷贝
- 只拷贝第一层,深层只是引用。(类似软链接)
- 新对象的更改会影响原对象。
- 新对象与原对象共享相同的对象。
深拷贝
- 层层拷贝。(类似全部复制)
- 新对象的更改不会影响原对象。
- 新对象与原对象不共享相同的属性。
//浅拷贝
let a = {
age: 1
};
//Object.assign(target, origin):将源对象的所有可枚举属性(包括symbol)复制到目标对象,返回目标对象。
let b = Object.assign({}, a);
//展开运算符...
let b = {...a};
//深拷贝
//json
//如果有字段值为undefined、function、symbol,字段直接消失;如果有字段值为NaN、+-Infinity,字段变成null;如果有字段为RegExp对象,字段变成{};如果有环引用,直接报错。
let b = JSON.parse(JSON.stringfy(a));
//用lodash的cloneDeep()
//不能复制函数。
//let b = _.cloneDeep(a);
//手写深拷贝(待填坑)
模块化规范CommonJS和ES6的区别
- CommonJS支持动态导入(require(${path}/xx));ES6不支持,但已有提案。
- CommonJS是同步导入,因为用在服务端,文件都在本地,即使卡住主线程,影响也不大;ES6是异步导入,因为用在浏览器,需要下载文件,如果采用同步导入对渲染会有很大影响。
- CommonJS在导出时是深拷贝,修改导出的值,导入的值也不会改变,需要重新导入来更新;ES6在导出时是浅拷贝,修改导出的值,导入的值会发生变化。
防抖、节流
防抖
- 频繁去触发一个事件,但是只触发最后一次,以最后一次为准。
- 应用场景(大量事件一次响应)
(1)防止频繁点击按钮提交表单。
(2)防止input框变化频繁触发事件。
//在事件被触发 n 秒后再执行回调,如果在这 n 秒内事件又被触发,则重新计时。
//不带immediate选项(搜索)
function debounce(func, delay = 500){
let timer;
return function(...args){
//如果已经有定时器,清空定时器
if(timer) clearTimeout(timer);
timer = setTimeout(()=>{
//this为调用debounce的对象
func.apply(this, args);
}, delay);
}
}
button.addEventListener('click', debounce(payMoney, 1000));
节流
- 频繁去触发一个事件,但是只能每隔一段时间触发一次。
- 应用场景(大量事件按时间平均分配触发)
(1)在滚动加载或者监听滚动条位置的场景下,用户在滚动页面时,每隔一段时间执行一次函数,而不是每次滚动就执行函数。
(2)搜索框的联想搜索。
(3)注册时手机验证码。
//每次触发判断是否在时间间隔内,如果在则不触发事件,如果不在则设置定时器并触发事件。
//定时器
function throttle(func, delay){
let timer;
return function(){
//如果已经有定时器,返回。
if(timer) return;
timer = setTimeout(()=>{
func.apply(this, arguments);
//清空定时器
timer = null;
}, delay);
}
}
//Date()
function throttle(func, delay){
let pre = 0;
return function(...args){
let now = new Date();
if(now - pre > delay){
func.apply(this, args);
pre = now;
}
}
}
window.addEventListener('resize', throttle(coloring, 2000));
继承
function Animal(name){
this.name = name || 'Animal';
this.sleep = function(){
console.log(this.name+'is sleeping...');
}
}
Animal.prototype.eat = function(food){
console.log(this.name + 'is eating the' + food);
}
//原型链继承:将父类的实例作为子类的原型
/*
优点:1、实例既是子类实例,又是父类实例。
2、父类新增原型属性,子类都能访问到。
3、简单,易于实现。
缺点:1、想为子类添加属性和方法,必须在new之后执行,不能放在构造器中。
2、子类实例既继承父类实例属性,也继承父类原型属性。
3、创建子实例时,无法向父类构造函数传参。
4、不支持多继承(继承多个父类)。
*/
function Cat(){}
Cat.prototype = new Animal();
//Cat.prototype = Object.creat(Animal.prototype)
Cat.prototype.name = 'cat';
var cat = new Cat();
console.log(cat.name); // 'cat'
cat.eat('fish'); // 'cat is eating the fish'
cat.sleep(); // 'cat is sleeping...'
console.log(cat instanceof Animal); // true
console.log(cat instanceof Cat); // true
//构造继承
/*
优点:1、解决了原型链继承中,子类实例继承父类原型属性的问题。
2、创建子类实例时,可以向父类传递参数。
3、支持多继承。
缺点:1、子类实例不是父类实例。
2、子类实例只继承父类实例属性。
*/
function Cat(name){
Animal.call(this);
this.name = name || 'Tom';
}
var cat = new Cat();
console.log(cat.name); // 'Tom'
cat.sleep(); // 'Tom is sleeping...'
console.log(cat instanceof Animal); // false
console.log(cat instanceof Cat); // true
//实例继承
/*
优点:不限制调用方式。
缺点:1、实例是父类的实例,不是子类的实例。
2、不支持多继承。
*/
function Cat(name){
var instance = new Animal();
instance.name = name || 'Tom';
return instance;
}
var cat = new Cat();
console.log(cat.name);
cat.sleep;
console.log(cat instanceof Animal);// true
console.log(cat instanceof Cat);// false
//拷贝继承
/*
优点:支持多继承。
缺点:1、效率低,内存占用高。
2、无法获取父类的不可枚举方法(for...in访问不到)。
*/
function Cat(name){
var animal = new Animal();
for(var p in animal){
Cat.prototype[p] = animal[p];
}
this.name = name || 'Tom';
}
var cat = new Cat();
console.log(cat.name);
cat.sleep();
console.log(cat instanceof Animal); // false
console.log(cat instanceof Cat); // true
//组合继承(构造继承+原型链继承)
/*
优点:1、既继承父类的实例属性,又继承父类的原型属性。
2、实例既是子类的实例,又是父类的实例。
3、不存在
4、构造函数可以传参。
缺点:调用两次父类构造函数,生成了两份实例。
*/
function Cat(name){
Animal.call(this);
this.name = name || 'Tom';
}
Cat.prototype = new Animal();
Cat.prototype.constructor = Cat;
var cat = new Cat();
console.log(cat.name);
cat.sleep();
cat.eat('fish');
console.log(cat instanceof Animal);// true
console.log(cat instanceof Cat);// true
//寄生组合继承:通过寄生方式,砍掉父类的实例属性,这样,在调用两次父类的构造时,就不会初始化两次实例方法/属性,避免继承组合的缺点。
function Cat(name){
Animal.call(this);
this.name = name || 'Tom';
}
var Super = function(){};
Super.prototype = Animal.prototype;
Cat.prototype = new Super();
var cat = new Cat();
console.log(cat.name);
cat.sleep();
console.log(cat instanceof Animal); // true
console.log(cat instanceof Cat); // true
call、apply、bind
/*
联系:都用于修改this指向。第一个参数都是this的指向对象。
区别:call的其他参数直接写入,用逗号分隔开。
apply的其他参数要放在一个数组中传入。
bind的其他参数直接写入,用逗号分隔开。但是它返回的是一个函数(相当于修改过this指向的函数)。调用时要在后面加一个括号,表示立即执行。
*/
obj.myFun.call(db,'成都','上海');
obj.myFun.apply(db, ['成都','上海']);
obj.myFun.bind(db,'成都','上海')('杭州');
//手写call
Function.prototype.myCall = function (obj, ...args){
//不传入第一个参数,则默认为window。
obj = obj || window;
//防止键名重复
const fn = Symbol();
//this指的是fun;在obj身上添加fun方法。
obj[fn] = this;
const res = obj[fn](...args);
delete obj[fn];
return res;
}
fun.myCall(obj, 'a', 'b');
//手写apply
Function.prototype.myApply = function(obj, args){
obj = obj || window;
const fn = Symbol();
obj[fn] = this;
const res = obj[fn](...args);
delete obj[fn];
return res;
}
//手写bind(柯里化)
Function.prototype.myBind = function(obj, ...args){
obj = obj || window;
const _this = this;
const res = function(...innerArgs){
//如果使用了new,this会绑定到新创建的实例对象
//判断是否使用了new
if(this instanceof res){
_this.apply(this, [...args, ...innerArgs]);
}else{
_this.apply(obj, [...args, ...innerArgs]);
}
};
res.prototype = _this.prototype;
return res;
}
fun.myBind(obj, 'a', 'b')('c');
//new
var ff = fun.myBind(obj, 'a', 'b');
var f = new ff('c');
Promise
async/await
//async相当于用Promise.resolve包裹处理
async function fn(){
//await的下一句相当于用Promise.then处理
await request(1);
await request(2);
//没有return语句,则返回的Promise对象的值为undefined
//return 3;
}
console.log(fn()); // Promise {<fulfilled>: undefined}
async function fn(){
const res1 = await request(5);
const res2 = await request(res1);
console.log(res2);
}
Generator
//第一个使用next(),传递参数是无效的。
function* gen(){
const num1 = yield 1;
const num2 = yield 2;
return 3;
}
const g = gen();
console.log(g.next()); // {value: 1, done: false}
//用generator函数实现async/await(填坑吧)
async/await和generator的区别
async/await返回值是promise对象,generator不是。
generator需要自己执行相应的操作来实现排队效果。
高阶函数
参数是函数,返回值也是函数。
柯里化
curring
- 将多参数的函数转换成单参数的形式。
//函数参数复用
function uri_currying(protocol){
return function(hostname, pathname){
return `${protocol}${hostname}${pathname}`;
};
}
const uri_https = uri_currying('https://');
console.log(uri_https('www.baidu.com', '/a'));
//'https://www.baidu.com/a'
//判断兼容性
const whichEvent = (function(){
if(window.addEventListener){
return function(element, type, listener, useCapture){
element.addEventListener(type, function(e){
listener.call(element, e);
}, useCapture);
}
}else if(window.attachEvent){
return function(element, type, handler){
element.attachEvent('on'+type, function(e){
handler.call(element, e);
});
}
}
});//使用立即执行函数则whichEvent直接等于两个之中的一个函数。
//延迟执行 bind
函数重载
function fn(name) {
console.log(`我是${name}`);
}
function fn(name, age) {
console.log(`我是${name},今年${age}岁`);
}
function fn(name, age, sport) {
console.log(`我是${name},今年${age}岁,喜欢运动是${sport}`);
}
//理想结果
fn('林三心'); // 我是林三心
fn('林三心', 18); // 我是林三心,今年18岁
fn('林三心', 18, '打篮球'); // 我是林三心,今年18岁,喜欢运动是打篮球
//arguments
function fn(){
switch(arguments.length){
case 1:
var [name] = arguments;
consoel.log(`我是${name}`);
break;
case 2:
var [name, age] = arguments;
console.log(`我是${name},今年${age}岁`);
break;
case 3:
var [name, age, sport] = arguments;
console.log(`我是${name},今年${age}岁,喜欢运动是${sport}`);
break;
}
}
//利用闭包保存引用
function addMethod(object, name, fn){
//保存前一次的方法
var old = object[name];
//重写方法(执行的时候才调用方法)
object[name] = function(){
//如果调用时,传入的参数和已传入方法的参数长度一样,则直接调用。
if(fn.length === arguments.length){
return fn.apply(this, arguments);
//否则判断old是否为函数,则调用
}else if(typeof old === 'function'){
return old.apply(this, arguments);
}
};
}
addMethod(window, 'fn', (name) => console.log(`我是${name}`));
addMethod(window, 'fn', (name, age) => console.log(`我是${name},今年${age}岁`));
addMethod(window, 'fn', (name, age, sport) => console.log(`我是${name},今年${age}岁,喜欢运动是${sport}`));
V8垃圾回收机制
(1)
基本数据类型
- 拥有固定的大小;栈内存保存值;直接访问。
引用数据类型
- 大小不固定;栈内存保存指针,指针指向堆内存中的对象空间;通过引用访问。
由于栈内存所存的基础数据类型大小是固定的,所以栈内存的内存都是操作系统自动分配、释放回收的。由于堆内存所存的大小不固定,系统无法自动释放回收,需要JS引擎来手动释放内存。
(2)回收算法
Scavenage算法
- 根据可达性,标记活动对象和非活动对象。
- 把from-space的活动对象复制到to-space并排序。
- 清除from-space的非活动对象。
- from-space和to-space互换,以便下次进行垃圾回收。
Mark-Sweep算法
- 标记活动对象和非活动对象。
- 清除非活动对象。
Mark-Compact算法
- 标记活动对象和非活动对象。
- 清除非活动对象。
- 将活动对象紧凑到一起。
(3)
全停顿Stop-The-World
- 垃圾回收优先于代码执行,两者同时进行时,会先停止代码的执行,等到垃圾回收完毕再执行代码。
Orinoco优化
- Orinoco是V8垃圾回收器的项目代号。为了解决全停顿问题,提出了增量标记、懒性清理、并发、并行的优化方法。
增量标记Incremental Marking
- 标记一段,JS代码运行一段时间。
懒性清理Lazy Sweeping
- 当剩余空间足以让JS代码跑起来时,延迟清理或者只清理部分垃圾。
并发
- 允许在垃圾回收的同时不需要将主线程挂起,两者可以同时进行,只需要在个别时候停下来对垃圾回收器做一些特殊的操作。
并行
- 允许主线程和辅助线程同时执行同样的GC工作,这样可以让辅助线程来分担主线程的GC工作,使得垃圾回收所耗费的时间等于总时间除以参与的线程数量(加上一些同步开销)。
写屏障
- 解决对象引用改变、标记错误等现象。
装箱拆箱
装箱
- 将基本数据类型转换为对应的引用数据类型。
const s = 'a_a';
const index = s.indexOf('_');
console.log(inde); // 1
//等同于(进行了装箱操作)
//1、创建对应类型的实例
var temp = new String('a_a');
//2、在实例上调用指定的方法
const index = temp.indexOf('_');
//3、销毁实例
temp = null;
console.log(index); // 1
拆箱
- 将引用数据类型转换为对应的基本数据类型的操作。
//通过valueOf实现拆箱
var objNum = new Number(123);
var objStr = new String('123');
console.log(typeof objNum);//'object'
console.log(typeof objStr);//'object'
console.log(tyeof objNum.valueOf());//number
console.log(tyeof objStr.valueOf());//number
如何避免内存泄漏
减少全局变量。
及时清理定时器。
合理使用闭包。
及时解除对分离DOM的引用。
- 分离DOM:分离的DOM元素是已从DOM中删除的元素,但是由于JavaScript的原因,它们的内存仍然保留。这意味着只要元素对任何变量或对象的引用在任何地方,即使从DOM中销毁后也不会进行垃圾回收。
<button id="btn">点击</button>
//虽然已从DOM中删除,但仍保存着引用,则仍存在内存之中。
let btn = document.getElementById('btn');
document.body.removeChild(btn);
//解除引用。
btn = null;
模块化和模块化规范
模块化是为了解决命名冲突、污染问题,良好的模块化设计可以降低代码之间的耦合性,提高代码的可维护性、可复用性。
模块化规范是为了统一JS模块的定义和加载机制,提高开发效率。
CommonJS
- 主要是Node.js使用。(同步加载不适用于浏览器端)
- 通过require同步加载模块,exports导出。(首次加载会缓存)
- 难以支持模块静态分析(在代码运行前就可以判断哪些代码使用到了,哪些代码没使用到)。
AMD
- 主要是浏览器端使用。
- 通过require异步加载模块,define定义模块和依赖。依赖前置。
- 难以支持模块静态分析。
CMD
- 主要是浏览器端使用。
- 通过require异步加载模块,define定义模块和依赖。依赖就近。
- 难以支持模块静态分析。
ES6
- 现代浏览器原生支持。
- 通过import异步加载模块,exports导出。
- 支持模块静态分析。
Symbol()、Symbol.for()和Symbol.keyFor()
Symbol()
- 直接返回一个新的symbol值。
Symbol.for()
- 先根据key,在全局symbol注册表中查找对应的symbol,如果查找到就返回;否则就创建一个与该key关联的symbol,并放入全局symbol注册表中。
let yellow = Symbol('Yellow');
let yellow1 = Symbol.for('Yellow');
yellow === yellow1; // false
let yellow2 = Symbol.for('Yellow');
yellow1 === yellow2; // true
Symbol.keyFor()
- 获取全局symbol表中与某symbol值相关联的键值。
let yellow1 = Symbol.for('Yellow');
Symbol.keyFor(yellow1); // 'Yellow'
for…in
遍历key。
for…of
- 内部通过Symbol.iterator实现。
- object不能用for…of遍历。
for(let key of Object.keys(obj)){
console.log(obj[key]);
}