本文仅对容易遗漏的知识点进行记录。
查看以下代码
// 片段1
var b =3;
function demo(){
b = 5; // ReferenceError ,即便 外部存在b
let b = 'hello';
}
demo();
//片段2
var b =3;
function demo2(){
b = 5; //没有报错
console.log(b);// 5
var b = 'hello';
console.log(b);// hello
}
demo2(); // 此时变量提升 ,依然不会修改全局中的b , window.b ===3;
/// 片段3
function demo3(){
a = 'hi';
console.log(a); // undefined;
var a = 9;
}
demo3();
可以理解为function的声明会被题升到AST的顶部,然后再执行其他定义
function demo(){
var b = ()=>{ return 1;}
function b(){
return 2;
}
return b();
}
demo();// 1
for(var i = 0;i<3;i++){
setTimeout(()=>console.log(i));
}
//333
for(let i = 0;i<3;i++){
setTimeout(()=>console.log(i));
}//012
const 只能保证引用地址的指针不变,而不能保证内部的值是否发生, 一般来说对象的比较是也就是地址的比较。
const a = {};
const b = a;
b.name = 'neyio';//可以随意操作内部内容,往往同学 以为只有let能进行操作,事实上只是保证地址不变而已。
console.log(a===b); // true
let x ='foo';
let y='bar';
([x,y] = [y,x]);//请注意这个括号,没有括号,就会报错噢
console.log(x,y);//bar foo
解构给予默认值在 写函数的时候十分常见,请注意第三个
fn
的输出,了解解构的玩法 同时注意 深层解构会自动放弃路径前缀
let [x = 3] = [];
console.log(x);//3;
//-----
let {name = 'neyio' }={ age:18 };
console.log(name);//neyio
//-----
const fn = ({name='neyio'}={name:'foo'})=>{
return name;
}
fn({age:18}); // neyio
fn();// foo;
// ---
const {a:{b:{c} }} = {a:{b:{c:"hello"},d:"world"}};
console.log(a)//ReferenceError 深层解构会自动放弃路径前缀
//---
let [a,b=a,c=b] = ['foo','bar'];
console.log(a,b,c); // foo bar bar
let {a:name} = {a:'neyio}; //name is neyio
const arr = [1,2,3];
const {0:first,[arr.length-1]:last} = arr;
// first is 1, last is 3
const {length} = arr;
//length is 3
对字符串进行数组解构,可以得到这正的字符长度,防止特殊字符占用码点,直接使用str.length可能获取的字符串长度过长
const [...str]= "hello";
console.log(str.length);//str is ['h','e','l','l','o'];
const {toString}= 3;
console.log(toString);//toString === Number.prototype.toString
注意:遍历map能保证map的key设置的顺序
const map = new Map();
map.set('foo',1);
map.set('bar',2);
for(let [key,value] of map){
console.log(key,value);
}
//foo 1
//bar 2
一般是使用 codePointAt(pos)就行,js不能正确识别 unicode码点大于 0xFFFFF的字符,和charCodeAt()一样会以为是两个字符。 此时
for(let i = 0;i<str.length;i++) str[i]//不能正确遍历,而 for(let i of str) i 可以
includes,startsWith,endsWith,repeat,padStart,padEnd
以及 字符串模板
//repeat
'hello'.repeat(2) // hellohello;`
//padStart
'name'.padStart(8,'aba');// abaaname
'name'.padEnd(8,'aba');// nameabaa
//使用方式
'2020'.padEnd(10,'-MM-DD')// 2020-MM-DD
const a = 'hello';
const b = `${p} world` // hello world;
const c = `支持换行
换行;
`;
const tag = (strsArray,...values) => {
}
tag`hello world ${1} ${2}`;
//等价于
tag(['hello world ',' '],1,2);
// 用法
i18n`welcome to ${position}`;
字符串对象具备4个方法,
match
,replace
,search
,split
可以使用正则表达式。 TODO://此处需要找一天全部整理一次
- 箭头函数内的this为定义时所在的对象,而不是使用时的对象。除非在箭头函数外层包裹一个函数,然后使用该函数的call|apply|bind的方式进行修改父级作用域,this则依然保障指向被修改后的父级函数。
- 不能作为构造函数,不能使用new命令
- 不可以使用arguments,不存在的
- 不能使用yield 即不能成为generator函数
- 返回对象时不带return的情况下,表达式需要
()
包裹对象const a = () => ({foo:'bar'});
;
const obj = {
name:'neyio',
getName:function(){
return ()=>{
return this.name;
};
}
}
const obj2 = { name:'foobar' };
obj.getName()(); //neyio;
obj.getName().call(obj2);// neyio;
obj.getName.call(obj2)();// foobar
function foo(){};foo.name // foo
;es5
和es6
差异 #### ** ES5 **javascript var f = function(){}; f.name //"" var bar = function foo(){}; bar.name // foo注意了!
#### ** ES6 **javascript const f = function(){}; f.name //"f" const bar = function foo(){}; bar.name // foo;注意了!
#### ** Bound **javascript const obj = {} function foo(){} foo.bind(obj); foo.name //bound foo
!> 注意了:本质是在对方对象上生成一个Symbol作为key,把当前的this挂载到对方对象上的Symbol上作为值,在执行完毕后,删除该key。其他都是鬼扯。
-
bind 生成一个绑定了第一个参数作为this指向对象的方法
-
call 直接触发绑定了第一个参数作为this指向对象的方法,其余作为该方法的参数入参
-
apply 同call,只是参数仅有一个。
-
实现一个bind函数
Function.prototype._bind = function(obj) {
var preThis = this;
var newFunction = function() {
return preThis.apply(obj, arguments); //注意arguments已经不被提倡使用了。
};
return newFunction;
};
const obj = {
name: 'neyio',
getName: function() {
return this.name;
}
};
const obj2 = { name: 'foobar' };
console.log(obj.getName._bind(obj2)()); //foobar
- 实现一个call函数
const obj2 = { name: 'foobar' };
Function.prototype._call = function(...args) {
const preThis = this;
const [ obj, ...rest ] = args;
var newFunction = function() {
return preThis.call(obj, ...rest); //注意arguments已经不被提倡使用了。
};
return newFunction();
};
const obj3 = {
name: 'neyio',
getName: function(suffix) {
return this.name + suffix;
}
};
console.log(obj3.getName._call(obj2, 'neyio'));//foobarneyio
- 然后你发现一个事实,我在耍无赖,你用的都是
Function
的原型方法本身,并没有进行自己手动实现啊,你实现apply和call了吗?你怎么能调用呢?
我痛恨这种喊着这个方法不能用,但是你必须要实现,你要掌握这三个方法,而不能用这种其他方法的诡辩。 如果需要,也许你需要实现一台x86的系统,或者像我的法布里斯·贝拉(FabriceBellard)一样,那我默不作声。
const obj = {
name: 'neyio',
getName: function(suffix) {
return this.name + suffix;
}
};
Function.prototype._apply = function(obj, args) {
obj = obj || window;
const symbol = Symbol('function');
obj[symbol] = this;
const result = obj[symbol](args);
delete obj[symbol];
return result;
};
console.log(obj.getName._apply(obj2, [ 'neyio' ]));
?> 我最烦心的事情就是面试造🚀的装逼大佬,问他们这些问题我几乎不能确定他们会不会,然而他们一定还是会说我只是为了了解你的基本功,那么手写bind和call和apply好像也不难吧. 一来是你不能篡改系统方法,二来是这个时代在进步,能不操作原型链就不操作原型链,操作带来的后果远大于一时半会儿的实现,然而在函数式编程逐渐流行的年代,为如何拆分运算和分布式运算应该是更加值得关注的事情,而不是关心this是啥,当然关心一下也略有裨益,起码你可以在面试新人的时候装逼。
const sum = (start, end, total = 0) => {
if (start > end) return total;
return sum(start + 1, end, total + start);
};
console.log(sum(1, 100));
const fabonacci = (current, gen, a, b) => {
if (current > gen) return a;
return fabonacci(current + 1, gen, b, a + b);
};
// 112358 13 21;
console.log(fabonacci(1, 1000, 1, 1));
const fabonacci2 = (gen, a, b) => {
if (gen <= 0) return a;
return fabonacci2(gen - 1, b, a + b);
};
// 112358 13 21;
console.log(fabonacci2(1000, 1, 1));
使用reduce实现reduceRight是一个复杂的工艺,隔段时间尝试都需要时间理解,一直找不到是哪一门学科能让我了解这个更多。 此处不再赘述
Array.from({length:30}).map((_,i)=>i);//生成0到29数组。
//等价于Array.from({length:30},(_,i)=>i);第二参数为类似map的方法
Array.from({0:'neyio',1:'18',length:2});// ['neyio','18']; 必须为key的toString能转换为数字,必须含有长度length属性
Array();// []
Array(3);//[ empty * 3]
Array(1,2,3);//[1,2,3]
Array(10).fill(10);//生成一个长度为10,值均为10 的数组。
// Array(10).fill(value,start,end) 不包括end
Array.of(1,2,3);//转换值为数组 [1,2,3] 等价于 [1,2,3];一般可以解构实现
Array.copyWithin(target,start,end = Array.length );//别看,无聊
[1,2,3,4,5].slice(-1);// 5
[1,2,3,4,5].slice(1,-1);//2,3,4
[1,2,3,4,5].slice(1,4);//2,3,4
[1,2,3,4,5].includes(3);
[1,2,3,4,5].filter(i => i>1);
[1,2,3,4,5].some(x => x===1);
let arr = [ 1, 1, 1, 2, 3 ];
//利用 set去重
const set = new Set(arr);
console.log((arr = Array.from(set))); // [1,2,3]
我们可以把ES的对象想象成一个散列表
,先看一个知识点丰富的示例,然后再逐步精进把,大量的OO的知识会在下方逐步讲到。
const NAME = Symbol('name');
const obj = {
[NAME]:'neyio',
get name(){
return this[NAME] + 'foobar';
},
set name(val){
this[NAME] += val;
}
}
console.log(obj.name);//neyiofoobar
obj.name = '1';
console.log(obj.name);//neyio1foobar
const obj2 = Object.assign(obj,name);
console.log(obj.name);//neyio1foobar
const obj3 = Object.assign(obj,{name:'what?'});//先执行了set然后执行neyio1what?foobar
console.log(obj3.name);//neyio1what?foobar
const obj4 = Object.assign(obj,{[NAME]:'resetAgain'});// Symbol依然会被拷贝 等价于 obj4 = {...obj,[NAME]:'resetAgain'}; obj5 ={...obj4};Symbol依然被解构进obj5了
console.log(obj4.name);//resetAgainfoobar
Object.is(NaN,NaN);//true NaN===NaN false 唯一自反值
Object.is('foo','foo');//true
Object.is({},{});//false {} === {} false
Object.is(+0,-0);//false 但是 +0===-0 true
const DEFUALTS = {
LOGIN:'/login',
REGISTER:'/register',
//...
}
const getApiMap = (map = {})=>{
return Object.assign({},DEFUALTS,map);
}
console.log(getApiMap({LOGIN:'/log-in'}));// {LOGIN: "/log-in", REGISTER: "/register"}
对象具有两种属性,一种是数据属性,一种是访问器属性。
4个配置项 [ 'value', 'writable', 'enumerable', 'configurable']
const obj = { name:'neyio' };
Object.getOwnPropertyDescriptor(obj,'name');
// {value: "neyio", writable: true, enumerable: true, configurable: true}
Object.defineProperty(obj,'getName',{
enumerable:true, // 是否可枚举,能否被 类似 for...in的遍历
value:function(){
return this.name;
},
configurable:true, // 是否可以删除或者修改属性,该属性一点变为false后,便无法再次将其变为true,直接锁死,一切想改变它的操作都是无效的。
writable:true // 是否可变更属性对应的值
})
console.log(obj.getName());//neyio
Object.freeze(obj); // 冻结所有属性,防止修改
obj.getName = function(){
return this.name+'foobar';
} //修改无效
console.log(obj.getName());//neyio
const obj = {_name:'neyio',modified:false};
Object.defineProperty(obj,'name',{
get:function(){
return this._name;
},
set:function(val){
if(val!==this._name){
this._name = val;
this.modified = true;
}
},
configurable:true,//同上
enumerable:true,//同上
});
obj.name = 'foobar';
console.log(obj.name);//foobar
console.log(obj);// { _name: "foobar", modified: true }
// Object.defineProperties 支持多个入参
Object.getOwnPropertyDescriptor(obj,'name');// {enumerable: true, configurable: true, get: ƒ, set: ƒ}
常规的LHS时,对象先读取本身的属性,如果不能获得,则读取原型链的值。
for ...in
不含不可枚举和Symbol,包含可枚举和继承的原型链属性Object.keys()
不含继承,不含Symbol,仅有可枚举属性Object.getOwnPropertyNames()
获取所有属性,仅不含SymbolObject.getOwnPropertySymbols()
获取所有Symbol 不含其他Reflect.ownKeys()
包含所有
!> 注意: Object.stringify(obj)
仅对自身可枚举的属性进行序列化
在接下来的内容开始前,我不得不进行对基础的补充,否则这生涩的东西是在恶心。大多数程序员都有oop的经验,但是原型真的是个异类。
此处不该提原型,但是我还是提了,毕竟面试了无数人(我挑人只是根据你的潜力和你的真诚),套路是上层建筑无所谓,底层(大部分机械面试官问的)呢?当然也不能理解成我对基础不重视,知晓和不知是两码事,会写原型和不会写原型是两码事,会不会面试答得出并不能获得多少印象分,只不过是短中拔长罢了,总有一天我也沦落于此。
- new操作符和工厂模式
下面先理解下new
,4个流程
- 创建一个对象,
- 将构造函数的作用域赋值给对象
- 执行构造函数代码
- 返回新对象
function createObject(name, age) {
const o = new Object();
o.name = name;
o.age = age;
return o;
}
const obj = createObject('neyio', 18);
console.log(obj); // {name:'neyio',age:18}
const Person = function(name, age) {
this.name = name;
this.age = age;
};
const person = Object.create(Person.prototype);
Person.call(person, 'neyio', 18);
console.log(person); // { name:'neyio', age:18 }
// 等价于
const person2 = new Person('neyio', 18);
console.log(person2); // { name:'neyio', age:18 } person3 instanceof Person true
// 等价于
const person3 = Object.create({});
Person.call(person3, 'neyio', 18);
console.log(person3); //此时只有数据,没有原型 { name:'neyio', age:18 } person3 instanceof Person false
Object.setPrototypeOf(person3, Person.prototype);
console.log(person3); // { name:'neyio', age:18 } person3 instanceof Person true
console.log(Object.getPrototypeOf(person3)===Person.prototype) //true
Object.setPrototypeOf(person3, Object.getPrototypeOf(person2));
console.log(person3); // { name:'neyio', age:18 } person3 instanceof Person true
- 原型模式
-
每一个对象(或者说函数,函数也是对象)都具备一个原型,无论是new操作符得到的还是,Object.create({})得到的。
-
在原型上增加东西,相当于拓展所有基于该原型对象作为原型的对象。存在风险。
-
每一个原型属性(prototype)都包含一个constructor执行构造方法。
-
创建一个对象,相当于把构造函数的原型对象给予新的对象的原型属性,并将函数的作用域赋给新对象(相当于this指向该对象),并执行构造函数的方法,并返回该对象。
-
原型链上请不要挂数组或者可以引用的修改值,
function Person(){
this.name = 'fresh bird neyio';//如果属性存在则覆盖原型链属性 //构造函数中的this,跟生成的新对象的地址是一致的,详见weakSet的时候的探索
}
Person.prototype.name = 'neyio';
Person.prototype._name = 'foobar';
Person.prototype.getName = function(){
return this.name;
}
const p1 = new Person();// Person {name: "fresh bird neyio"} p1._name// foobar
delete p1.name;
console.log(p1); // neyio
const p2 = new Person();// Person {name: "fresh bird neyio"} p1._name// foobar
// - - - - -
function Boy(){
this.name = 'shit';
}
// 一次性多个赋值
Boy.prototype={
name:'neyio',
getName:function(){
return this.name;
}
}
- 寄生构造模式
这种方式断开了原型联系
const Person = function(...cards){
let _cards = [].concat(cards||[]);
_cards.getNumbers = function(){
return this;
}
return _cards;
}
const p1 = new Person(1,2,3);
console.log(p1.getNumbers());//[1, 2, 3, getNumbers: ƒ] 特别不好
- 稳妥构造模式
这种方式断开了原型联系
const Person = function(name,age){
const obj = {name,age};
obj.getName = function(){
return this.name;
}
return obj;
}
const p1 = new Person('neyio',18);
console.log(p1.getName(),p1);// neyio , {name: "neyio", age: 18, getName: ƒ}
Javascript的继承实际上可以理解为原型链的继承。
const Super = function(){
this.name = 'neyiobaba';
}
Super.prototype.getName = function(){
return this.name;
}
const Sub = function(){
this.age = 18;
}
Sub.prototype = new Super();//此处创建了一个新的对象,并将 name,getName属性全部也赋值给了Sub.prototype
Sub.prototype.getAge = function(){
return this.age;
}
Sub.prototype.getParentName = function(){
return this.getName();
}
const child = new Sub();
console.log(child.getAge());//18
console.log(child.getParentName());//neyiobaba
console.log(child.getName());//neyiobaba
child.name = 'neyio';
console.log(child.getParentName());//neyio
console.log(child.getName());//neyio
console.log(child instanceof Super)//true
console.log(child instanceof Sub)//true
Array.prototype.isPrototypeOf([]);//true
Array.prototype.isPrototypeOf({});//false
Array.isArray([]);//true
const Super = function(){
this.name = 'neyiobaba';
}
Super.prototype.getName = function(){
return this.name;
}
const Sub = function(){
this.age = 18;
}
Sub.prototype = Super.prototype;// 此时丢失了name属性,而且会使得父亲一旦发生,儿子同时也会发生变化
Sub.prototype.getAge = function(){
return this.age;
}
Sub.prototype.getParentName = function(){
return this.getName();
}
const child = new Sub();
console.log(child.getAge());//18
console.log(child.getParentName());//undefined
console.log(child.getName());//undefined
child.name = 'neyio';
console.log(child.getParentName());//neyio
console.log(child.getName());//neyio
Super.prototype.getName = function(){
return this.name+' after modified';
}
console.log(child.getParentName());//neyio after modified
console.log(child.getName());//neyio after modified
用来解决上述原型链上的引用影响,缺点就是 instanceof 无法 探测父类的实例
const Super = function(name){
this.cards = [ 1, 2, 3, 4 ];
this.name = name;
}
const p1 = new Super('foobar');
const Sub = function(name){
Super.call(this,name);// 原因就是使得 this被 Super执行,this上挂上cards的值
}
const p2 = new Sub('neyio');
p2.cards.push(5);
console.log(p1.cards);//[ 1, 2, 3, 4 ]
console.log(p2.cards);//[ 1, 2, 3, 4 , 5 ]
console.log(p1.name);//foobar
console.log(p2.name);//neyio
console.log(p2 instanceof Sub);//true
console.log(p2 instanceof Super);//false
稍作修改
const Super = function(name){
this.cards = [ 1, 2, 3, 4 ];
this.name = name;
}
const p1 = new Super('foobar');
const Sub = function(name){
Super.call(this,name);// 原因就是使得 this被 Super执行,this上挂上cards的值
}
Sub.prototype = new Super();
Sub.prototype.constructor = Sub;
const p2 = new Sub('neyio');
p2.cards.push(5);
console.log(p1.cards);//[ 1, 2, 3, 4 ]
console.log(p2.cards);//[ 1, 2, 3, 4 , 5 ]
console.log(p1.name);//foobar
console.log(p2.name);//neyio
console.log(p2 instanceof Sub);//true
console.log(p2 instanceof Super);//true
person.hasOwnProperty('name')
'name' in person
Symbol方法的参数都是字符串,如果是其他值会调用拆箱toString。 Symbol函数声生成一个唯一值,属性值不冲突,此时参数为一个描述字符串,该字符串不能作为key进行理解,如果使用
Symbol.keyFor(一个非.for函数生成的symbol对象)
,则产生的值为undefined
。
而Symbol.for(key)
生成的值可以被复用,参数为类似散列表中的唯一key,你可以再次使用Symbol.for(key)
获得原值,如何再次获得该key,可以通过
Symbol.keyFor(s)
获取。
!> 注意了,Symbol.for()
会在全局中注册一个变量,你可以在任意位置获得它,而 Symbol()
不会.
const s = Symbol('neyio');
typeof s; // "symbol";
s.toString(); // "Symbol(neyio)";
const sp = Symbol.for('neyio'); //全局的变量
console.log(s === sp)// false 注意了!
const sp2 = Symbol.for('neyio');
console.log(sp2 === sp)//true
// -----
const sk = Symbol.for('neyio');//全局的变量
console.log(Symbol.keyFor(sk));//neyio
const obj = {name:'neyio'};
const obj2 = {name:'foobar'};
const sk2 = Symbol.for(obj);
const sk3 = Symbol.for(obj2);
console.log(Symbol.keyFor(sk2),Symbol.keyFor(sk3))// [object Object] 的字符串
console.log(sk2===sk3);//true 注意,别这样用,
// ---- 注意下方的示例
const sk4 = Symbol.for(1);
const sk5 = Symbol.for('1');
console.log(sk4===sk5);//true
- 基础使用
for...in
和object.keys()
均无法遍历,但是解构和Object.assign
依旧能继承。使用下方描述的获取遍历中的Object.getOwnPropertySymbols
;
const _uid = Symbol('s');
const person = {
[_uid]:'xxxx'
}
const person2 = {...person};
const person3 = Object.assign({},person);
for(let key in person){
console.log(key); // 不执行
}
console.log(Object.keys(person));//[]
console.log(person,person2,person3); // {Symbol(s): "xxxx"} * 3
- 获取遍历
Object.getOwnPropertySymbols();
- 隐藏属性值(
for ... in
和Object.keys()
无法遍历的特性) - 模块单例 //利用
Symbol.for
的全局性,依然 可以在操作过程中被修改,或者hack
//module.js
const MODULE_NAME = Symbol.for('neyio_module');
const g = window||global;
let module = null;
const ModuleConstructor = function(){
//...
}
if(!module){
module = new ModuleConstructor();
}
g[MODULE_NAME] = module;
export default g[MODULE_NAME];
稍作修改 const MODULE_NAME = Symbol.for('neyio_module')
修改为 const MODULE_NAME = Symbol('neyio_module')
;外部将很难修改,除非导出 MODUL_NAME
,再对g[MODUL_NAME]
做修改,主要是利用了无法访问到这个属性的特性。
- 作为过渡的变量使用,用后删除,防止覆盖,接下看一个apply的实现
Function.prototype._apply = function(obj,...args){
obj = obj||window||global;
const symbolFn = Symbol('fn');
obj[symbolFn] = this;
const ans = obj[symbolFn](...args);
delete obj[symbolFn];
return ans;
}
const obj ={name:"neyio"};
const obj2 = {getName(suffix=''){return `${this.name}+${suffix}`;}};
obj2.getName._apply(obj,'suffix');//neyio+suffix
- 作为Symbol作为私有key
const foo = Symbol('foo');
class Person{
constructor(){
this.name = 'neyio';
}
[foo](){
return this.name;
}
bar(){
return this[foo]();
}
}
只介绍一个吧Symbol.hasInstance
class MyClass {
[Symbol.hasInstance](foo){
return foo instanceof Array;
}
}
[1,2,3] instanceof MyClass
?> Set 是一种 key和value一致的数据结构,values,keys,返回结果相同entries返回一个两个相同数据的元组
不想用Set的退路,但是一定要拥抱时代变化,以及认清使用场景
const arr = Array.from(new Set([1,2,3]));
const set = new Set(arr);
console.log(arr);
console.log(set);//Set(3) {1, 2, 3}
const arr2 = [...set];
console.log(arr2);// [1,2,3]
const set = new Set([1,2,3]);
set.add('neyio');
for(let i of set){
console.log(i);
}// 1,2,3,neyio
for(let i in set){
console.log(i) ;//不会执行
}
- 获取长度
[size]
- 增加成员
[add]
- 删除成员
[delete]
- 判断成员
[has]
- clear清空
const set = new Set([1,2,3]);
set.add(2);
console.log(set);// Set(3) {1, 2, 3}//保证了唯一性
set.delete(2);
console.log(set);// Set(2) {1, 3}//
set.has(2);//false
//forEach第二参数用于改变this绑定
const set = new Set([1,2,3])
set.forEach((value,key)=>{
console.log(value,key);
});
// 1 1
// 2 2
// 3 3
const obj = {
name:'neyio'
}
set.forEach((value,key)=>{
console.log(this);// window,注意不要使用箭头函数,否则无法变更this绑定
},obj);
set.forEach(function(value,key){
console.log(this);// {name: "neyio"}
},obj);
const duplicateRemoval = (arr)=>{
return Array.from(new Set(arr));
}
const union = (arr1,arr2)=>{
return Array.from(new Set([...arr1,...arr2]));
}
union([1,2,3],[1,2,3,4]);// [1, 2, 3, 4]
const intersect = (arr1,arr2)=>{
const set = new Set(arr2);
return arr1.filter(i=>set.has(i));
}
intersect([1,2,3],[2]);//[2]
const difference = (arr1,arr2)=>{
const set = new Set(arr2);
return arr1.filter(i=>!set.has(i));
}
difference([1,2,3],[2]);//[1,3]
!> WeakSet 的 key 只能是 object(不含null),不能是Symbol, WeakSet没有size,没有遍历器, 其余跟set用法相似,然而最大的差别是在于他的key指向的对象内存地址一旦被回收,不管weakSet是否还存在,一样都会被强制回收 。
有且仅有三个方法 add
,has
,delete
,访问 size,forEach
均为undifined
const ws = new WeakSet([]);
const Person = function(){
ws.add(this);
this.name = 'neyio';//构造函数中的this,跟生成的新对象的地址是一致的
}
const a = new Person();
ws.has(a);//true //构造函数中的this,跟生成的新对象的地址是一致的
// ws.add(null);//报错
//ws.add(Symbol('neyio'));//报错
!> Object的key只能是字符串,而实际上对象应该更倾向于hash键值对。
cont m = new Map();
const map = new Map([
['key','value'],
[m,'Everything is ok!']
])
console.log(map);//Map(2) {"key" => "value", Map(0) => "anything is ok!"}
console.log(map.get(m));//Everything is ok!
const map = new Map([]);
map.set('name','neyio');
map.set('age','18');
map.set('age','18');
map.size;// 2
const others = {foo:"bar"};
map.set(others,"foobar");
const getName = function(){
return this.name;
}
map.set(getName,'haha');
map.get(getName);// haha
map.get(others);// foobar
map.has(others);// true
map.delete(others);//true
map.has(others);//false
map.size;// 3
map.clear();//清空hash表
- keys 获取 key 迭代器
- values 获取 value 迭代器
- entries 获取 [key,value] 迭代器
- forEach 遍历方法,获取内容同上entries [key,value]
const map = new Map([
['k1','v1'],
['k2','v2']
]);
const obj = {
name:'neyio',
getName:function(suffix=""){
return `${this.name}+${suffix}`;
}
}
map.forEach(function(value,key){
console.log(this.getName(value),this.name,key,value);
//不要使用箭头函数
},obj);
// neyio+v1 neyio k1 v1
// neyio+v2 neyio k2 v2
等同于数组的转化
const set = new Set([
['k1','v1'],
['k2','v2']
])
const map = new Map(set);
console.log(map);
console.log(map.get(k1),map.get(k2));
同weakSet的要求和使用方式
!> 对dom操作不熟悉,本代码可能存在问题
const wm = new WeakMap([]);
const foo = document.getElementById('foo');
const bar = document.getElementById('bar');
wm.set(foo,'foo');
wm.set(bar,'bar');
document.removeChild(foo);
document.removeChild(bar);
我知道这是一个好玩的东西,但是因为不求甚解,在实战项目中用的也很少,足以证明我是一个彩笔,但是今天的面试官动不动就说,你会Vue的代码把,那你实现一下MVVM(尤大笑晕在厕所),那你说下他是怎么实现的。原理你总知道把,知道,getter,setter...
Proxy主要是用来修改某些操作的默认行为,从这个角度来讲,你可以把他理解成一个拦截器。想要访问对象,都要经过这层拦截。那么我们就可以在这层拦截上做各种操作了。比如你设置一个对象的值的时候,对对象的值进行校验等。 注意这一句如果一个属性不可配置(configurable)和不可写(writable),则该属性不能被代理,通过 Proxy 对象访问该属性会报错。
get(target, propKey, receiver)
:拦截对象属性的读取,比如proxy.foo
和proxy['foo']
。如果一个属性不可配置(configurable)和不可写(writable),则该属性不能被代理,通过 Proxy 对象访问该属性会报错。set(target, propKey, value, receiver)
:set方法的第四个参数receiver,总是返回this关键字所指向的那个对象,即proxy实例本身。代表拦截对象属性的设置,比如proxy.foo = v或proxy['foo'] = v,返回一个布尔值。has(target, propKey)
:拦截propKey in proxy
的操作,返回一个布尔值。值得注意的是,has方法拦截的是HasProperty
操作,而不是HasOwnProperty
操作,即has方法不判断一个属性是对象自身的属性,还是继承的属性。deleteProperty(target, propKey)
:拦截delete proxy[propKey]
的操作,返回一个布尔值。ownKeys(target)
:拦截Object.getOwnPropertyNames(proxy)
、Object.getOwnPropertySymbols(proxy)
、Object.keys(proxy)
,返回一个数组。该方法返回目标对象所有自身的属性的属性名,而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)
,返回一个布尔值。这个方法有一个强限制,它的返回值必须与目标对象的isExtensible属性保持一致,否则就会抛出错误。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)
。
const person = {
name: 'neyio'
};
const proxy = new Proxy(person, {
get: function(target, property) {
if (property in target) {
// 等价于 target.hasOwnproperty(property)
return target[property];
} else {
throw new Error(`${property} does not exsist;`);
}
}
});
console.log(proxy.name); //neyio
console.log(proxy.age); //Error: age does not exsist;
const person = {
name: 'neyio'
};
const proxy = new Proxy(person, {
get: function(target, property) {
if (property in target) {
return target[property];
} else {
throw new Error(`${property} does not exsist;`);
}
}
});
const person2 = Object.create(proxy);
try {
//console.log(person2);//不要进行输出,否则会报 Cannot convert a Symbol value to a string
console.log(person2.age); //throw error 进行catch处理
console.log(person2.name); //继续执行 输出neyio
} catch (e) {
console.log(e.message); // age does not exsist;
}
// ---- 拦截数组负数索引
const createArray = (...arr) => {
const target = [ ...arr ];
const handler = {
get: function(target, property, receiver) {
let index = Number(property);
if (!Number.isInteger(index)) {
throw new Error('');
}
if (index < 0) {
index = target.length + index;
}
return Reflect.get(target, index, receiver);
}
};
return new Proxy(target, handler);
};
const arrProxy = createArray(1, 2, 3);
console.log(arrProxy[1], arrProxy[-1]); //2 3
const actionsFns = {
double: (x) => x * 2,
plusOne: (x) => x + 1,
minusOne: (x) => x - 1
};
const pipProxy = (actions = actionsFns) => {
const fns = [];
const handler = {
get: function(target, property, receiver) {
if (property !== 'get') {
if (actions.hasOwnProperty(property)) {
fns.push(actions[property]);
return receiver;
} else {
throw new Error(`action function ${property} does not exsist!`);
}
} else {
return (initial) =>
fns.reduce((acc, current) => {
return current(acc);
}, initial);
}
}
};
return new Proxy(fns, handler);
};
const ans = pipProxy().plusOne.double.minusOne.get(1);
console.log(ans); //3
// ---- 生成 _DOM 树
const domCreator = () => {
const handler = {
get: function(target, property, receiver) {
return (attributes = {}, ...children) => {
const el = document.createElement(property);
Object.entries(attributes).map(([ key, value ]) => {
el.setAttribute(key, value);
});
children.map((child) => {
el.appendChild(typeof child === 'string' ? document.createTextNode(child) : child);
});
return el;
};
}
};
return new Proxy({}, handler);
};
const _DOM = domCreator();
const domEl = _DOM.div(
{ style: `background:#000;color:#fff;z-index:10001;` },
_DOM.a({ href: 'http://neyio.cn' }, `neyio's site`),
_DOM.ul(
{},
_DOM.li({}, '这是第一行'),
_DOM.li({}, '这是第二行'),
_DOM.li({}, '这是第三行'),
_DOM.li({}, '这是第四行'),
_DOM.h1({}, '标题')
)
);
document.body.appendChild(domEl);
const Validator = () => {
const handler = {
set: function(target, key, value, receiver) {
if (key === 'name' && value !== 'neyio') {
throw new Error('name must be neyio');
}
target[key] = value;
}
};
return new Proxy({}, handler);
};
const user = Validator();
try {
user.name = 'neyio';
console.log(user.name);
user.name = 'laoqian';
} catch (e) {
console.log(e.message); //Error: name must be neyio
}
// 如果 target 是一个函数,调用 proxy包裹的target会被 handler的apply捕获 // ctx是apply对象和call入参的第一参数,如果直接调用方法ctx为undefined
const fnApply = (fn) => {
const handler = {
apply: function(target, ctx, args) {
console.log(ctx && ctx.name); //此处的ctx是apply对象和call触发的,如果直接调用方法ctx为undefined
return target(...args);
}
};
return new Proxy(fn, handler);
};
const wrappedFn = fnApply((a, b) => {
return a + b;
});
console.log(wrappedFn(1, 2)); // 3;
const fnPerson = {
name: 'neyio'
};
const fnAns = wrappedFn.call(fnPerson, 1, 2);
console.log('TCL: fnAns', fnAns);
判断key是否存在
//...
const handler ={
has:function(target,key){
return key in target;
}
}
//...
let _proxyConstructAddress = null;
const constructProxy = new Proxy(function() {}, {
construct: function(target, args, newTarget) {
_proxyConstructAddress = newTarget; //等同于 constructProxy
target = { ...args };
target.name = 'neyio';
return target;
}
});
const _constructProxyDemo = new constructProxy(1, 2);
console.log(_constructProxyDemo); //{ '0': 1, '1': 2, name: 'neyio' }
console.log(_proxyConstructAddress === constructProxy); // 代理后的构造器的地址 true
// revocable
let target = {
foo: 'bar'
};
let handler = {
get: function() {
return 'neyio';
}
};
let { proxy: proxyRevocable, revoke } = Proxy.revocable(target, handler);
console.log('TCL: proxy.foo before', proxyRevocable.foo); //TCL: proxy.foo before neyio
revoke();
try {
console.log('TCL: proxy.foo end', proxyRevocable.foo); // Error:revoked. 结束代理后,拒绝访问目标对象
} catch (e) {
console.log(e.message); // Cannot perform 'get' on a proxy that has been revoked
defineProperty
,deleteProperty
,defineProperty
,getOwnPropertyDescriptor
,getPrototypeOf
,isExtensible
,ownKeys
,preventExtensions
,setPrototypeOf
!> 注意this指向
自行查阅 https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Reflect
Promise的三种 状态 分别为 pending,fulfilled,rejected; Promise.all 和 Promise.race 前者所有resolve则resolve,后者第一个到达即resolve Promise.resolve 生成Promise并触发resolve状态改变fulfilled,Promise.reject用于触发一个rejected状态转变
const pm = new Promise(function(resolve, reject) {
setTimeout(() => {
resolve({ name: 'neyio' });
}, 3000);
});
pm.then((res) => {
console.log(res); // { name: 'neyio' } 第一次等3s
});
pm.then((res) => {
console.log(res); // { name: 'neyio' } 第二次微观队列直接操作
});
const setTimeoutWrapper = (fn, timeout, ...args) => {
return new Promise((resolve, reject) => {
setTimeout((...args) => resolve(fn(...args)), timeout, ...args);
});
};
const sum = (a, b) => a + b;
setTimeoutWrapper(sum, 1000, 1, 2).then((res) => {
console.log(res); // 1秒后执行
});
// Promise 的 微观队列和宏观队列
console.log('start'); // 同步方法 时序1
setTimeout(() => {
console.log(1); // 宏观队列[0] 时序2
});
new Promise(function(resolve) {
console.log(2); // 同步方法 2
resolve(); // 微观队列[0] 时序3
console.log(3); //同步方法 4
}).then(() => {
console.log(4);
});
Promise.resolve(true).then((res) => {
console.log(5); //微观队列[1] 时序4
});
setTimeout(() => {
console.log('6'); // 宏观队列[1]
});
setTimeout(() => {
console.log('7'); // 宏观队列[2]
});
console.log('end'); // 同步方法
//start 2 3 end 4 5 1 6 7
遍历器,把它想象成一个generator对象。要适用于被·for ... of 循环调用
function makeIterator(arr){
let index = 0;
return {
next:function(){
return index< arr.length? {
value:arr[index++],done:false
}:{
value:undefined,
done:true
}
}
}
}
const it = makeIterator([1,2,3]);
it.next().value;//1
it.next().value;//2
it.next().value;//3
const obj = {
[Symbol.iterator]:function(){
return {
next:function(){
return {
value:1,
done:true
}
}
}
}
}
let arr = ['a','b','c'];
let it = arr[Symbol.iterator]();
it.next().value;// a
it.next().value;// b
it.next().value;// c
本文只介绍static 和super
class Square extends Polygon {
constructor(length) {
// 在这里, 它调用了父类的构造函数, 并将 lengths 提供给 Polygon 的"width"和"height"
super(length, length);
// 注意: 在派生类中, 必须先调用 super() 才能使用 "this"。
// 忽略这个,将会导致一个引用错误。
this.name = 'Square';
}
get area() {
return this.height * this.width;
}
set area(value) {
// 注意:不可使用 this.area = value
// 否则会导致循环call setter方法导致爆栈
this._area = value;
}
}
// ----
class Polygon {
constructor() {
this.name = 'Polygon';
}
}
class Square extends Polygon {
constructor() {
super();
}
}
class Rectangle {}
Object.setPrototypeOf(Square.prototype, Rectangle.prototype); //这里,Square类的原型被改变,但是在正在创建一个新的正方形实例时,仍然调用前一个基类Polygon的构造函数。
console.log(Object.getPrototypeOf(Square.prototype) === Polygon.prototype); //false
console.log(Object.getPrototypeOf(Square.prototype) === Rectangle.prototype); //true
let newInstance = new Square();
console.log(newInstance.name); //Polygon
// 静态方法
class Tripple {
static tripple(n = 1) {
return n * 3;
}
}
class BiggerTripple extends Tripple {
static tripple(n) {
return super.tripple(n) * super.tripple(n);
}
}
console.log(Tripple.tripple()); // 3
console.log(Tripple.tripple(6)); // 18
let tp = new Tripple();
console.log(BiggerTripple.tripple(3)); // 81(不会受父类实例化的影响)
console.log(tp.tripple()); // 'tp.tripple 不是一个函数'.
import 只能在模块顶层使用
import { A ,B ,C} from 'module.js';
export const A ;
export { A };
export { A as APro };
export default any[ function , var, object ... ];
import _,{ A , B ,C } from 'module.js';
export { A , B} from 'module.js';
export * from 'module.js';
export * as moduleB from 'module.js';
ES希望模块和模块组织结构尽可能是静态化,所以希望通过远端加载CDN地址的行为是不可取的,但是你可以在webpack中external某个模块,例如vue.js,然后再在index.html引入CDN,来优化性能。
import('module.js').then(module=>{
...
})
//可以直接远端引入
import {A} from 'https://xxx.js' //未考证
<script type="module" scr="module.js"></script>
等同于
<script type="module" scr="module.js" defer></script>
如果增加 async 标签,则当加载完成后,会阻塞渲染再立即执行脚本,然后再继续渲染。
<script type="module" src="module.js" defer async></script>
ES 模块支持内嵌
<script type="module">
import moduleA from 'module.js';
...
</script>
CommonJS导出的是值的复制
let a = null;
export default function(){
if(!a){
a = 'module inited';
return a;
}
return a;
};