ES6的基础知识
var, let, const知识
- 函数的提升优于变量的提升,函数的提升是把函数挪到作用域的顶部,变量的提升只是把变量的声明提升到顶部;
- var 可以在声明之前使用,let 和 const 因为暂时性死区,不能在声明之前使用;
- var 声明的变量会挂载到window对象上,let和const不能;
- let 和 const 基本一致,但是const声明的变量不能被赋值;
JavaScript继承
原型继承
- 组合继承
通过在子类的构造函数中通过Parent.call(this, val)继承父类的属性,子类的原型等于new Parent()来继承父类的函数
function Parent(val) {
this.value = val;
}
Parent.prototype.getValue = function () {
return this.value;
};
function Child(val) {
Parent.call(this, val);
}
Child.prototype = new Parent();
const child = new Child(10);
console.log(child.getValue());
缺点:子类在继承父类的函数的时候会继承父类的一些不需要的属性,比如上面的value,存在内存上的浪费
- 寄生组合继承
- 优化了上面组合继承的缺点,通过继承构造函数的原型实现继承
function Parent(val) {
this.value = val;
}
Parent.prototype.getValue = function () {
return this.value;
};
function Child(val) {
Parent.call(this, val);
}
Child.prototype = Object.create(Parent.prototype, {
constructor: {
value: Child,
writable: true,
configurable: true,
enumerable: false
}
});
const child = new Child(12);
console.log(child.getValue());
以上的继承将父类的原型赋值给子类的原型,并且设置了构造函数为子类,还能正确找到了子类的构造函数;
class 继承
class Animal {
constructor(name) {
this.name = name;
}
getName() {
return this.name;
}
}
class Cat extends Animal {
constructor(value) {
super(value);
this.name = value;
}
}
const cat = new Cat('汤圆');
console.log(cat.getName());
模块化
- 模块化好处
- 解决命名冲突的问题
- 解决代码的复用性
- 解决代码的维护性
模块化的历史
- 立即执行函数
- AMD和CMD
- commonjs: 代表是 nodejs
exports = module.exports, 不能对exports直接赋值,会使得两者不是指向同一个内存地址- ES module
commonjs 和 es module的区别
- commonjs支持动态的导入,如:
require(${a}.js), 而es module目前还不支持动态导入,目前已经有提案了- commonjs 是同步导入的,因为用于服务器,文件都在本地,即使主线程卡住影响也不大;es module是异步导入的,因为用于浏览器,需要下载文件 如果采用同步,卡住会影响页面的渲染
- commonjs 导出采用的是值拷贝,就算是导出的值变了,导入的值也不会变,如果要更新需要重新导入一次;es module采用的是实时绑定的方式,导入和导出指向的是同一个地址 所以导入值会随导出的值变化;
- es module 会编译为
require/exports执行;
commonjs 和 es module循环引用问题
example:
// foo.js
const bar = require('./bar');
console.log('this is from bar value = ', bar);
module.exports = 'this is foo';
// bar.js
const foo = require('./foo');
console.log('this is from foo value = ', foo);
module.exports = 'this is bar';
// index.js
const foo = require('./foo');
const bar = require('./bar');
console.log('end');
结果如下:
this is from foo value = {}
this is from bar value = this is bar
end
解释:
- 执行index.js发现导入foo.js, 去执行foo.js中的代码
- foo中发现依赖bar文件,这时候不会执行foo.js转到bar.js中执行,
- 在bar.js中发现又依赖foo.js,这就是循环依赖,这时候 bar.js的执行权不会继续交回去,而是直接取foo.js中的导出值
module.exports, 由于还没有执行到 foo.js中的导出部分,所以默认的导出是一个空对象,因此继续bar.js的执行顺序;- bar.js执行完后执行权交回给foo.js,继续执行;
- 最后执行index.js剩下的部分;(或许你发现index中的bar.js为啥没有继续执行,因为这里node.js做了缓存了,缓存的时机是模块必须执行完之后);
es module的循环依赖较复杂:
- es module从加载入口到所有模块实例化执行主要经历三步:
- 构建
- 实例化
- 运行
- 构建:
从入口模块开始,根据import关键字遍历依赖树,每个模块生成一个模块记录,所有模块组成一个模块图谱;
所有模块记录都会被缓存在模块映射中,即使是一个模块被多次依赖,也只会记录一次;从而避免模块的重复下载;

- 实例化
根据模块记录的关系,在内存中把模块的导入 import 和导出 export 连接在一起,也称为活绑定。
JS引擎会为每个模块记录创建 模块环境记录(module environment record),用来关联模块实例和模块的导入/导出值。引擎会先采用 深度优先后序遍历(depth first post-order traversal),
将模块及其依赖的导出 export 连接到内存中(直到依赖树末端),然后逐层返回再把模块相对应的导入 import 连接到内存的同一位置。

实例化只是JS引擎在内存中绑定模块间关系,并没有执行任何代码, 也就是说这些连接好的内存空间中并没有存储变量值, 然而,在此过程中导出函数将会被初始化,即所谓的
函数具有提升作用
JS引擎不需要关心是否存在循环依赖,只需要在代码运行的时候,从内存空间中读取该导出值。
- 运行
往内存中填充真实的值
example1:
// index.js
import './bar.js';
import './foo.js';
// bar.js
console.log('bar starting');
export default {
done: true,
}
import foo from './foo.js';
console.log('in bar, foo.done = %j', foo.done);
console.log('bar done');
// foo.js
console.log('foo starting');
export default {
done: true,
};
import bar from './bar.js';
console.log('in bar, foo.done = %j', bar.done);
console.log('foo done');
结果:
foo starting 报错:bar undefined
example2:
// index.js
import './bar.js';
import './foo.js';
// bar.js
console.log('bar starting');
export default function () {
return { done: true };
}
import foo from './foo.js';
console.log('in bar, foo.done = %j', foo().done);
console.log('bar done');
// foo.js
console.log('foo starting');
export default function () {
return {done: true}
}
import bar from './bar.js';
console.log('in bar, foo.done = %j', (bar()).done);
console.log('foo done');
结果:
foo starting
in bar, foo.done = true
foo done
bar starting
in bar, foo.done = true
bar done
所以将示例1中的导出变为函数导出,那么就不会报错问题,因为函数的状态提升,在foo中引用的时候,函数已经声明了所以不会报错;
Proxy
proxy 是es6新增的一个api, 在Vue3中通过这个功能替换了以前的
Object.defineProperty来实现数据的响应式
const p = new Proxy(target, handler);
const onWatch = (obj, setBind, getLogger) => {
const handler = {
get(target, property, receiver) {
getLogger(target, property);
return Reflect.get(target, property, receiver);
},
set(target, property, value, receiver) {
setBind(value, property);
return Reflect.set(target, property, value, receiver);
}
};
return new Proxy(obj, handler);
};
const obj = {a: 1};
const p = onWatch(obj,
(v, property) => {
console.log(v, property);
},
(target, property) => {
console.log(target);
console.log(property);
}
);
map, filter, reduce
[1, 2, 3].map(parseInt);
// 第一轮遍历:parseInt(1, 0) => 1
// 第二次遍历:parseInt(2, 1) => NaN
// 第三次遍历:parseInt(3, 2) => NaN
关于setTimeout 和 setInterval 中的this问题
setTimeout(function() {
console.log(this === window) // true
})
上述代码在任何时候调用this 都是 指的是 window
解决办法
- 使用箭头函数;
- 使用闭包解决;(_this = this)
- 使用
bind函数;
正则匹配汉字和数字
/^[0-9\u4e00-\u9fa5]{2,20}$/