是时候撸一波 JS 基础啦,撸熟了,银十速拿 offer; 本文不从传统的问答方式梳理,而是从知识维度梳理,以便造成知识网络; 包括函数,数组,对象,数据结构,算法,设计模式和 http.php
//ES5
function getSum(){}
function (){}//匿名函数
//ES6()=>{}//若是{}内容只有一行{}和return关键字可省
//ES5
var sum=function(){}
//ES6
let sum=()=>{}//若是{}内容只有一行{}和return关键字可省,
const sum = new Function('a', 'b' , 'return a + b')
1.函数声明有预解析,并且函数声明的优先级高于变量;css
2.使用 Function 构造函数定义函数的方式是一个函数表达式,这种方式会致使解析两次代码,影响性能。第一次解析常规的 JavaScript 代码,第二次解析传入构造函数的字符串html
在 ES5 中函数内容的 this 指向和调用方法有关vue
包括函数名()和匿名函数调用,this 指向 windownode
function getSum() {
console.log(this) //这个属于函数名调用,this指向window
}
getSum()
(function() {
console.log(this) //匿名函数调用,this指向window
})()
var getSum=function() {
console.log(this) //实际上也是函数名调用,window
}
getSum()
对象.方法名(),this 指向对象android
var objList = {
name: 'methods',
getSum: function() {
console.log(this) //objList对象
}
}
objList.getSum()
new 构造函数名(),this 指向实例化的对象ios
function Person() {
console.log(this); //是构造函数调用,指向实例化的对象personOne
}
var personOne = new Person();
利用 call 和 apply 来实现,this 就是 call 和 apply 对应的第一个参数,若是不传值或者第一个值为 null,undefined 时 this 指向 windowgit
function foo() {
console.log(this);
}
foo.apply('我是apply改变的this值');//我是apply改变的this值
foo.call('我是call改变的this值');//我是call改变的this值
箭头函数不能够看成构造函数使用,也就是不能用 new 命令实例化一个对象,不然会抛出一个错误 箭头函数的 this 是和定义时有关和调用无关 调用就是函数调用模式es6
(() => {
console.log(this)//window
})()
let arrowFun = () => {
console.log(this)//window
}
arrowFun()
let arrowObj = {
arrFun: function() {
(() => {
console.log(this)//this指向的是arrowObj对象
})()
}
}
arrowObj.arrFun();
1.IE5 以前不支持 call 和 apply,bind 是 ES5 出来的; 2.call 和 apply 能够调用函数,改变 this,实现继承和借用别的对象的方法;web
调用方法,用一个对象替换掉另外一个对象(this) 对象.call(新 this 对象,实参 1,实参 2,实参 3.....) 对象.apply(新 this 对象,[实参 1,实参 2,实参 3.....])
1.间接调用函数,改变做用域的 this 值 2.劫持其余对象的方法
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 "fn")
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 "fn");
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 是接收数组
let throttle = function(func, delay) {
let timer = null;
return ()=> {
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) {
var 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 的第二个参数,
访问对象存在的属性
因此经过上面三种声明方法已存在的属性都是有这些默认描述符
2.访问对象不存在的属性
get,set 与 wriable,value 是互斥的,若是有交集设置会报错
1.定义属性的函数有两个:Object.defineProperty 和 Object.defineProperties.例如: Object.defineProperty(obj, propName, desc)
2.在引擎内部,会转换成这样的方法调用: obj.[[DefineOwnProperty]](propName, desc, true "[DefineOwnProperty]")
1.赋值运算符(=)就是在调用[[Put]].好比: obj.prop = v;
2.在引擎内部,会转换成这样的方法调用: obj.[[Put]]("prop", v, isStrictModeOn)
是一种数据类型; 不能 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
总结: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 个处理方法 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 采用的核心) 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'
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 = function() {
const map = {}
const result = []
for (const n of this) {
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 3.11 有一项知足
[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]
特色:经过最优解来解决问题 用贪心算法来解决 案例
function MinCoinChange(coins) {
var coins = coins;
var cache = {};
this.makeChange = function(amount) {
var change = [],
total = 0;
for (var i = coins.length; i >= 0; i--) {
var coin = coins[i];
while (total + coin <= amount) {
change.push(coin);
total += coin;
}
}
return change;
};
}
var minCoinChange = new MinCoinChange([1, 5, 10, 25]);
console.log(minCoinChange.makeChange(36));
console.log(minCoinChange.makeChange(34));
console.log(minCoinChange.makeChange(6));
设计模式若是应用到项目中,能够实现代码的复用和解耦,提升代码质量。本文主要介绍 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 "type");
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.代码: 外观模式https://www.cnblogs.com/linda586586/p/4237093.html
1.定义:将一个接口转换成客户端须要的接口而不须要去修改客户端代码,使得不兼容的代码能够一块儿工做
2.应用:适配函数参数
3.代码: 适配器模式https://www.cnblogs.com/TomXu/archive/2012/04/11/2435452.html
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.代码 桥接模式https://www.cnblogs.com/TomXu/archive/2012/04/19/2437321.html
1.定义:定义一个模板,供之后传不一样参数调用
2.代码: 模块方法模式https://blog.csdn.net/namechenfl/article/details/80685741
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.代码 状态模式https://www.jianshu.com/p/ec9b9a1cd148
1.定义:定义了一系列家族算法,并对每一种算法单独封装起来,让算法之间能够相互替换,独立于使用算法的客户
2.代码 策略模式https://www.cnblogs.com/Medeor/p/5001841.html
1.定义:经过继承封装一些该数据类型不具有的属性,
2.做用:让对象具有数组的操做方法
3.代码: 访问者模式https://blog.csdn.net/itpinpai/article/details/51644922
1.定义:设置一个中间层,处理对象之间的交互
2.代码:
中介者模式https://www.cnblogs.com/xiaohuochai/p/8042198.html
HTTP 是一个链接客户端,网关和服务器的一个协议。
支持客户/服务器模式:能够链接客户端和服务端;简单快速:请求只需传送请求方法,路径和请求主体;灵活:传输数据类型灵活;无链接:请求结束当即断开;无状态:没法记住上一次请求。
无状态:HTTP 协议自己没法解决这个状态,只有经过 cookie 和 session 将状态作贮存,常见的场景是登陆状态保持;无链接:能够经过自身属性 Keep-Alive。
HTTP(S) 请求地址 → DNS 解析 → 三次握手 → 发送请求 → 四次挥手
三次握手过程图片来源 CSDN)
四次挥手过(图片来源 CSDN)
只容许客户端发送 GET 这一种请求;且不支持请求头,协议只支持纯文本;无状态性,每一个访问独立处理,完成断开;无状态码。
有身份认证,三次握手;请求与响应支持头域;请求头内容;
响应头内容;
注意 expires 是响应头内容,返回一个固定的时间,缺陷是时间到了服务器要从新设置; 请求头中若是有 If-Modified-Since,服务器会将时间与 last-modified 对比,相同返回 304; 响应对象以一个响应状态行开始; 响应对象不仅限于超文本; 支持 GET、HEAD、POST 方法; 有状态码; 支持长链接(但默认仍是使用短链接)、缓存机制以及身份认证。
请求头增长 Cache-Control
属性名 含义
注意 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 都失效
协议
缓存