var、let、const区别
1. var存在提升,可以在声明之前使用。let、const因为暂时死区的原因,不能在声明之前使用
例子1:
console.log(a); // 输出: undefiend
var a = 10;
console.log(b); // 输出:报错 ReferenceError: b is not defined
let b = 20;
function foo() {
console.log(test);
let test = 'test'
}
foo(); // Uncaught ReferenceError: Cannot access 'test' before initialization
使用var声明的变量在编译的时候,会将声明提升,编译后如下,所以console输出的结果是10。
var a;
console.log(a);
a = 10;
函数提升优于变量提升,函数提升把整个函数挪到了作用域的顶部,变量提升只会声明挪到作用域顶部
console.log(foo); // 输出:function foo
var foo = 123;
function foo () {}
console.log(foo); // 输出:123
console.log(foo1); // 输出:function foo1
function foo1 () {}
var foo1 = 123;
console.log(foo1); // 输出:123
从上面代码可以看出,用函数方式声明的函数 在编译的时候会被整体提升,所以在第一次console的结果都是函数,第二次输出的为变量的值。编译后的两块大致如下
// 第一段代码
var foo;
function foo() {};
console.log(foo);
foo = 123;
console.log(foo);
// 第二段代码
var foo1;
function foo1() {};
console.log(foo1);
foo1 = 123;
console.log(foo1);
大家是否疑惑过为什么存在变量提升这个东西,多麻烦呀,但是存在即合理,提升根本原始是为了解决函数相互调用的情况,如下代码
function test1() {
test2();
}
function test2() {
test1();
}
test1();
在上述代码中,如果没有变量提升,是无法实现的。
报错是因为存在暂时死区,不能在声明变量前进行访问。
3. const和let基本一致,const声明的基础类型的数据无法变更,声明的对象类型可以变更属性的值
4. 全局中使用var声明的变量会挂挂载window上,let和const不会
var a = 1;
let b = 2;
const c = 3;
console.log(window.a, window.b, window.c); // 输出:1 undefined undefiend
原型继承和class继承
JS中不存在类,class只是语法糖,其本质是函数
class Person {};
Person instanceof Function; // true
组合继承
function Parent(name) {
this.name = name
}
Parent.prototype.getName = function() {
console.log(this.name);
}
function Child(name) {
Parent.call(this, name);
}
Child.prototype = new Parent();
let child = new Child('捌');
child instanceof Parent; // true
child.getName(); // '捌'
组合继承的核心在于子类的构造函数中Parent.call(this, name);继承父类的属性,然后改变子类的原型为 new Parent()来继承父类的函数。
组合继承有点在于构造函数可以传参,不会和父类共享引用数据类型,但可以共享父类的函数。但存在一个缺点是在继承父类函数的时候调用了父类的构造函数,导致子类原型上多了父类的属性,存在内存上的浪费。
寄生组合继承
这种方式是对组合继承的优化,而组合继承的缺点在于继承父类方法的时候调用了构造函数,我们只需要优化了这点就行。
function Parent(name) {
this.name = name;
}
Parent.prototype.getName = function() {
console.log(this.name);
}
function Child(name) {
Parent.call(this, name);
}
Child.prototype = Object.create(Parent.prototype, {
constructor: {
value: Child,
enumerable: false,
writable: true,
configurable: true
}
});
let child = new Child();
child instanceof Parent // true
class 继承
组合继承和寄生组合继承都是通过原型继承,在es6中,我们可以直接通过class解决。
class Parent {
constructor(name) {
this.name = name;
}
getName() {
console.log(this.name);
}
}
class Child extends Parent {
constructor(name) {
super(name);
this.name = name;
}
}
let child = new Child('捌');
child.getName(); // 捌
child instanceof Parent; // true
class继承核心在与子类使用extends表明继承那个父类,并在子类中调用super,这代码可以看成 Parent.call(this, name);
模块化
面试题:什么是模块化?都有哪几种方式?各有什么特点
好处:
- 解决命名冲突
- 提供复用性
- 提高代码可维护性
立即执行函数(方式1)
早期,立即执行函数是实现模块化最常见的手段,通过函数作用域解决各种命名冲突,污染全局作用域的问题
(function(data) {
console.log(t);
// 进行各种声明和操作
})(globlaData)
AMD 和 CMD
// AMD
defined('./a', './b', function(a, b) {
// 模块加载完成后可以使用
a.do();
b.do();
});
// CMD
defined(function(require, exports, module) {
// 加载模块,可以把require卸载任意地方,使用模块的延迟加载
let a = reqire('./a');
a.do();
})
CommonJS
commonJs最早是在node中使用,目前仍然使用广泛,我们在webpack中也会经常看到它。
// a.js
module.exports = {
name: 'commonJs'
}
// 或者
exports.name = 'commonJs'
// b.js
const module = require('./a.js');
console.log(module.name); // commonJs
commonJs的实现
const module = require('./a.js');
module.name;
// 这里其实包装了一层立即执行函数,防止对全局变量造成影响
// 这里的module是Node中的一个独有变量
module.exports = {
name: 'commonJs'
}
// module的实现
let module = {
id: 'XXX',
exports: {
name: 'commonJs'
}
}
// exports 和 module。exports共用一个内存地址,所以用法一样
let exports = module.exports;
let load = function(module) {
// 导出的内容
var name = 'commonJs';
module.exports = name;
return module.exports;
}
// 当require的时候,去找到独特的id对应的内容,然后将使用的内容用立即执行函数包装下。
exports 和 module.exports用法相同的原因是 共用了一个内存地址,所以不能直接对exports直接进行赋值,如果直接赋值会导致 和module.exports内存地址不一致,修改并不会修改module.exports的值。
ES Module
与CommonJS的区别
- CommonJS支持动态导入,例如
const module = require(path + '/a.js'),ES Module目前不支持 - CommonJS 是同步加载,因为用于服务端,文件就在本地,同步导入,即使卡住对主线程影响也不大。后者是异步导入,因为用于浏览器,需要下载文件,如果也才用同步导入会对渲染造成很大的影响
- CommonJS 导入都是值拷贝,就算导出的值改变了,前面导入的值也不会改变,如果需要更新,需要再重新导入。ES Module采用实时绑定的方式,导入导出的值都指向同一个内存地址,所以导入的值会随导出的值变化。
- ES Module会变编译成
reuire/exports来执行
// 导出模块儿
export function foo () {};
export default function() {};
// 导入模块
import {foo} from './a';
import foo from './a';
Proxy
vue3讲使用Proxy代码Object.defineProperty来实现响应式。Proxy是es6中新增的功能,可以用来定义对象。
let obj = new Proxy(target, handler);
target表示添加代理的对象,handler用来自定义对象中的操作,比如set和get函数
接下来用Proxy实现一个响应式
let onWatch = (obj, setBind, getLogger) => {
const handler = {
set(obj, property, value, receiver) {
console.log('receiver==', receiver);
setBind(property, value);
return Reflect.set(obj, property, value);
},
get(obj, property, receiver) {
getLogger(obj, property);
return Reflect.get(obj, property, receiver);
}
}
return new Proxy(obj, handler);
}
const obj = { age: 16 };
const p = onWatch(
obj,
(property, value) => {
console.log(`监听到${property}变更为${value}`);
},
(obj, property) => {
console.log(`${property} = ${obj[property]}`);
}
)
p.age // a = 16
p.age = 24 // 监听到age变更为24