模块化
- CommonJS规范:它是Node.js中所遵循的模块规范,该规范约定,一个文件就是一个模块,每个模块都有单独的作用域,通过module.exports导出成员,再通过require函数载入模块。
缺点:CommonJS约定的是以同步的方式加载模块,因为Node.js执行机制是在启动时加载模块,执行过程中只是使用模块,所以这种方式不会有问题。但是如果浏览器使用同步的加载模式,会引起大量同步模式请求,导致应用运行效率地下。
- AMD(Asynchronous Module Definition)规范:异步模块定义规范。有一个出名的库Require.js。社区提出的规范。
缺点:目前绝大多数第三方库都支持 AMD 规范,但是它使用起来相对复杂,而且当项目中模块划分过于细致时,就会出现同一个页面对 js 文件的请求次数过多的情况,从而导致效率降低。
- ES Modules:ECMAScript 2015(ES6)中定义的模块系统,逐渐被浏览器实现。
CommonJS
CommonJS规范:它是Node.js中所遵循的模块规范,该规范约定,一个文件就是一个模块,每个模块都有单独的作用域,通过module.exports导出成员,再通过require函数载入模块。
exports module.export
exports 只是 module.exports的引用,辅助后者添加内容用的。这等同在每个模块头部,有一行这样的命令:
var exports = module.exports;
好多建议尽量都用 module.exports 导出,然后用require导入。 www.cnblogs.com/shichangchu…
ES Modules
export
-
export与export default均可用于导出常量、函数、文件、模块等
-
在一个文件或模块中,export 、import可以有多个,export default仅有一个
-
通过export方式导出,在导入时要加{ },export default则不需要
-
export能直接导出变量表达式,export default不行 juejin.cn/post/684490…
Symbol
请回答一下JavaScript的数据类型有哪些呢?
基本数据类型:undefined、boolean、string、number、null、Symbol、bigint(7种)
引用数据类型:Object(Array、Function、Date、对象)
基本数据类型存储在栈内存中;
引用数据类型保存在堆内存中。
Symbol是一种基本数据类型。
第一个作用是作为属性名避免属性名冲突;
第二个作用是替代代码中多次使用的字符串(例如:abc),多次使用的字符串在代码中不易维护,而这时候定义一个对象的属性(属性名用Symbol格式),值为abc,就可以作为全局变量来使用了。
第三个,由于以Symbol值作为名称的属性,不会被常规方法遍历得到。我们可以利用这个特性,为对象定义一些非私有的、但又希望只用于内部的方法。
第四个,这个有时,我们希望重新使用同一个Symbol值,Symbol.for方法可以做到这一点。它接受一个字符串作为参数,然后搜索有没有以该参数作为名称的Symbol值。如果有,就返回这个Symbol值,否则就新建并返回一个以该字符串为名称的Symbol值。
zhuanlan.zhihu.com/p/22652486
定义和使用
在对象中使用的三种方式:
let mySymbol = Symbol();
//第一种
let a1 = {};
a1[mySymbol] = 'hello';
//第二种
let a2 = {
[mySymbol]: 'javascript'
};
//第三种
let a3 = {};
Object.defineProperty(a3, mySymbol, {value: 'good'});
console.log(a1[mySymbol], a2[mySymbol], a3[mySymbol] )
- 使用 Symbol 值定义属性时,Symbol 值必须放在方括号之中。
- Symbol 作为属性名,遍历对象的时候,该属性不会出现在for...in、for...of循环中,也不会被Object.keys()、Object.getOwnPropertyNames()、JSON.stringify()返回。
- 可以通过 Object.getOwnPropertySymbols(obj) 获取 Symbol 属性名,(不太实用)
- 新的 API,Reflect.ownKeys()方法可以返回所有类型的键名,包括常规键名和 Symbol 键名。
let mySymbol = Symbol('motto');
let person = {
name: 'zs',
age: 12,
[mySymbol]: 'less code'
}
console.log(Object.getOwnPropertySymbols(person));
for(let key of Reflect.ownKeys(person)){
console.log(key);
}
Symbol.for()
Symbol.for()与Symbol()这两种写法,都会生成新的 Symbol。它们的区别是,前者会被登记在全局环境中供搜索,后者不会。Symbol.for()不会每次调用就返回一个新的 Symbol 类型的值,而是会先检查给定的key是否已经存在,如果不存在才会新建一个值。比如,如果你调用Symbol.for("cat")30 次,每次都会返回同一个 Symbol 值,但是调用Symbol("cat")30 次,会返回 30 个不同的 Symbol 值。
Map和Object区别
定义
//定义
let map1 = new Map();
let map2 = new Map([['name', 'zs'], ['age', 14]])
let obj = {
name: 'zs',
age: '44'
};
console.log(map1 instanceof Map, map1); // true
console.log(typeof map1); //object
key值的不同
我们都知道object类型的key只能是字符串、Symbol或者数字(其实也被转为字符串了);
Map的key可以是基本类型以及引用类型。
元素顺序
Map 元素的顺序遵循插入的顺序,而 Object 的则没有这一特性。
数据访问 get/has VS . / in
console.log(map2.get('name'), map2.get('name2')); //zs undefined
console.log(obj.name, obj.name2); //zs undefined
console.log(map2.has('name')); //true
console.log('name' in obj); //true
新增一个数据 set
Map和Object如果key相同都会覆盖。
map2.set('gender', 'male');
obj.gender = 'male';
删除数据 delete clear
map2.delete('gender');
delete obj.gender;
obj.address = 'shanghai';
obj.address = undefined;
console.log(map2, obj)
需要注意的是,使用 delete 会真正的将属性从对象中删除,而使用赋值 undefined 的方式,仅仅是值变成了 undefined。属性仍然在对象上,也就意味着 在使用 for … in… 去遍历的时候,仍然会访问到该属性。
获取size size VS Object.keys(obj).length
console.log(map2.size, Object.keys(obj).length);
Iterating
具有 [Symbol.iterator] 属性的数据类型才可以进行for...of循环;
object本身没有 [Symbol.iterator] 属性,所以for...of 会报错!
原生具备 Iterator 接口的数据结构如下:
Map
Array
String
Set
函数arguments对象
TypedArray
NodeList对象
话锋一转,怎么循环遍历对象呢? for...in
for(let t in obj){
console.log(t); // name age address
}
var let const区别
区别1:var定义的变量如果不是在函数体里面,那么会挂载在window上,成为它的属性; let const定义的变量即使不在函数体里面,也不会挂载在window上面:
var name='xyy';
let age = 29;
console.log(window.name, window.age); //xyy undefined
为什么呢?
因为let const定义的变量有块级作用域的作用,let age = 29; 相当于:
(function(){
let age = 29;
})()
区别2:var定义的变量有变量声明提升的效果,const/let定义的没有
console.log(a); //undefined
var a = 100;
console.log(b); //报错 Uncaught ReferenceError: Cannot access 'b' before initialization
let b = 100;
Proxy 和 Reflect
Proxy 对象用于创建一个对象的代理,从而实现基本操作的拦截和自定义(如属性查找、赋值、枚举、函数调用等)
const proxy = new Proxy(target, handler)
- target 要使用 Proxy 包装的目标对象(可以是任何类型的对象,包括原生数组、函数,甚至另一个代理)
- handler 一个通常以函数作为属性的对象,各属性中的函数分别定义了在执行各种操作时的代理 p 的行为。
- Prxoy.revocable() 方法可以创建一个可撤销的Proxy对象。 对于Proxy而言,重点是第二个参数 handler:
get(target, propKey, receiver){}拦截对象属性的读取,如 p.foo 和 p['foo'];receiver是由proxy对象提供的,所以receiver总是指向proxy对象,即receiver === proxyset(target, propKey, value, receiver){}拦截对象属性的设置,比如 proxy.foo = v 或者 proxy[foo] = v; 返回一个布尔值。has(target, propkey){}拦截 propKey in proxy 的操作,返回一个布尔值。deleteProperty(target, propKey){}拦截 delete proxy[propKey] 的操作,返回一个布尔值。如果这个方法抛出错误或者返回false,当前属性就无法被 delete 命令删除。ownKeys(target){}拦截 Object.getOwnPropertyNames(proxy)、Object.getOwnPropertySymbols(proxy)、Object.keys(proxy)、for...in循环,返回一个数组。该方法返回目标对象所有自身属性的属性名,而 Object.keys() 返回结果仅包括目标对象自身的可遍历属性。getOwnPropertyDescriptor(target, propKey)拦截Object.getOwnPropertyDescriptor(proxy, propKey),返回属性的描述对象。defineProperty(target, propKey, propDesc):拦截Object.defineProperty(proxy, propKey, propDesc)、Object.defineProperties(proxy, propDescs),返回一个布尔值。preventExtensions(target):拦截Object.preventExtensions(proxy),返回一个布尔值。getPrototypeOf(target):拦截Object.getPrototypeOf(proxy),返回一个对象。isExtensible(target):拦截Object.isExtensible(proxy),返回一个布尔值。setPrototypeOf(target, proto):拦截Object.setPrototypeOf(proxy, proto),返回一个布尔值。如果目标对象是函数,那么还有两种额外操作可以拦截。apply(target, object, args):拦截 Proxy 实例作为函数调用的操作,比如proxy(...args)、proxy.call(object, ...args)、proxy.apply(...)。construct(target, args):拦截 Proxy 实例作为构造函数调用的操作,比如new proxy(...args)。 例子:赋值验证
let validator = {
set: function(obj, prop, value){
if(pro === 'age'){
if(!Number.isInteger(value)){
throw new TypeError('The age is not an integer');
}
if(value>200){
throw new RangeError('The age seems invalid');
}
}
obj[prop] = value;
return true;
}
}
let person = {name: 'Max'};
let proxy = new Proxy(person, validator);
person = 300; // ok
proxy.age = 'young';
// 抛出异常: Uncaught TypeError: The age is not an integer
proxy.age = 300;
// 抛出异常: Uncaught RangeError: The age seems invalid
console.log(typeof proxy); //object
- 真正被代理的是
proxy对象,而不是目标对象 person,这一点不要搞混; set(target, propKey, value, receiver){}这里的 target === person; recerver === proxy- person 对象修改的时候会同步反应在 proxy 对象上;反过来亦是如此。
person.name = 'William';
proxy.job = 'actor';
console.log(person, proxy);
Reflect 是一个内置对象,它提供了拦截 JavaScript 操作的方法。这些方法与 proxy handlers 的方法相同。 Reflect 不是一个构造对象,因此它是不可构造的。
静态方法:
Reflect.apply(target, thisArgument, argumentsList)对一个函数进行调用操作,同时可以传入一个数组作为调用参数。和 Function.prototype.apply() 功能类似。Reflect.construct(target, argumentsList[, newTarget])对构造函数进行 new 操作,相当于执行 new target(...args)Reflect.defineProperty(target, propertyKey, attributes)和 Object.defineProperty()类似。如果设置成功就会返回 trueReflect.deleteProperty(target, propertyKey)作为函数的delete操作符,相当于 delete target[name]Reflect.get(target, propertyKey[, receiver])获取对象身上某个属性的值,类似于 target[name]Reflect.getOwnPropertyDescriptor(target, propertyKey)类似于 Object.getOwnPropertyDescriptor()。如果对象中存在该属性,则返回对应的属性描述符, 否则返回 undefined.Reflect.getPrototypeOf(target)类似于 Object.getPrototypeOf()。Reflect.has(target, propertyKey)判断一个对象是否存在某个属性,和 in 运算符 的功能完全相同。Reflect.isExtensible(target)类似于 Object.isExtensible().Reflect.ownKeys(target)返回一个包含所有自身属性(不包含继承属性)的数组。(类似于 Object.keys(), 但不会受enumerable影响).Reflect.preventExtensions(target)类似于 Object.preventExtensions()。返回一个Boolean。Reflect.set(target, propertyKey, value[, receiver])将值分配给属性的函数。返回一个Boolean,如果更新成功,则返回true。Reflect.setPrototypeOf(target, prototype)设置对象原型的函数. 返回一个 Boolean, 如果更新成功,则返回true。
箭头函数
www.jianshu.com/p/a416cb02e…
juejin.cn/post/684490…
www.jianshu.com/p/31ac0f628…
juejin.cn/post/686473…
www.babeljs.cn/
我来总结一下:
- 箭头函数不能用于构造函数,即不能用 new 关键字调用;
- 箭头函数没有prototype属性,我们知道构造函数的prototype属性指向原型对象,箭头函数没有prototype是更纯粹的函数;
- 箭头函数没有argeuments; 可以使用(...rest)=>{}解决参数不定长的问题;
- 箭头函数不能绑定this,不能改变其this指向;
普通函数 this
const printNumbers = {
phrase: 'The current value is:',
numbers: [1, 2, 3, 4],
loop() {
this.numbers.forEach(function (number) {
console.log(this.phrase, number)
})
},
}
普通函数并不是通过上下文作用域来决定this的值,而是通过实际调用函数的对象来决定的。
箭头函数 this
- 箭头函数的外层如果有普通函数,那么箭头函数的 this 就是外层普通函数的this
- 箭头函数的外层如果没有普通函数,那么箭头函数的 this 就是全局变量
**箭头函数不能绑定this,箭头函数的this在定义的时候就确定了。
不能通过apply call bind 改变其this指向 **
对于上面这句话的理解:
window.color = "red";
let color = "green";
// var color = "black";
let obj = {
color: "blue"
};
let sayColor = () => {
return this.color;
};
console.log(sayColor.apply(obj));
sayColor是一个箭头函数,applay是不起作用的,所以函数里面的this没有被改变。这里箭头函数里面的this按照定义的时候this的指向的。定义sayColor的时候 this 指向全局window。所以 this.color指的是window.color。同时还有一个知识点就是 let const定义的变量并不会加载到 window 上面的。所以 let color并不会改变 window.color的值。
没有变量指向匿名函数的执行环境具有全局性,因此其 this 对象通常指向 window
什么是没有变量指向的匿名函数?比如:
setTimeout(function(){}, 100)
arr.forEach(function(){})
function(){ return function(){} }
里面的函数没有变量指向它们,这些函数执行的时候执行环境具有全局性,因此其 this 对象通常指向 window。
但是 下面的代码中 obj.getNameFunc 指向了后面的匿名函数,所以obj.getNameFunc执行里面this 指向obj。箭头函数的出现解决了这一弊端。
var name = "The window";
var object = {
name: 'My Object',
getNameFunc: function () {
console.log(this);
return function () {
return this.name;
}
},
getName: function () {
return ()=>{
return this.name;
}
}
}
console.log(object.getNameFunc()());
console.log(object.getName()());
console.log(object.getNameFunc()()); 执行输出 "The window"
console.log(object.getName()()); 执行输出 "My Object"
为什么呢?object.getNameFunc()返回的是一个匿名函数,执行环境具有全局性,所以this指向window
箭头函数体内的 this 对象就是定义时所在的对象,而不是使用时所在的对象。