是时候撸一波 JS 基础啦,撸熟了,银十速拿 offer;
本文不从传统的问答方式梳理,而是从知识维度梳理,以便造成知识网络
包括函数,数组,对象,数据结构,算法,设计模式和 http.
//ES5 function getSum(){} function (){}//匿名函数 //ES6 ()=>{}//若是{}内容只有一行{}和return关键字可省,
//ES5 var sum=function(){} //ES6 let sum=()=>{}//若是{}内容只有一行{}和return关键字可省,
const sum = new Function('a', 'b' , 'return a + b')
1.函数声明有预解析,并且函数声明的优先级高于变量;
2.使用Function构造函数定义函数的方式是一个函数表达式,这种方式会致使解析两次代码,影响性能。第一次解析常规的JavaScript代码,第二次解析传入构造函数的字符串html
在ES5中函数内容的this指向和调用方法有关vue
包括函数名()和匿名函数调用,this指向windownode
function getSum() { console.log(this) //window } getSum() (function() { console.log(this) //window })() var getSum=function() { console.log(this) //window } getSum()
对象.方法名(),this指向对象react
var objList = { name: 'methods', getSum: function() { console.log(this) //objList对象 } } objList.getSum()
new 构造函数名(),this指向构造函数es6
function Person() { console.log(this); //指向实例 } var personOne = new Person();
利用call和apply来实现,this就是call和apply对应的第一个参数,若是不传值或者第一个值为null,undefined时this指向window算法
function foo() { console.log(this); } foo.apply('我是apply改变的this值');//我是apply改变的this值 foo.call('我是call改变的this值');//我是call改变的this值
箭头函数不能够看成构造函数使用,也就是不能用new命令实例化一个对象,不然会抛出一个错误
箭头函数的this是和定义时有关和调用无关
调用就是函数调用模式json
(() => { console.log(this)//window })() let arrowFun = () => { console.log(this)//window } arrowFun() let arrowObj = { arrFun: function() { (() => { console.log(this)//指向函数arrFun })() } } arrowObj.arrFun();
1.IE5以前不支持call和apply,bind是ES5出来的;
2.call和apply能够调用函数,改变this,实现继承和借用别的对象的方法;小程序
调用方法,用一个对象替换掉另外一个对象(this)
对象.call(新this对象,实参1,实参2,实参3.....)
对象.apply(新this对象,[实参1,实参2,实参3.....])设计模式
1.间接调用函数,改变做用域的this值
2.劫持其余对象的方法api
var foo = { name:"张三", logName:function(){ console.log(this.name); } } var bar={ name:"李四" }; foo.logName.call(bar);//李四 实质是call改变了foo的this指向为bar,并调用该函数
3.两个函数实现继承
function Animal(name){ this.name = name; this.showName = function(){ console.log(this.name); } } function Cat(name){ Animal.call(this, name); } var cat = new Cat("Black Cat"); cat.showName(); //Black Cat
4.为类数组(arguments和nodeList)添加数组方法push,pop
(function(){ Array.prototype.push.call(arguments,'王五'); console.log(arguments);//['张三','李四','王五'] })('张三','李四')
5.合并数组
let arr1=[1,2,3]; let arr2=[4,5,6]; Array.prototype.push.apply(arr1,arr2); //将arr2合并到了arr1中
6.求数组最大值
Math.max.apply(null,arr)
7.判断字符类型
Object.prototype.toString.call({})
bind是function的一个函数扩展方法,bind之后代码从新绑定了func内部的this指向,不会调用方法,不兼容IE8
var name = '李四' var foo = { name: "张三", logName: function(age) { console.log(this.name, age); } } var fooNew = foo.logName; var fooNewBind = foo.logName.bind(foo); fooNew(10)//李四,10 fooNewBind(11)//张三,11 由于bind改变了fooNewBind里面的this指向
call实现:
Function.prototype.newCall = function(context, ...parameter) { if (typeof context === 'object' || typeof context === 'function') { context = context || window } else { context = Object.create(null) } context[fn] = this const res =context[fn](...parameter) delete context.fn; return res } let person = { name: 'Abiel' } function sayHi(age,sex) { console.log(this.name, age, sex); } sayHi.newCall (person, 25, '男'); // Abiel 25 男
apply实现:
Function.prototype.newApply = function(context, parameter) { if (typeof context === 'object' || typeof context === 'function') { context = context || window } else { context = Object.create(null) } let fn = Symbol() context[fn] = this return res=context[fn](..parameter); delete context[fn] return res } sayHi.newApply (person,[ 25, '男']) //Abiel 25 男
bind实现:
Function.prototype.bind = function (context,...innerArgs) { var me = this return function (...finnalyArgs) { return me.call(context,...innerArgs,...finnalyArgs) } } let person = { name: 'Abiel' } function sayHi(age,sex) { console.log(this.name, age, sex); } let personSayHi = sayHi.bind(person, 25) personSayHi('男')
同:都是改变this指向,均可接收参数
异:bind和call是接收单个参数,apply是接收数组
类型 | 概念 | 应用 |
---|---|---|
节流 | 事件触发后每隔一段时间触发一次,可触发屡次 | scroll,resize事件一段时间触发屡次 |
防抖 | 事件触发动做完后一段时间触发一次 | scroll,resize事件触发完后一段时间触发 |
节流:
let throttle = function(func, delay) { let timer = null; return function() { if (!timer) { timer = setTimeout(()=> { func.apply(this, arguments); timer = null; }, delay); } }; }; function handle() { console.log(Math.random()); } window.addEventListener("scroll", throttle(handle, 1000)); //事件处理函数
function debounce(fn, wait) { let timeout = null; return function() { if (timeout !== null) clearTimeout(timeout);//若是屡次触发将上次记录延迟清除掉 timeout = setTimeout(()=> { fn.apply(this, arguments); timeout = null; }, wait); }; } // 处理函数 function handle() { console.log(Math.random()); } // 滚动事件 window.addEventListener("scroll", debounce(handle, 1000));
对象继承属性的一个链条
var Person = function (name) { this.name = name; }//person是构造函数 var o3personTwo = new Person('personTwo')//personTwo是实例
原型对象都有一个默认的constructor属性指向构造函数
1.字面量
let obj={'name':'张三'}
2.Object构造函数建立
let Obj=new Object() Obj.name='张三'
3.使用工厂模式建立对象
function createPerson(name){ var o = new Object(); o.name = name; }; return o; } var person1 = createPerson('张三');
4.使用构造函数建立对象
function Person(name){ this.name = name; } var person1 = new Person('张三');
1.创了一个新对象;
2.this指向构造函数;
3.构造函数有返回,会替换new出来的对象,若是没有就是new出来的对象
4.手动封装一个new运算符
var new2 = function (func) { var o = Object.create(func.prototype); //建立对象 var k = func.call(o); //改变this指向,把结果付给k if (k&&typeof k === 'object') { //判断k的类型是否是对象 return k; //是,返回k } else { return o; //不是返回返回构造函数的执行结果 } }
JS是一门弱类型动态语言,封装和继承是他的两大特性
将父类的实例做为子类的原型
1.代码实现
定义父类:
// 定义一个动物类 function Animal (name) { // 属性 this.name = name || 'Animal'; // 实例方法 this.sleep = function(){ console.log(this.name + '正在睡觉!'); } } // 原型方法 Animal.prototype.eat = function(food) { console.log(this.name + '正在吃:' + food); };
子类:
function Cat(){ } Cat.prototype = new Animal(); Cat.prototype.name = 'cat'; // Test Code var cat = new Cat(); console.log(cat.name);//cat console.log(cat.eat('fish'));//cat正在吃:fish undefined console.log(cat.sleep());//cat正在睡觉! undefined console.log(cat instanceof Animal); //true console.log(cat instanceof Cat); //true
2.优缺点
简单易于实现,可是要想为子类新增属性和方法,必需要在new Animal()这样的语句以后执行,没法实现多继承
实质是利用call来改变Cat中的this指向
1.代码实现
子类:
function Cat(name){ Animal.call(this); this.name = name || 'Tom'; }
2.优缺点
能够实现多继承,不能继承原型属性/方法
为父类实例添加新特性,做为子类实例返回
1.代码实现
子类
function Cat(name){ var instance = new Animal(); instance.name = name || 'Tom'; return instance; }
2.优缺点
不限制调用方式,但不能实现多继承
将父类的属性和方法拷贝一份到子类中
1.子类:
function Cat(name){ var animal = new Animal(); for(var p in animal){ Cat.prototype[p] = animal[p]; } Cat.prototype.name = name || 'Tom'; }
2.优缺点
支持多继承,可是效率低占用内存
经过调用父类构造,继承父类的属性并保留传参的优势,而后经过将父类实例做为子类原型,实现函数复用
1.子类:
function Cat(name){ Animal.call(this); this.name = name || 'Tom'; } Cat.prototype = new Animal(); Cat.prototype.constructor = Cat;
function Cat(name){ Animal.call(this); this.name = name || 'Tom'; } (function(){ // 建立一个没有实例方法的类 var Super = function(){}; Super.prototype = Animal.prototype; //将实例做为子类的原型 Cat.prototype = new Super(); })();
ES6 的继承机制是先创造父类的实例对象this(因此必须先调用super方法),而后再用子类的构造函数修改this,连接描述
//父类 class Person { //constructor是构造方法 constructor(skin, language) { this.skin = skin; this.language = language; } say() { console.log('我是父类') } } //子类 class Chinese extends Person { constructor(skin, language, positon) { //console.log(this);//报错 super(skin, language); //super();至关于父类的构造函数 //console.log(this);调用super后获得了this,不报错,this指向子类,至关于调用了父类.prototype.constructor.call(this) this.positon = positon; } aboutMe() { console.log(`${this.skin} ${this.language} ${this.positon}`); } } //调用只能经过new的方法获得实例,再调用里面的方法 let obj = new Chinese('红色', '中文', '香港'); obj.aboutMe(); obj.say();
函数的参数是函数或返回函数
map,reduce,filter,sort
1.定义:只传递给函数一部分参数来调用它,让它返回一个函数去处理剩下的参数
fn(a,b,c,d)=>fn(a)(b)(c)(d)
2.代码实现:
const currying = fn => { const len = fn.length return function curr (...args1) { if (args1.length >= len) { return fn(...args1) } return (...args2) => curr(...args1, ...args2) }
}
1.定义:
obj.func(arg1, arg2)=>func(obj, arg1, arg2)
2.代码实现:
Function.prototype.uncurrying = function() { var that = this; return function() { return Function.prototype.call.apply(that, arguments); } }; function sayHi () { return "Hello " + this.value +" "+[].slice.call(arguments); } let sayHiuncurrying=sayHi.uncurrying(); console.log(sayHiuncurrying({value:'world'},"hahaha"));
1.定义:指定部分参数来返回一个新的定制函数的形式
2.例子:
function foo(a, b, c) { return a + b + c; } function func(a, b) { return foo(a,b,8); }
var test2 = {x:123,y:345}; console.log(test2);//{x:123,y:345}; console.log(test2.x);//123 console.log(test2.__proto__.x);//undefined console.log(test2.__proto__.x === test2.x);//false
var test1 = new Object({x:123,y:345}); console.log(test1);//{x:123,y:345} console.log(test1.x);//123 console.log(test1.__proto__.x);//undefined console.log(test1.__proto__.x === test1.x);//false
new的做用:
1.创了一个新对象;
2.this指向构造函数;
3.构造函数有返回,会替换new出来的对象,若是没有就是new出来的对象
Obejct.create(obj,descriptor),obj是对象,describe描述符属性(可选)
let test = Object.create({x:123,y:345}); console.log(test);//{} console.log(test.x);//123 console.log(test.__proto__.x);//3 console.log(test.__proto__.x === test.x);//true
1.功能:都能实现对象的声明,并可以赋值和取值
2.继承性:内置方法建立的对象继承到__proto__属性上
3.隐藏属性:三种声明方法会默认为内部的每一个成员(属性或方法)生成一些隐藏属性,这些隐藏属性是能够读取和可配置的,属性分类见下面
4.属性读取:Object.getOwnPropertyDescriptor()或getOwnPropertyDescriptor()
5.属性设置:Object.definePropertype或Object.defineProperties
1.数据属性4个特性:
configurable(可配置),enumerable(可枚举),writable(可修改),value(属性值)
2.访问器属性2个特性:
get(获取),set(设置)
3.内部属性
由JavaScript引擎内部使用的属性;
不能直接访问,可是能够经过对象内置方法间接访问,如:[[Prototype]]能够经过 Object.getPrototypeOf()访问;
内部属性用[[]]包围表示,是一个抽象操做,没有对应字符串类型的属性名,如[[Prototype]].
1.定义:将一个属性的全部特性编码成一个对象返回
2.描述符的属性有:数据属性和访问器属性
3.使用范围:
做为方法Object.defineProperty, Object.getOwnPropertyDescriptor, Object.create的第二个参数,
1.访问对象存在的属性
特性名 | 默认值 |
---|---|
value | 对应属性值 |
get | 对应属性值 |
set | undefined |
writable | true |
enumerable | true |
configurable | true |
因此经过上面三种声明方法已存在的属性都是有这些默认描述符
2.访问对象不存在的属性
特性名 | 默认值 |
---|---|
value | undefined |
get | undefined |
set | undefined |
writable | false |
enumerable | false |
configurable | false |
get,set与wriable,value是互斥的,若是有交集设置会报错
1.定义属性的函数有两个:Object.defineProperty和Object.defineProperties.例如:
Object.defineProperty(obj, propName, desc)
2.在引擎内部,会转换成这样的方法调用:
obj.[[DefineOwnProperty]](propName, desc, true)
1.赋值运算符(=)就是在调用[[Put]].好比:
obj.prop = v;
2.在引擎内部,会转换成这样的方法调用:
obj.[[Put]]("prop", v, isStrictModeOn)
名称 | 含义 | 用法 |
---|---|---|
in | 若是指定的属性在指定的对象或其原型链中,则in 运算符返回true | 'name' in test //true |
hasOwnProperty() | 只判断自身属性 | test.hasOwnProperty('name') //true |
.或[] | 对象或原型链上不存在该属性,则会返回undefined | test.name //"lei" test["name"] //"lei" |
是一种数据类型;
不能new,由于Symbol是一个原始类型的值,不是对象。
Symbol(),能够传参
var s1 = Symbol(); var s2 = Symbol(); s1 === s2 // false // 有参数的状况 var s1 = Symbol("foo"); var s2 = Symbol("foo"); s1 === s2 // false
1.不能与其余类型的值进行运算;
2.做为属性名
let mySymbol = Symbol(); // 第一种写法 var a = {}; a[mySymbol] = 'Hello!'; // 第二种写法 var a = { [mySymbol]: 'Hello!' }; // 第三种写法 var a = {}; Object.defineProperty(a, mySymbol, { value: 'Hello!' }); // 以上写法都获得一样结果 a[mySymbol] // "Hello!"
3.做为对象属性名时,不能用点运算符,能够用[]
let a = {}; let name = Symbol(); a.name = 'lili'; a[name] = 'lucy'; console.log(a.name,a[name]);
4.遍历不会被for...in、for...of和Object.keys()、Object.getOwnPropertyNames()取到该属性
1.定义:在全局中搜索有没有以该参数做为名称的Symbol值,若是有,就返回这个Symbol值,不然就新建并返回一个以该字符串为名称的Symbol值
2.举例:
var s1 = Symbol.for('foo'); var s2 = Symbol.for('foo'); s1 === s2 // true
1.定义:返回一个已登记的Symbol类型值的key
2.举例:
var s1 = Symbol.for("foo"); Symbol.keyFor(s1) // "foo" var s2 = Symbol("foo"); Symbol.keyFor(s2) // undefined
方法 | 特性 |
---|---|
for ... in | 遍历对象自身的和继承的可枚举属性(不含Symbol属性) |
Object.keys(obj) | 返回一个数组,包括对象自身的(不含继承的)全部可枚举属性(不含Symbol属性) |
Object.getOwnPropertyNames(obj) | 返回一个数组,包括对象自身的全部可枚举属性(不含Symbol属性) |
Object.getOwnPropertySymbols(obj) | 返回一个数组,包含对象自身的全部Symbol属性 |
Reflect.ownKeys(obj) | 返回一个数组,包含对象自身的全部(不枚举、可枚举和Symbol)属性 |
Reflect.enumerate(obj) | 返回一个Iterator对象,遍历对象自身的和继承的全部可枚举属性(不含Symbol属性) |
总结:1.只有Object.getOwnPropertySymbols(obj)和Reflect.ownKeys(obj)能够拿到Symbol属性
2.只有Reflect.ownKeys(obj)能够拿到不可枚举属性
数据模型:
var treeNodes = [ { id: 1, name: '1', children: [ { id: 11, name: '11', children: [ { id: 111, name: '111', children:[] }, { id: 112, name: '112' } ] }, { id: 12, name: '12', children: [] } ], users: [] }, ];
递归:
var parseTreeJson = function(treeNodes){ if (!treeNodes || !treeNodes.length) return; for (var i = 0, len = treeNodes.length; i < len; i++) { var childs = treeNodes[i].children; console.log(treeNodes[i].id); if(childs && childs.length > 0){ parseTreeJson(childs); } } }; console.log('------------- 递归实现 ------------------'); parseTreeJson(treeNodes);
1.定义:将源对象(source)的全部可枚举属性,复制到目标对象(target)
2.用法:
合并多个对象 var target = { a: 1, b: 1 }; var source1 = { b: 2, c: 2 }; var source2 = { c: 3 }; Object.assign(target, source1, source2);
3.注意:
这个是伪深度拷贝,只能拷贝第一层
1.原理:是将对象转化为字符串,而字符串是简单数据类型
function deepClone(source){ const targetObj = source.constructor === Array ? [] : {}; // 判断复制的目标是数组仍是对象 for(let keys in source){ // 遍历目标 if(source.hasOwnProperty(keys)){ if(source[keys] && typeof source[keys] === 'object'){ // 若是值是对象,就递归一下 targetObj[keys] = source[keys].constructor === Array ? [] : {}; targetObj[keys] = deepClone(source[keys]); }else{ // 若是不是,就直接赋值 targetObj[keys] = source[keys]; } } } return targetObj; }
定义:利用对象内置方法,设置属性,进而改变对象的属性值
1.ES5出来的方法;
2.三个参数:对象(必填),属性值(必填),描述符(可选);
3.defineProterty的描述符属性
数据属性:value,writable,configurable,enumerable 访问器属性:get,set 注:不能同时设置value和writable,这两对属性是互斥的
4.拦截对象的两种状况:
let obj = {name:'',age:'',sex:'' }, defaultName = ["这是姓名默认值1","这是年龄默认值1","这是性别默认值1"]; Object.keys(obj).forEach(key => { Object.defineProperty(obj, key, { get() { return defaultName; }, set(value) { defaultName = value; } }); }); console.log(obj.name); console.log(obj.age); console.log(obj.sex); obj.name = "这是改变值1"; console.log(obj.name); console.log(obj.age); console.log(obj.sex); let objOne={},defaultNameOne="这是默认值2"; Object.defineProperty(obj, 'name', { get() { return defaultNameOne; }, set(value) { defaultNameOne = value; } }); console.log(objOne.name); objOne.name = "这是改变值2"; console.log(objOne.name);
5.拦截数组变化的状况
let a={}; bValue=1; Object.defineProperty(a,"b",{ set:function(value){ bValue=value; console.log("setted"); }, get:function(){ return bValue; } }); a.b;//1 a.b=[];//setted a.b=[1,2,3];//setted a.b[1]=10;//无输出 a.b.push(4);//无输出 a.b.length=5;//无输出 a.b;//[1,10,3,4,undefined]; 结论:defineProperty没法检测数组索引赋值,改变数组长度的变化; 可是经过数组方法来操做能够检测到
多级对象监听
let info = {}; function observe(obj) { if (!obj || typeof obj !== "object") { return; } for (var i in obj) { definePro(obj, i, obj[i]); } } function definePro(obj, key, value) { observe(value); Object.defineProperty(obj, key, { get: function() { return value; }, set: function(newval) { console.log("检测变化", newval); value = newval; } }); } definePro(info, "friends", { name: "张三" }); info.friends.name = "李四";
6.存在的问题
不能监听数组索引赋值和改变长度的变化 必须深层遍历嵌套的对象,由于defineProterty只能劫持对象的属性,所以咱们须要对每一个对象的每一个属性进行遍历,若是属性值也是对象那么须要深度遍历,显然能劫持一个完整的对象是更好的选择
1.ES6出来的方法,实质是对对象作了一个拦截,并提供了13个处理方法
13个方法详情请戳,阮一峰的proxy介绍
2.两个参数:对象和行为函数
let handler = { get(target, key, receiver) { console.log("get", key); return Reflect.get(target, key, receiver); }, set(target, key, value, receiver) { console.log("set", key, value); return Reflect.set(target, key, value, receiver); } }; let proxy = new Proxy(obj, handler); proxy.name = "李四"; proxy.age = 24;
涉及到多级对象或者多级数组监听
//传递两个参数,一个是object, 一个是proxy的handler //若是是否是嵌套的object,直接加上proxy返回,若是是嵌套的object,那么进入addSubProxy进行递归。 function toDeepProxy(object, handler) { if (!isPureObject(object)) addSubProxy(object, handler); return new Proxy(object, handler); //这是一个递归函数,目的是遍历object的全部属性,若是不是pure object,那么就继续遍历object的属性的属性,若是是pure object那么就加上proxy function addSubProxy(object, handler) { for (let prop in object) { if ( typeof object[prop] == 'object') { if (!isPureObject(object[prop])) addSubProxy(object[prop], handler); object[prop] = new Proxy(object[prop], handler); } } object = new Proxy(object, handler) } //是否是一个pure object,意思就是object里面没有再嵌套object了 function isPureObject(object) { if (typeof object!== 'object') { return false; } else { for (let prop in object) { if (typeof object[prop] == 'object') { return false; } } } return true; } } let object = { name: { first: { four: 5, second: { third: 'ssss' } } }, class: 5, arr: [1, 2, {arr1:10}], age: { age1: 10 } } //这是一个嵌套了对象和数组的数组 let objectArr = [{name:{first:'ss'}, arr1:[1,2]}, 2, 3, 4, 5, 6] //这是proxy的handler let handler = { get(target, property) { console.log('get:' + property) return Reflect.get(target, property); }, set(target, property, value) { console.log('set:' + property + '=' + value); return Reflect.set(target, property, value); } } //变成监听对象 object = toDeepProxy(object, handler); objectArr = toDeepProxy(objectArr, handler); //进行一系列操做 console.time('pro') objectArr.length objectArr[3]; objectArr[2]=10 objectArr[0].name.first = 'ss' objectArr[0].arr1[0] object.name.first.second.third = 'yyyyy' object.class = 6; object.name.first.four object.arr[2].arr1 object.age.age1 = 20; console.timeEnd('pro')
3.问题和优势
reflect对象没有构造函数
能够监听数组索引赋值,改变数组长度的变化,
是直接监听对象的变化,不用深层遍历
1.defineProterty是es5的标准,proxy是es6的标准;
2.proxy能够监听到数组索引赋值,改变数组长度的变化;
3.proxy是监听对象,不用深层遍历,defineProterty是监听属性;
3.利用defineProterty实现双向数据绑定(vue2.x采用的核心)
请戳,剖析Vue原理&实现双向绑定MVVM
4.利用proxy实现双向数据绑定(vue3.x会采用)
数组基本上考察数组方法多一点,因此这里就单纯介绍常见的场景数组的方法,还有不少场景后续补充;
本文主要从应用来说数组api的一些骚操做;
如一行代码扁平化n维数组、数组去重、求数组最大值、数组求和、排序、对象和数组的转化等;
上面这些应用场景你能够用一行代码实现?
1.终极篇
[1,[2,3]].flat(2) //[1,2,3] [1,[2,3,[4,5]].flat(3) //[1,2,3,4,5] [1,[2,3,[4,5]]].toString() //'1,2,3,4,5' [1[2,3,[4,5[...]].flat(Infinity) //[1,2,3,4...n]
Array.flat(n)是ES10扁平数组的api,n表示维度,n值为Infinity时维度为无限大
2.开始篇
function flatten(arr) { while(arr.some(item=>Array.isArray(item))) { arr = [].concat(...arr); } return arr; } flatten([1,[2,3]]) //[1,2,3] flatten([1,[2,3,[4,5]]) //[1,2,3,4,5]
实质是利用递归和数组合并方法concat实现扁平
1.终极篇
Array.from(new Set([1,2,3,3,4,4])) //[1,2,3,4] [...new Set([1,2,3,3,4,4])] //[1,2,3,4]
set是ES6新出来的一种一种定义不重复数组的数据类型
Array.from是将类数组转化为数组
...是扩展运算符,将set里面的值转化为字符串
2.开始篇
Array.prototype.distinct = nums => { const map = {} const result = [] for (const n of nums) { if (!(n in map)) { map[n] = 1 result.push(n) } } return result } [1,2,3,3,4,4].distinct(); //[1,2,3,4]
取新数组存值,循环两个数组值相比较
1.终极篇
[1,2,3,4].sort((a, b) => a - b); // [1, 2,3,4],默认是升序 [1,2,3,4].sort((a, b) => b - a); // [4,3,2,1] 降序
sort是js内置的排序方法,参数为一个函数
2.开始篇
冒泡排序:
Array.prototype.bubleSort=function () { let arr=this, len = arr.length; for (let outer = len; outer >= 2; outer--) { for (let inner = 0; inner <= outer - 1; inner++) { if (arr[inner] > arr[inner + 1]) { //升序 [arr[inner], arr[inner + 1]] = [arr[inner + 1], arr[inner]]; console.log([arr[inner], arr[inner + 1]]); } } } return arr; } [1,2,3,4].bubleSort() //[1,2,3,4]
选择排序
Array.prototype.selectSort=function () { let arr=this, len = arr.length; for (let i = 0, len = arr.length; i < len; i++) { for (let j = i, len = arr.length; j < len; j++) { if (arr[i] > arr[j]) { [arr[i], arr[j]] = [arr[j], arr[i]]; } } } return arr; } [1,2,3,4].selectSort() //[1,2,3,4]
1.终极篇
Math.max(...[1,2,3,4]) //4 Math.max.apply(this,[1,2,3,4]) //4 [1,2,3,4].reduce( (prev, cur,curIndex,arr)=> { return Math.max(prev,cur); },0) //4
Math.max()是Math对象内置的方法,参数是字符串;
reduce是ES5的数组api,参数有函数和默认初始值;
函数有四个参数,pre(上一次的返回值),cur(当前值),curIndex(当前值索引),arr(当前数组)
2.开始篇
先排序再取值
1.终极篇
[1,2,3,4].reduce(function (prev, cur) { return prev + cur; },0) //10
2.开始篇
function sum(arr) { var len = arr.length; if(len == 0){ return 0; } else if (len == 1){ return arr[0]; } else { return arr[0] + sum(arr.slice(1)); } } sum([1,2,3,4]) //10
利用slice截取改变数组,再利用递归求和
1.终极篇
[1,2,3,4].concat([5,6]) //[1,2,3,4,5,6] [...[1,2,3,4],...[4,5]] //[1,2,3,4,5,6] let arrA = [1, 2], arrB = [3, 4] Array.prototype.push.apply(arrA, arrB))//arrA值为[1,2,3,4]
2.开始篇
let arr=[1,2,3,4]; [5,6].map(item=>{ arr.push(item) }) //arr值为[1,2,3,4,5,6],注意不能直接return出来,return后只会返回[5,6]
1.终极篇
[1,2,3].includes(4) //false [1,2,3].indexOf(4) //-1 若是存在换回索引 [1, 2, 3].find((item)=>item===3)) //3 若是数组中无值返回undefined [1, 2, 3].findIndex((item)=>item===3)) //2 若是数组中无值返回-1
includes(),find(),findIndex()是ES6的api
2.开始篇
[1,2,3].some(item=>{ return item===3 }) //true 若是不包含返回false
1.终极篇
Array.prototype.slice.call(arguments) //arguments是类数组(伪数组) Array.prototype.slice.apply(arguments) Array.from(arguments) [...arguments]
类数组:表示有length属性,可是不具有数组的方法
call,apply:是改变slice里面的this指向arguments,因此arguments也可调用数组的方法
Array.from是将相似数组或可迭代对象建立为数组
...是将类数组扩展为字符串,再定义为数组
2.开始篇
Array.prototype.slice = function(start,end){ var result = new Array(); start = start || 0; end = end || this.length; //this指向调用的对象,当用了call后,可以改变this的指向,也就是指向传进来的对象,这是关键 for(var i = start; i < end; i++){ result.push(this[i]); } return result; }
1.终极篇
[1,2,3].fill(false) //[false,false,false]
fill是ES6的方法
2.开始篇
[1,2,3].map(() => 0)
[1,2,3].every(item=>{return item>2}) //false
every是ES5的api,每一项知足返回 true
[1,2,3].some(item=>{return item>2}) //true
some是ES5的api,有一项知足返回 true
[1,2,3].filter(item=>{return item>2}) //[3]
filter是ES5的api,返回知足添加的项的数组
Object.keys({name:'张三',age:14}) //['name','age'] Object.values({name:'张三',age:14}) //['张三',14] Object.entries({name:'张三',age:14}) //[[name,'张三'],[age,14]] Object.fromEntries([name,'张三'],[age,14]) //ES10的api,Chrome不支持 , firebox输出{name:'张三',age:14}
[{count:1},{count:2},{count:3}].reduce((p, e)=>p+(e.count), 0)
数据结构是计算机存储、组织数据的方式,算法是系统描述解决问题的策略。了解基本的数据结构和算法能够提升代码的性能和质量。
也是程序猿进阶的一个重要技能。
手撸代码实现栈,队列,链表,字典,二叉树,动态规划和贪心算法
栈的特色:先进后出
class Stack { constructor() { this.items = []; } // 入栈 push(element) { this.items.push(element); } // 出栈 pop() { return this.items.pop(); } // 末位 get peek() { return this.items[this.items.length - 1]; } // 是否为空栈 get isEmpty() { return !this.items.length; } // 长度 get size() { return this.items.length; } // 清空栈 clear() { this.items = []; } } // 实例化一个栈 const stack = new Stack(); console.log(stack.isEmpty); // true // 添加元素 stack.push(5); stack.push(8); // 读取属性再添加 console.log(stack.peek); // 8 stack.push(11); console.log(stack.size); // 3 console.log(stack.isEmpty); // false
队列:先进先出
class Queue { constructor(items) { this.items = items || []; } enqueue(element) { this.items.push(element); } dequeue() { return this.items.shift(); } front() { return this.items[0]; } clear() { this.items = []; } get size() { return this.items.length; } get isEmpty() { return !this.items.length; } print() { console.log(this.items.toString()); } } const queue = new Queue(); console.log(queue.isEmpty); // true queue.enqueue("John"); queue.enqueue("Jack"); queue.enqueue("Camila"); console.log(queue.size); // 3 console.log(queue.isEmpty); // false queue.dequeue(); queue.dequeue();
链表:存贮有序元素的集合,
可是不一样于数组,每一个元素是一个存贮元素自己的节点和指向下一个元素引用组成
要想访问链表中间的元素,须要从起点开始遍历找到所需元素
class Node { constructor(element) { this.element = element; this.next = null; } } // 链表 class LinkedList { constructor() { this.head = null; this.length = 0; } // 追加元素 append(element) { const node = new Node(element); let current = null; if (this.head === null) { this.head = node; } else { current = this.head; while (current.next) { current = current.next; } current.next = node; } this.length++; } // 任意位置插入元素 insert(position, element) { if (position >= 0 && position <= this.length) { const node = new Node(element); let current = this.head; let previous = null; let index = 0; if (position === 0) { this.head = node; node.next = current; } else { while (index++ < position) { previous = current; current = current.next; } node.next = current; previous.next = node; } this.length++; return true; } return false; } // 移除指定位置元素 removeAt(position) { // 检查越界值 if (position > -1 && position < length) { let current = this.head; let previous = null; let index = 0; if (position === 0) { this.head = current.next; } else { while (index++ < position) { previous = current; current = current.next; } previous.next = current.next; } this.length--; return current.element; } return null; } // 寻找元素下标 findIndex(element) { let current = this.head; let index = -1; while (current) { if (element === current.element) { return index + 1; } index++; current = current.next; } return -1; } // 删除指定文档 remove(element) { const index = this.findIndex(element); return this.removeAt(index); } isEmpty() { return !this.length; } size() { return this.length; } // 转为字符串 toString() { let current = this.head; let string = ""; while (current) { string += ` ${current.element}`; current = current.next; } return string; } } const linkedList = new LinkedList(); console.log(linkedList); linkedList.append(2); linkedList.append(6); linkedList.append(24); linkedList.append(152); linkedList.insert(3, 18); console.log(linkedList); console.log(linkedList.findIndex(24));
字典:相似对象,以key,value存贮值
class Dictionary { constructor() { this.items = {}; } set(key, value) { this.items[key] = value; } get(key) { return this.items[key]; } remove(key) { delete this.items[key]; } get keys() { return Object.keys(this.items); } get values() { /* 也可使用ES7中的values方法 return Object.values(this.items) */ // 在这里咱们经过循环生成一个数组并输出 return Object.keys(this.items).reduce((r, c, i) => { r.push(this.items[c]); return r; }, []); } } const dictionary = new Dictionary(); dictionary.set("Gandalf", "gandalf@email.com"); dictionary.set("John", "johnsnow@email.com"); dictionary.set("Tyrion", "tyrion@email.com"); console.log(dictionary); console.log(dictionary.keys); console.log(dictionary.values); console.log(dictionary.items);
特色:每一个节点最多有两个子树的树结构
class NodeTree { constructor(key) { this.key = key; this.left = null; this.right = null; } } class BinarySearchTree { constructor() { this.root = null; } insert(key) { const newNode = new NodeTree(key); const insertNode = (node, newNode) => { if (newNode.key < node.key) { if (node.left === null) { node.left = newNode; } else { insertNode(node.left, newNode); } } else { if (node.right === null) { node.right = newNode; } else { insertNode(node.right, newNode); } } }; if (!this.root) { this.root = newNode; } else { insertNode(this.root, newNode); } } //访问树节点的三种方式:中序,先序,后序 inOrderTraverse(callback) { const inOrderTraverseNode = (node, callback) => { if (node !== null) { inOrderTraverseNode(node.left, callback); callback(node.key); inOrderTraverseNode(node.right, callback); } }; inOrderTraverseNode(this.root, callback); } min(node) { const minNode = node => { return node ? (node.left ? minNode(node.left) : node) : null; }; return minNode(node || this.root); } max(node) { const maxNode = node => { return node ? (node.right ? maxNode(node.right) : node) : null; }; return maxNode(node || this.root); } } const tree = new BinarySearchTree(); tree.insert(11); tree.insert(7); tree.insert(5); tree.insert(3); tree.insert(9); tree.insert(8); tree.insert(10); tree.insert(13); tree.insert(12); tree.insert(14); tree.inOrderTraverse(value => { console.log(value); }); console.log(tree.min()); console.log(tree.max());
冒泡排序,选择排序,插入排序,此处不作赘述,请戳 排序
特色:第三项等于前面两项之和
function fibonacci(num) { if (num === 1 || num === 2) { return 1 } return fibonacci(num - 1) + fibonacci(num - 2) }
特色:经过全局规划,将大问题分割成小问题来取最优解
案例:最少硬币找零
美国有如下面额(硬币):d1=1, d2=5, d3=10, d4=25
若是要找36美分的零钱,咱们能够用1个25美分、1个10美分和1个便士( 1美分)
class MinCoinChange { constructor(coins) { this.coins = coins this.cache = {} } makeChange(amount) { if (!amount) return [] if (this.cache[amount]) return this.cache[amount] let min = [], newMin, newAmount this.coins.forEach(coin => { newAmount = amount - coin if (newAmount >= 0) { newMin = this.makeChange(newAmount) } if (newAmount >= 0 && (newMin.length < min.length - 1 || !min.length) && (newMin.length || !newAmount)) { min = [coin].concat(newMin) } }) return (this.cache[amount] = min) } } const rninCoinChange = new MinCoinChange([1, 5, 10, 25]) console.log(rninCoinChange.makeChange(36)) // [1, 10, 25] const minCoinChange2 = new MinCoinChange([1, 3, 4]) console.log(minCoinChange2.makeChange(6)) // [3, 3]
特色:经过最优解来解决问题
用贪心算法来解决2.3中的案例
class MinCoinChange2 { constructor(coins) { this.coins = coins } makeChange(amount) { const change = [] let total = 0 this.coins.sort((a, b) => a < b).forEach(coin => { if ((total + coin) <= amount) { change.push(coin) total += coin } }) return change } } const rninCoinChange2 = new MinCoinChange2 ( [ 1, 5, 10, 25]) console.log (rninCoinChange2. makeChange (36))
设计模式若是应用到项目中,能够实现代码的复用和解耦,提升代码质量。 本文主要介绍14种设计模式
写UI组件,封装框架必备
1.定义:又叫静态工厂方法,就是建立对象,并赋予属性和方法
2.应用:抽取类相同的属性和方法封装到对象上
3.代码:
let UserFactory = function (role) { function User(opt) { this.name = opt.name; this.viewPage = opt.viewPage; } switch (role) { case 'superAdmin': return new User(superAdmin); break; case 'admin': return new User(admin); break; case 'user': return new User(user); break; default: throw new Error('参数错误, 可选参数:superAdmin、admin、user') } } //调用 let superAdmin = UserFactory('superAdmin'); let admin = UserFactory('admin') let normalUser = UserFactory('user') //最后获得角色,能够调用
1.定义:对产品类的抽象使其建立业务主要负责用于建立多类产品的实例
2.应用:建立实例
3.代码:
var Factory=function(type,content){ if(this instanceof Factory){ var s=new this[type](content); return s; }else{ return new Factory(type,content); } } //工厂原型中设置建立类型数据对象的属性 Factory.prototype={ Java:function(content){ console.log('Java值为',content); }, PHP:function(content){ console.log('PHP值为',content); }, Python:function(content){ console.log('Python值为',content); }, } //测试用例 Factory('Python','我是Python');
1.定义:设置函数的原型属性
2.应用:实现继承
3.代码:
function Animal (name) { // 属性 this.name = name || 'Animal'; // 实例方法 this.sleep = function(){ console.log(this.name + '正在睡觉!'); } } // 原型方法 Animal.prototype.eat = function(food) { console.log(this.name + '正在吃:' + food); }; function Cat(){ } Cat.prototype = new Animal(); Cat.prototype.name = 'cat'; // Test Code var cat = new Cat(); console.log(cat.name);//cat console.log(cat.eat('fish'));//cat正在吃:fish undefined console.log(cat.sleep());//cat正在睡觉! undefined console.log(cat instanceof Animal); //true console.log(cat instanceof Cat); //true
1.定义:只容许被实例化依次的类
2.应用:提供一个命名空间
3.代码:
let singleCase = function(name){ this.name = name; }; singleCase.prototype.getName = function(){ return this.name; } // 获取实例对象 let getInstance = (function() { var instance = null; return function(name) { if(!instance) {//至关于一个一次性阀门,只能实例化一次 instance = new singleCase(name); } return instance; } })(); // 测试单体模式的实例,因此one===two let one = getInstance("one"); let two = getInstance("two");
1.定义:为子系统中的一组接口提供一个一致的界面
2.应用:简化复杂接口
3.代码:
外观模式
1.定义:将一个接口转换成客户端须要的接口而不须要去修改客户端代码,使得不兼容的代码能够一块儿工做
2.应用:适配函数参数
3.代码:
适配器模式
1.定义:不改变原对象的基础上,给对象添加属性或方法
2.代码
let decorator=function(input,fn){ //获取事件源 let input=document.getElementById(input); //若事件源已经绑定事件 if(typeof input.onclick=='function'){ //缓存事件源原有的回调函数 let oldClickFn=input.onclick; //为事件源定义新事件 input.onclick=function(){ //事件源原有回调函数 oldClickFn(); //执行事件源新增回调函数 fn(); } }else{ //未绑定绑定 input.onclick=fn; } } //测试用例 decorator('textInp',function(){ console.log('文本框执行啦'); }) decorator('btn',function(){ console.log('按钮执行啦'); })
1.定义:将抽象部分与它的实现部分分离,使它们均可以独立地变化
2.代码
桥接模式
1.定义:定义一个模板,供之后传不一样参数调用
2.代码:
模块方法模式
1.做用:解决类与对象,对象与对象之间的耦合
2.代码:
let Observer= (function(){ let _message={}; return { //注册接口, //1.做用:将订阅者注册的消息推入到消息队列 //2.参数:因此要传两个参数,消息类型和处理动做, //3.消息不存在从新建立,存在将消息推入到执行方法 regist:function(type,fn){ //若是消息不存在,建立 if(typeof _message[type]==='undefined'){ _message[type]=[fn]; }else{ //将消息推入到消息的执行动做 _message[type].push(fn); } }, //发布信息接口 //1.做用:观察这发布消息将全部订阅的消息一次执行 //2.参数:消息类型和动做执行传递参数 //3.消息类型参数必须校验 fire:function(type,args){ //若是消息没有注册,则返回 if(!_message[type]) return; //定义消息信息 var events={ type:type, //消息类型 args:args||{} //消息携带数据 }, i=0, len=_message[type].length; //遍历消息 for(;i<len;i++){ //依次执行注册消息 _message[type][i].call(this,events); } }, //移除信息接口 //1.做用:将订阅者注销消息从消息队列清除 //2.参数:消息类型和执行的动做 //3.消息参数校验 remove:function(type,fn){ //若是消息动做队列存在 if(_message[type] instanceof Array){ //从最后一个消息动做序遍历 var i=_message[type].length-1; for(;i>=0;i--){ //若是存在该动做在消息队列中移除 _message[type][i]===fn&&_message[type].splice(i,1); } } } } })() //测试用例 //1.订阅消息 Observer.regist('test',function(e){ console.log(e.type,e.args.msg); }) //2.发布消息 Observer.fire('test',{msg:'传递参数1'}); Observer.fire('test',{msg:'传递参数2'}); Observer.fire('test',{msg:'传递参数3'});
1.定义:一个对象状态改变会致使行为变化
2.做用:解决复杂的if判断
3.代码
状态模式
1.定义:定义了一系列家族算法,并对每一种算法单独封装起来,让算法之间能够相互替换,独立于使用算法的客户
2.代码
策略模式
1.定义:经过继承封装一些该数据类型不具有的属性,
2.做用:让对象具有数组的操做方法
3.代码:
访问者模式
1.定义:设置一个中间层,处理对象之间的交互
2.代码:
中介者模式
HTTP 是一个链接客户端,网关和服务器的一个协议。
支持客户/服务器模式:能够链接客户端和服务端;
简单快速:请求只需传送请求方法,路径和请求主体;
灵活:传输数据类型灵活;
无链接:请求结束当即断开;
无状态:没法记住上一次请求。
无状态:HTTP 协议自己没法解决这个状态,只有经过 cookie 和 session 将状态作贮存,常见的场景是登陆状态保持;
无链接:能够经过自身属性 Keep-Alive。
HTTP(S) 请求地址 → DNS 解析 → 三次握手 → 发送请求 → 四次挥手
三次握手过程(图片来源 CSDN)
在这里插入图片描述
在这里插入图片描述
只容许客户端发送 GET 这一种请求;
且不支持请求头,协议只支持纯文本;
无状态性,每一个访问独立处理,完成断开;
无状态码。
有身份认证,三次握手;
请求与响应支持头域;
请求头内容;
属性名 | 含义 |
---|---|
Accept | 可接受的 MIME 类型 |
Accept-Encoding | 数据可解码的格式 |
Accept-Language | 可接受语言 |
Connection | 值 keep-alive 是长链接 |
Host | 主机和端口 |
Pragma | 是否缓存,指定 no-cache 返回刷新 |
Referer | 页面路由 |
If-Modified-Since | 值为时间 |
响应头内容;
属性名 | 含义 |
---|---|
Connection | 值 keep-alive 是长链接 |
Content-Type | 返回文档类型,常见的值有 text/plain,text/html,text/json |
Date | 消息发送的时间 |
Server | 服务器名字 |
Last-Modified | 值为时间,s 返回的最后修改时间 |
Expires | 缓存过时时间,b 和 s 时间作对比 |
注意
expires 是响应头内容,返回一个固定的时间,缺陷是时间到了服务器要从新设置。
请求头中若是有 If-Modified-Since,服务器会将时间与 last-modified 对比,相同返回 304。
响应对象以一个响应状态行开始
响应对象不仅限于超文本
支持 GET、HEAD、POST 方法
有状态码
支持长链接(但默认仍是使用短链接)、缓存机制以及身份认证。
请求头增长 Cache-Control
属性名 | 含义 |
---|---|
Cache-Control | 在1.1 引入的方法,指定请求和响应遵循的缓存机制,值有:public(b 和 s 都缓存),private(b 缓存),no-cache(不缓存),no-store(不缓存),max-age(缓存时间,s 为单位),min-fresh(最小更新时间),max-age=3600 |
If-None-Match | 上次请求响应头返回的 etag 值响应头增长 Cache-Control,表示全部的缓存机制是否能够缓存及哪一种类型 etag 返回的哈希值,第二次请求头携带去和服务器值对比 |
注意
Cache-Control 的 max-age 返回是缓存的相对时间
Cache-Control 优先级比 expires 高
缺点:不能第一时间拿到最新修改文件
采用二进制格式传输
多路复用,其实就是将请求数据分红帧乱序发送到 TCP 中。TCP 只能有一个 steam,因此仍是会阻塞
报头压缩
服务器推送主动向 B 端发送静态资源,避免往返延迟。
1.是基于 QUIC 协议,基于 UDP
2.特色:
自定义链接机制:TCP 以 IP/端口标识,变化从新链接握手,UDP 是一 64 位 ID 标识,是无链接;
自定义重传机制:TCP 使用序号和应答传输,QUIC 是使用递增序号传输; 无阻塞的多路复用:同一条 QUIC 能够建立多个 steam。
1.https 是在 http 协议的基础上加了个 SSL;
2.主要包括:握手(凭证交换和验证)和记录协议(数据进行加密)。
1.按协议分:协议层缓存和非 http 协议缓存:
1.1协议层缓存:利用 http 协议头属性值设置;
1.2非协议层缓存:利用 meta 标签的 http-equiv 属性值 Expires,set-cookie。
2.按缓存分:强缓存和协商缓存:
2.1强缓存:利用 cache-control 和 expires 设置,直接返回一个过时时间,因此在缓存期间不请求,If-modify-since;
2.2协商缓存:响应头返回 etag 或 last-modified 的哈希值,第二次请求头 If-none-match 或 IF-modify-since 携带上次哈希值,一致则返回 304。
3.协商缓存对比: etag 优先级高于 last-modified;
4.etag 精度高,last-modified 精度是 s,1s 内 etag 修改多少次都会被记录; last-modified 性能好,etag 要获得 hash 值。
5.浏览器读取缓存流程:
会先判断强缓存;再判断协商缓存 etag(last-modified)是否存在;
存在利用属性 If-None-match(If-Modified-since)携带值;
请求服务器,服务器对比 etag(last-modified),生效返回 304。
F5 刷新会忽略强缓存不会忽略协商缓存,ctrl+f5 都失效
序列 | 详情 |
---|---|
1XX(通知) | |
2XX(成功) | 200(成功)、201(服务器建立)、202(服务器接收未处理)、203(非受权信息)、204(未返回内容)、205(重置内容)、206(部份内容) |
3XX(重定向) | 301(永久移动)、302(临时移动)、303(查看其余位置)、304(未修改)、305(使用代理)、307(临时重定向) |
4XX(客户端错误) | 400(错误请求)、401(未受权)、403(禁止)、404(未找到)、405(方法禁用)、406(不接受)、407(须要代理受权) |
5XX(服务器错误) | 500(服务器异常)、501(还没有实施)、502(错误网关)、503(服务不可用)、504(网关超时)、505(HTTP 版本不受支持) |
在这里插入图片描述
协议
版本 | 内容 |
---|---|
http0.9 | 只容许客户端发送 GET 这一种请求;且不支持请求头,协议只支持纯文本;无状态性,每一个访问独立处理,完成断开;无状态码 |
http1.0 解决 0.9 的缺点,增长 If-modify-since(last-modify)和 expires 缓存属性 | |
http1.x | 增长 cache-control 和 If-none-match(etag)缓存属性 |
http2.0 | 采用二进制格式传输;多路复用;报头压缩;服务器推送 |
http3.0 | 采用 QUIC 协议,自定义链接机制;自定义重传机制;无阻塞的多路复用 |
缓存
类型 | 特性 |
---|---|
强缓存 | 经过 If-modify-since(last-modify)、expires 和 cache-control 设置,属性值是时间,因此在时间内不用请求 |
协商缓存 | 经过 If-none-match(etag)设置,etag 属性是哈希值,因此要请求和服务器值对比 |
这只是 JS 原生梳理,后续会再出 react,node,小程序相关的梳理;原创码字不易,欢迎 star!