JSMST

JS部分

JSMST

一.水平垂直居中的方法 && 数组方法

1.垂直方法

1、知道父级的宽高,position:absolute,四个值都为0,margin:auto;
2、无需父级的宽高,positon:absolute,top:50%,left:50%
transform:translate(-50%,-50%)
3、display:flex;justify-content:center,align-items:center


2.数组方法

length–:删除数组的最后一项
lengtg++:数组末尾增加,没有什么都没,增加一个undefined
push:末尾增加一项,返回数组的新长度
pop:删除末尾项,返回删除的那一项
unshift:数组开头增加一项,返回数组的新长度
shift:删除数组的开头像,返回删除的那一项
splice(n,m):从索引n开始删除m个,返回删除的项组成的新数组
splice(n):从索引n开始删除到末尾
splice(n,m,x):从索引n开始删除m个,然后把x插入到删除的位置
splice(n,0,x):删除的个数为0的时候,默认把x插入到n索引的前面
splice的返回值永远是一个数组,如果删除项,那数组里就是这些项,没删就是空数组
slice(n,m):从索引n开始赋值到索引m,不包含m
slice(n):从索引n开始复制到末尾
slice():复制整个数组
slice方法的返回值永远都是一个新数组,如果没复制到那么就是一个空数组
ary1.concat(ary2):拼接数组,返回ary1和ary2拼接好的新数组
ary1.concat(5):参数如果不是数组那么把这个参数当做数组项拼接在一起
join(’ $ '):把数组的项目拼接成字符串,数组的每一项用$隔开
eval(‘alert()’):把这个字符串当做js表达式运行=>去引号
reverse():数组倒序,返回倒序过后的原有数组
sort():升序:sort( function(a,b){ return a-b } )
                 降序: sort( function(a,b){ return b-a } )
indexOf:判断这个项是否出现在数组中,如果出现,返回对应的索引位置,没出现返回-1
lastIndexOf:和indexOf用法基本相同,这个是最后一次出现的索引位置
forEach:遍历数组,需要一个函数参数。item,index,ary形参forEach没返回值

 forEach(function (item,index,ary){
*          // item代表的是循环时候的每一项。每次都是代表不同的
*          // index 是item的索引
*          // ary 是遍历的数组
*        });

map和forEach最大的去呗就是有返回值,并且返回值是一个数组,数组的长度取决于原数组有多少项,数组里的每一项是函数的每次执行的返回值

  map(function (item,index,ary){
 *          // return的值是否使用item或index都可以。
 * 		    //return什么就把什么添加到map的返回值那个数组中
 *          return item+index
 *           })

二.var 和 let const区别 &&垃圾回收

什么是变量提升

js中,函数及变量的声明都将被提升到函数的最顶部,变量可以先使用后声明。
var声明变量存在变量提升,let和const不存在
       var声明的变量的范围是函数作用域,let声明的变量和const声明的常量范围是块级作用域,
       let和csont不能在同一块作用域重复声明;var可以
       暂时性死区:在代码块内,使用let命令声明变量之前,该变量都是不可用的
       在全局作用域下用var声明的常量会作为window对象的属性保存,let和const不会。
       const声明的常量在声明时必须赋值,let和var可以只声明不赋值,此时变量的值为undefined。
       const声明的常量在声明后就不能被修改。所谓的不能修改,不是变量的值,而是变量指向那个内存地址保存的数据不能改动。

for(var i=0;i<5;i++){
setTimeout(()=>{
console.log(i)})
} //5

for(let i=0;i<5;i++){
setTimeout(()=>{
console.log(i)})
}//01234

       因为var是全局的,for循环是同步代码,setTimeout是宏任务,在执行setTimeout的时候,for循环早就结 束了,所以取的是全局的i;
而let会产生一个块级作用域,每一次循环都会生成一个单独的块级作用域,在执行setTimeout的时候,回去对应的块级作用域内找i

垃圾回收

垃圾回收GC)是一种自动内存管理形式,它可以删除不再需要的对象。常见的方法有:标记清除:在运行的时候给存储在内存中的所有变量加上标记,引用计数:保存内存中所有资源的引用次数,如果一个值的引用次数是0,表示用不上,可以释放。

三.闭包 &&原型链

1、什么是闭包

闭包是有权访问另一个函数作用域中的变量的函数;他是一个函数,存在于另一个函数当中,简单来说可以说定义在函数内部的函数,闭包可以访问到父级函数的变量,切该变量不会销毁

function person(){
    var name = '有鱼';
    function cat(){
        console.log(name);
    }
    return cat;
}
var per = person();// per的值就是return后的结果,即cat函数
per();// 有鱼 per()就相当于cat()
per();// 有鱼 同上,而且变量name没有销毁,一直存在内存中,供函数cat调用
per();// 有鱼
2.闭包作用

1、隐藏变量,避免全局污染
2、可以读取函数内部的变量
缺点:如果使用不当会导致变量不会被垃圾回收机制回收,造成内存消耗;也可能会造成内存泄漏的问题;
JS规定在一个函数作用域内,程序执行完以后变量就会销毁,这样可节省内存;使用闭包时,按照作用域链的特点,闭包函数外面的变量不会被销毁,因为函数会一直被调用,所以一直存在,如果闭包使用过多就会造成内存销毁。

3.闭包应用

实现变量a 自增
1、通过全局变量,可以实现,但会污染其他程序

var a = 10;
function Add(){
    a++;
    console.log(a);
}
Add();//11
Add();//12
Add();//13

2、定义一个局部变量,不污染全局,但是实现不了递增

var a = 10;
function Add2(){
    var a = 10;
    a++;
    console.log(a);
}
Add2();//11
Add2();//11
Add2();//11
console.log(a);//10

3、通过闭包,可以是函数内部局部变量递增,不会影响全部变量

var a  = 10;
function Add3(){
    var a = 10;
    return function(){
        a++;
        return a;
    };
};
var cc =  Add3();
console.log(cc());//11
console.log(cc());//12
console.log(cc());//13
console.log(a);//10

2.原型链 &&new的过程 && js原型继承的几种方法

原型:
①所有引用类型都有一个__proto__(隐式原型)属性,属性值是一个普通的对象
②所有函数都有一个prototype(原型)属性,属性值是一个普通的对象
③所有引用类型的__proto__属性指向它构造函数的prototype
原型链:
当访问一个对象的某个属性时,会先在这个对象本身属性上查找,如果没有找到,则会去它的__proto__隐式原型上查找,即它的构造函数的prototype,如果还没有找到就会再在构造函数的prototype的__proto__中查找,这样一层一层向上查找就会形成一个链式结构,我们称为原型链。如果Object的原型上还没有,那就不找了,返回undefined。所有实例通过prototype.__proto__一直向上找 ,最后都会指向Object.prototype,可以理解成所有对象都是Object的实例,而Object.prototype.proto 是null。

new的过程

1.新生成一个对象
2.链接到原型
3.绑定this
4.返回新对象

继承方法

1、原型继承:创建一个构造函数,并为其设置私有属性和公有属性。
// 定义一个人类
    function Person (name) {
      // 属性
      this.name = name;
      // 实例方法
      this.sleep = function(){
        console.log(this.name + '正在睡觉!');
      }
    }
    // 原型方法
    Person.prototype.eat = function(food) {
      console.log(this.name + '正在吃:' + food);
    };
2、原型链继承:将父类实例赋值给子类原型对象
// 创建子类、添加子类属性。
   function arrange(name){
     this.name = name;
     this.goShop = function(food){
       console.log(name + '叫你去买'+ food)
     }
   }
   // 将父类实例赋值给子类原型对象
   arrange.prototype = new Person();
   // 将constructor指向本身,保证原型链不断。
   arrange.prototype.constructor = arrange; 

   //创建arrange新实例,也是Person实例;
   var newObj = new arrange('李四'); 
   console.log(newObj instanceof Person)  //true
   console.log(newObj instanceof arrange) //true
   // 原型链继承成功,可以访问父类所有属性;
   console.log(newObj)
   console.log(newObj.name) // 李四
   newObj.sleep(); // 李四正在睡觉!
   newObj.eat('苹果'); // 李四正在吃:苹果
   newObj.goShop('香蕉'); // 李四叫你去买香蕉
3、构造继承:执行父构造,将This指向本身,拉取父私有属性
// 创建子类、添加子类属性。
	function arrange(name){
      Person.call(this,name) // 执行父构造,将This指向本身,拉取父私有属性;
    }
    arrange.prototype.getName = function(){
      console.log('我叫' + this.name)
    }
    var newObj = new arrange('李四');
    console.log(newObj)
    console.log(newObj.name) //李四
    newObj.sleep() // 李四正在睡觉!
    newObj.getName() //我叫李四
4、组合继承:构造继承与原型继承组合。

不要在继承时使用此方法重写或添加方法,否则将会修改整个原型,导致崩溃

// 创建子类、添加子类属性。
	function arrange(name){
      Person.call(this,name) // 执行父构造,将This指向本身,拉取父私有属性;
    }
    // 将父类实例赋值给子类原型对象
    arrange.prototype = new Person(); // 直接拉取父类实例继承原型
    // 将constructor指向本身,保证原型链不断。
    arrange.prototype.constructor = arrange;
    
    // 下面这个方法会替换整个原型对象
    // arrange.prototype = {
    //   init:function(){
    //     console.log('我会替换整个原型')
    //   }
    // }
    // 必须在原型实例继承之后,在给子类原型添加方法,或重写方法,请使用以下方法
    arrange.prototype.eat = function() {
      console.log(this.name + '重写了此方法');
    };
    arrange.prototype.addFn = function(){
      console.log(this.name + '添加了新方法' );
    }
    
    var newObj = new arrange('王五');
    console.log(newObj)
    newObj.eat(); // 王五重写了此方法
    newObj.addFn(); // 王五添加了新方法
5、克隆原型链继承:将等待继承的原型对象克隆,再赋值给继承的原型对象

此种方法会脱轨,不在同一原型链上

// 创建子类、添加子类属性。
	function arrange(name){
      this.name = name;
      this.goShop = function(food){
        console.log(name + '叫你去买'+ food)
      }
    }
    // 创建克隆类型
    function Clone(obj){
      for(var key in obj){
        this[key] = typeof obj[key] == 'object' ? new Clone(obj[key]) : obj[key];
      }
    }
    // 使用Clone构造函数继承原型
    arrange.prototype = new Clone(Person.prototype); 
    // 将constructor指向本身,保证原型链不断。
    arrange.prototype.constructor = arrange; 

    //创建arrange新实例,也是Clone实例,却不在是Person实例;
    var newObj = new arrange('李四'); 
    console.log(newObj instanceof Person)  //false
    console.log(newObj instanceof arrange) //true
    console.log(newObj instanceof Clone) //true
    // 克隆成功,可以访问克隆对象的原型对象;
    console.log(newObj)
    console.log(newObj.name) // 李四
    newObj.eat('苹果'); // 李四正在吃:苹果
    newObj.goShop('香蕉'); // 李四叫你去买香蕉
6、Class 继承

使用 extends 表明继承自哪个父类,并且在子类构造函数中必须调用 super,这段代码可以看成 Animal.call(this, name)。Class 的本质就是函数

    class Cat extends Animal {
        constructor(name) {
            super(name);
            this.name = name || 'Animal';
        }
    }

四.浅拷贝和深拷贝的区别

基本类型(名字和值都保存在栈内存中)
引用类型(名字存在栈内存中。值存在堆内存中,但是栈内存会提供一个引用的地址指向堆内存中的值)
浅拷贝:复制对象的第一次属性:方法:object.assign( target,…sources),拷贝对象的属性的引用,而不是对象本身。Array.prototype.slice(), Array.prototype.concat()
深拷贝:开辟一个新的栈,两个对象属性完全相同,但是对应两个不同的地址,修改一个对象的属性,不会改变另一个对象的属性。jQuery.extend();JSON.stringify();

	假设B复制了A,当修改A时,如果B也跟着变了,说明这是浅拷贝,如果B没变,那就是深拷贝。
例如:解构运算;
let a=[0,1,2,3,4],
   	 b=a;
	console.log(a===b);
	a[0]=1;
	console.log(a,b);
深拷贝:JSON.parse(JSON.stringigy(obj);
循环递归:
functiondeepClone(obj, hash = new WeakMap()){
if(obj ===null)returnobj;// 如果是null或者undefined我就不进行拷贝操作
if(objinstanceofDate)returnnewDate(obj);
if(objinstanceofRegExp)returnnewRegExp(obj);
// 可能是对象或者普通的值  如果是函数的话是不需要深拷贝
if(typeofobj !=="object")returnobj;
// 是对象的话就要进行深拷贝
if(hash.get(obj))returnhash.get(obj);
letcloneObj =newobj.constructor();
// 找到的是所属类原型上的constructor,而原型上的 constructor指向的是当前类本身
hash.set(obj, cloneObj);
for(letkeyinobj) {
if(obj.hasOwnProperty(key)) {
// 实现一个递归拷贝
cloneObj[key] = deepClone(obj[key], hash);
}
}
returncloneObj;
}

五.跨域解决

       跨域需要针对浏览器的同源策略来理解,同源策略指的是请求必须是同一个端口,同一个协议,同一个域名,不同源的客户端脚本在没有明确授权的情况下,不能读写对方资源。受同源策略影响,不是同源的脚本不能操作其他源下面的对象。想要操作另一个源下的对象就需要跨域。


1、Jsonp:只能get,不安全、有缓存、大小限制:jsonp只能是get请求,利用的script标签的src属性不会跨域的原理,在客户端注册一个函数,然后把这个「函数的函数名名」传给服务器,然后后端返回「函数名」执行,把数据当参数传进去
2、iframe :window.name、document.domin、location.hash、post message
3、cors-服务端配置:

  res.header("Access-Control-Allow-Origin", "*");//允许哪些域名跨域,*代表所有
  res.header("Access-Control-Allow-Headers", "content-type");//允许哪些自定义请求头字段,jwt的时候会用到
  res.header("Access-Control-Allow-Methods", "PUT,POST,GET,DELETE,OPTIONS");//允许哪些请求方式
  res.header("Access-control-Allow-Credentials", "true");//应该是缓存

在这里插入图片描述
4、http Proxy webpack添加proxy( 开发的时候,可以在webpack配置proxy代理)

六.flex布局

1.flex-direction:决定主轴的方向: row | row-reverse | column | column-reverse;

row(默认值):主轴为水平方向,起点在左端。
row-reverse:主轴为水平方向,起点在右端。
column:主轴为垂直方向,起点在上沿。
column-reverse:主轴为垂直方向,起点在下沿。

2.flex-wrap:换行:nowrap | wrap | wrap-reverse;

nowrap(默认):不换行。
wrap:换行,第一行在上方。
wrap-reverse:换行,第一行在下方。

3.flex-flow:是flex-direction属性和flex-wrap属性的简写形式,默认值为row nowrap;

4.justify-content:定义在主轴对齐方式:flex-start | flex-end | center | space-between | space-around;

flex-start(默认值):左对齐
flex-end:右对齐
center: 居中
space-between:两端对齐,项目之间的间隔都相等。
space-around:每个项目两侧的间隔相等。所以,项目之间的间隔比项目与边框的间隔大一倍。

5.align-items:定义在交叉轴上对齐:flex-start | flex-end | center | baseline | stretch;

flex-start:交叉轴的起点对齐。
flex-end:交叉轴的终点对齐。
center:交叉轴的中点对齐。
baseline: 项目的第一行文字的基线对齐。
stretch(默认值):如果项目未设置高度或设为auto,将占满整个容器的高度。

6.align-content:定义多跟轴线的对齐方式:flex-start | flex-end | center | space-between | space-around | stretch;

flex-start:与交叉轴的起点对齐。
flex-end:与交叉轴的终点对齐。
center:与交叉轴的中点对齐。
space-between:与交叉轴两端对齐,轴线之间的间隔平均分布。
space-around:每根轴线两侧的间隔都相等。所以,轴线之间的间隔比轴线与边框的间隔大一倍。
stretch(默认值):轴线占满整个交叉轴。

7.项目的属性order、flex-grow、flex-shrink、flex-basis、flex、align-self

order:定义项目的排列顺序。数值越小,排列越靠前,默认为0。
flex-grow:定义项目的放大比例,默认为0,即如果存在剩余空间,也不放大。
flex-shrink:定义了项目的缩小比例,默认为1,即如果空间不足,该项目将缩小。
flex-basis:定义了在分配多余空间之前,项目占据的主轴空间。浏览器属性,计算主轴是否有多余空间。默认值为auto。
flex:是flex-grow, flex-shrink 和 flex-basis的简写,默认值为0 1 auto。后两个属性可选。
属性有两个快捷值:auto (1 1 auto) 和 none (0 0 auto)。
align-self:允许单个项目有与其他项目不一样的对齐方式,可覆盖align-items属性。默认值为auto,表示继承父元素的align-items属性,如果没有父元素,则等同于stretch。该属性可能取6个值,除了auto,其他都与align-items属性完全一致。

七.事件循环(event loop)

JS事件循环

        js内存分为堆内存和栈内存,堆内存中存的是自己声明的object类型的数据,栈内存中存的是基本数据类型和函数执行时的运行空间,同步代码就放在执行栈中,而异步代码的话,浏览器会将dom事件,例如ajax、setTimeout等放到队列中,等待执行栈中的代码都执行完毕,才会执行队列中的代码。以上不断重复的过程就是eventloop
        微任务:promise,宏任务包含:setTimeout,定时器;
        执行栈中的代码永远最先执行的,当执行栈中的代码执行完毕,会在宏任务队列之前先看看微任务队列有没有任务,如果有会先将微任务队列中的任务清空才会去执行宏任务,等待执行栈和微任务都执行完毕才会继续执行,并且在执行完每一个宏任务之后,会去看看微任务有没有新添加的任务,如果有,会先将微任务队列中的任务清空,才会继续执行下一个宏任务。如此循环,JS的执行顺序就是每次事件循环中的宏任务和微任务。

console.log(1);
setTimeout(() => {
    console.log(2);    
}, 0);
console.log(3);

根据之前说的,setTimeout 会被放到队列中,等待执行栈中的代码执行完毕才会执行,所以会输出1, 3, 2
但是异步代码也是有区别的:

console.log(1)

setTimeout(() => {
    console.log(2)
}, 0)

Promise.resolve().then(() => {
    console.log(3)
})

输出的永远是1, 3, 2,也就是说 promisesetTimeout之前执行了。这是因为异步任务分为 微任务(microtask)宏任务(task),执行的顺序是 执行栈中的代码 => 微任务 => 宏任务

setTimeout(() => {
    console.log('timeout1')
    Promise.resolve().then(() => {
        console.log('promise1')
    })
    Promise.resolve().then(() => {
        console.log('promise2')
    })
}, 100)

setTimeout(() => {
    console.log('timeout2')
    Promise.resolve().then(() => {
        console.log('promise3')
    })
}, 200)
  1. 先将两个setTimeout塞到宏任务队列中
  2. 当第一个setTimeout1时间到了执行的时候,首先打印timeout1,然后在微任务队列中塞入promise1promise2
  3. 当第一个setTimeout1执行完毕后,会去微任务队列检查是不是空的,他发现了有两个promise,会把两个promise按顺序执行完再去执行下一个宏任务
  4. 两个promise执行完毕后会微任务队列中没有任务了,会去宏任务中执行下一个任务 setTimeout2
  5. setTimeout2执行的时候,先打印一个timeout2,然后又在微任务队列中塞了一个promise2
  6. setTimeout2执行完毕后会去微任务队列检查,发现有一个promise3,会将promise3执行
  7. 会依次打印 timeout1 promise1 promise2 timeout2 promise3

练习题
async function async1() {
      console.log("async1 start");
      await async2();
      console.log("async1 end");
    }
    async function async2() {
      console.log("async2");
    }
    console.log("script start");
    setTimeout(function () {
      console.log("setTimeout");
    }, 0)
    async1();
    new Promise(function (resolve) {
      console.log("promise1");
      resolve();
    }).then(function () {
      console.log("promise2");
    });
    console.log("script end");

输出依次为:script start==>async1 start==>async2==>promise1==>script end==>async1 end==>promise2==>setTimeout
在这里插入图片描述

Node事件循环

node的微任务:process.nextTric promise
宏任务 setTimeout setInterval;
       Node.js是基于ChromeV8引擎的JS运行环境,等于是能够让js在服务端运行,但是node中的event loop是用libuv模拟的,他将不同的任务分配给不同的线程,形成一个事件循环,以异步的方式将任务的执行结果返回给V8引擎。
       Node中的Event Loop会在每次切换队列的时候,清空微任务队列,也就会将当前队列都执行完,在进入下一阶段的时候检查一下微任务中有没有任务

setTimeout(() => {
    console.log('timeout1')
    Promise.resolve().then(() => {
        console.log('promise1')
    })
    Promise.resolve().then(() => {
        console.log('promise2')
    })
}, 0)

setTimeout(() => {
    console.log('timeout2')
    Promise.resolve().then(() => {
        console.log('promise3')
    })
}, 0)
  1. 先将两个setTimeout塞到宏任务队列中
  2. 当第一个setTimeout1时间到了执行的时候,首先打印timeout1,然后在微任务队列中塞入promise1promise2
  3. 当第一个setTimeout1执行完毕后,继续执行下一个setTimeout2
  4. setTimeout2执行的时候,先打印一个timeout2,然后又在微任务队列中塞了一个promise2
  5. 当前宏任务队列清空,进入下一阶段,去检查微任务队列中有没有任务
  6. 清空微任务队列
  7. 在node环境中执行 会依次打印timeout1 timeout2 promise1 promise2 promise3

八.Promise

用Promise解决什么问题

1.回调地狱。代码难以维护,可读性差;
2.promise可以支持多个并发的请求,获取并发请求中的数据;
3.可以解决异步问题,本身不能说promise是异步;

什么是Promise

是异步编程的解决方案,promise是一个对象,可以获取异步操作的消息,可以把Promise看成一个状态机,可以通过函数 resolve 和 reject ,将状态转变为 resolved 或者 rejected 状态,状态一旦改变,就不会再变,创建promise实例后,它会立即执行;then函数会返回一个Promise实例,并且改返回值是一个新的实例。

API

1.Promise.resolve(value)
有时需要将现有对象转为Promise对象。参数分为四种:
1、参数是promise实例他不做任何修改,原封不动的返回这个实例
2、是一个thenable对象 这个thenable是指具有then方法的对象,resolve会将这个对象转为Promise对象, 然后立即执行thenable对象的then()方法
3、参数不是具有then()或不是对象,如果参数是一个原始值,resolve()返回一个新的promise对象,状态为resolved
4、不带任何参数不带参数,直接返回一个resolved状态的Promise对象。

//如果传入的 value 本身就是 Promise 对象,则该对象作为 Promise.resolve 方法的返回值返回。  
function fn(resolve){
    setTimeout(function(){
        resolve(123);
    },3000);
}
let p0 = new Promise(fn);
let p1 = Promise.resolve(p0);
// 返回为true,返回的 Promise 即是 入参的 Promise 对象。
console.log(p0 === p1);
------------------------------------------------------------------------------------
//如果传入的 value 本身就是 thenable 对象,返回的 promise 对象会跟随 thenable 对象的状态。
let promise = Promise.resolve($.ajax('/test/test.json'));// => promise对象
promise.then(function(value){
   console.log(value);
});
------------------------------------------------------------------------------------
let p1 = Promise.resolve(123); 
//打印p1 可以看到p1是一个状态置为resolved的Promise对象
console.log(p1)

2.Promise.reject
返回一个新的 Promise 实例,该实例的状态为rejected,该方法的参数,会原封不动作为reject的理由,变成后续方法的参数
3.Promise.prototype.then
then方法是定义在原型对象上,作用是为promise实例添加状态改变时的回调函数,then方法第一个参数是resolved状态的回调函数,第二个参数是rejected状态的回调函数可选的,返回一个新的promise实例,可采用链式写法,then后面再调用另一个then方法。then中的回调函数可以有一个参数,也可以不带参数,如果then中的回调函数依赖上一步的返回结果,那就带上参数。
4.Promise.prototype.catch
是.then(null,rejected)的别名,用于指定发生错误时的回调函数,promise对象后面跟catch()方法,可以处理promise内部发生的错误,catch()返回的还是一个promise对象,后面可以接着调用then()方法
5.Promise.prototype.finally()
不管promise对象最后状态如何,都会执行操作。不接受任何参数,本质上是then方法的特例
6.Promise.all
用于将多个Promise实例,包装成一个新的Promise实例。可以接受一个数组为参数,参数也可以不是参数,但必须具有Iterator接口,且返回的每个成员都是Promie实例。
多个 Promise 任务同时执行。如果全部成功执行,则以数组的方式返回所有 Promise 任务的执行结果。 如果有一个 Promise 任务 rejected,则只返回 rejected 任务的结果。
7.Promise.race
也是将多个Promise实例,包装秤一个新的promise实例。但是只要数组中有一个实例第一个改变,p状态就跟着改变,而第一个改变的Promise实例的返回值,就传递给p的回调函数

const p=Promise.race([p1,p2,p3])

Promise的使用总结 和async语法

1、首先初始化一个promise对象,可以通过两种方式创建,都会返回一个promise对象
(1)、new Promise(fn)
(2)、Promise.resolve(fn)
2、然后调用上一步返回的promise对象的then方法,注册回调函数

new Promise(fn)
.then(fn1(value){
//处理value
})
---------------------------------------------------------------------------
(async ()=>{
    let 蔬菜 = await 买菜();
    let 饭菜 = await 做饭(蔬菜);
    let 送饭结果 = await 送饭(饭菜);
    let 通知结果 = await 通知我(送饭结果);
})();

4、最后注册catch异常处理函数,处理前面回调中可能抛出的异常。

Promsie 与事件循环

Promise在初始化时,传入的函数是同步执行的,然后注册 then 回调。注册完之后,继续往下执行同步代码,在这之前,then 中回调不会执行。同步代码块执行完毕后,才会在事件循环中检测是否有可用的 promise 回调,如果有,那么执行,如果没有,继续下一个事件循环。

九.async和await

什么是async和await

他是Generator函数的语法糖,而Generator函数的执行必须靠执行器,所以有co模块,async 函数返回一个 Promise 对象,可以使用 then 方法添加回调函数。当函数执行的时候,一旦遇到 await 就会先返回,等到触发的异步操作完成,再接着执行函数体内后面的语句。需要注意的是await 命令后面的 Promise 对象,运行结果可能是 rejected,所以最好把 await 命令放在 try…catch 代码块中。await只能用在async函数之中,如果在普通函数就会报错。

假设函数getJSON返回值是 Promise,并且 Promise resolves 有一些JSON 对象。
我们只想调用它并且记录该JSON并且返回完成。
  1)使用Promise:
  const makeRequest = () =>
        getJSON().then(data => {
            console.log(data)
            return "done"
        })
    makeRequest()
 2)使用Async:
 const makeRequest = async () => {
        // await getJSON()表示console.log会等到getJSON的promise成功reosolve之后再执行。
        console.log(await getJSON)
        return "done"
    }
    makeRequest()

async/await和promise的区别

1、promise解决了回调函数导致的‘回调地狱’问题,then链式方法如果遇到复杂的代码,他不美观不易维护,而async使异步代码看起来像同步代码,这句代码执行完,才会执行下一句。可以很好的处理then链,避免了嵌套代码。
2、他俩都是非阻塞的;
3、async是基于promise实现的,相当于改良版的promise,不能用于普通的回调函数;更好的处理了then链,如果滥用await可能会导致性能问题,await会组赛代码
4、reject状态:
1)promise错误可以通过catch来捕捉,建议尾部捕获错误,
2)async/await既可以用.then又可以用try-catch捕捉

对于单一的 Promise 链其实并不能发现 async/await 的优势,
当需要处理由多个 Promise 组成的 then 链的时候,优势就能体现出来了,
/**
 * 传入参数 n,表示这个函数执行的时间(毫秒)
 * 执行的结果是 n + 200,这个值将用于下一步骤
 */
function takeLongTime(n) {
    return new Promise(resolve => {
        setTimeout(() => resolve(n + 200), n);
    });
}
function step1(n) {
    console.log(`step1 with ${n}`);
    return takeLongTime(n);
}
function step2(n) {
    console.log(`step2 with ${n}`);
    return takeLongTime(n);
}
function step3(n) {
    console.log(`step3 with ${n}`);
    return takeLongTime(n);
}
--------------------------------------------------------------------
  现在用 Promise 方式:
function doIt() {
    console.time("doIt");
    const time1 = 300;
    step1(time1)
        .then(time2 => step2(time2))
        .then(time3 => step3(time3))
        .then(result => {
            console.log(`result is ${result}`);
            console.timeEnd("doIt");
        });
}
doIt();
输出结果 result 是 step3() 的参数 700 + 200 = 900。doIt() 顺序执行了三个步骤,
一共用了 300 + 500 + 700 = 1500 毫秒,和 console.time()/console.timeEnd() 计算的结果一致。
---------------------------------------------------------------------async/await 的方式:
async function doIt() {
    console.time("doIt");
    const time1 = 300;
    const time2 = await step1(time1);
    const time3 = await step2(time2);
    const result = await step3(time3);
    console.log(`result is ${result}`);
    console.timeEnd("doIt");
}
doIt();

十.箭头函数和普通函数的区别:

十三、箭头函数和普通函数的区别:

1、箭头函数都是匿名函数;不能使用new构造函数
2、箭头函数的this指向不同,箭头函数本身没有this,会捕获所在的上下文的this值,作为自己的this值;
3、箭头函数不绑定arguments,取而代之用rest参数…解决;
4、箭头函数没有prototype原型对象;没有super;
5、箭头函数的this永远指向其上下文的this,任何方法都改变不了指向,如call();普通函数的this指向调用它的那个对象;

this指向

1、绑定事件中,事件绑定的谁,this就是谁
2、函数执行的时候,.前面是谁就是谁,如果没有.就是window
3、自运行函数的this永远是window
4、构造函数中的this是当前实例
5、call和apply可以强制改变this
6、定时器中的this永远是window,回调函数的this一般也是window

十一.浏览器运行机制 && 重绘和回流

1.运行机制:

1)构建DOM树:渲染引擎解析HTML文档。将标签转换成DOM树找那个的DOM。
2)构建渲染树:解析CSS样式
3)布局渲染树:从根节点递归调用,计算每个元素的大小、位置等,给出每个节点在屏幕上出现的位置
4)绘制渲染树:遍历渲染树,使用UI后断层来绘制每个节点。

2.重绘:

当盒子的大小、位置以及其他属性,例如知道属性的颜色,字体大小等,浏览器会把这些属性绘制一遍,将内容呈现在页面上,重绘是指一个元素外观的改变所触发的浏览器行为,浏览器会根据元素的新属性重新绘制,使元素呈现新的外观。
触发条件:改变元素外观属性:如:color,font-size。

3.回流:

当渲染树中的一部分因为元素的规模尺寸,布局,隐藏等改变而需要重新构建,被称为回流,每个页面至少需要一次回流,就是在页面第一次加载的时候。
触发回流的条件:任何页面布局和几何属性的改变都会触发回流,例如:
1.页面渲染初始化(无法避免)
2.添加或删除可见的DOM元素
3.元素位置的改变,或使用动画
4.元素尺寸的改变——大小,边距
5.浏览器窗口尺寸的变化(resize事件发生时)
6.填充内容的改变,比如文本的改变或图片大小改变而引起的计算值宽高的改变
7.读取某些元素属性:(offsetLeft/Top/Height/Width, clientTop/Left/Width/Height, scrollTop/Left/Width/Height, width/height, getComputedStyle(), currentStyle(IE))
重绘回流的代价:耗时,导致浏览器卡慢;
重绘和回流的关系:回流必定会引发重绘,但重绘不一定会回流

4.优化:

1.浏览器自己的优化:浏览器会维护1个队列,把所有会引起回流、重绘的操作放入这个队列,等队列中的操作到了一定的数量或者到了一定的时间间隔,浏览器就会flush队列,进行一个批处理。这样就会让多次回流、重绘变成一次回流重绘。

2.减少重绘和回流就是要减少对渲染树的操作,可以合并多次的DOM和样式修改。并减少style样式的请求。
1)直接改变元素的className
2)display:none;先设置元素巍峨display:none,然后进行页面布局等操作;设置完成后将元素设置为display:block;这样就只引发两次重绘和回流
3)不要经常访问浏览器的flush队列属性:如果一定要访问,利用缓存。将访问的值存储起来,下面使用就不会引起回流
4)使用cloneNode(true or false)和replaceChild,引发一次回流和重绘
5)将需要多次回流的元素,position设为absolute或fixed,元素脱离文档流,他的变化不会影响到其他元素
6)如果需要创建多个DOM节点,可以使用DocumentFragment创建完后一次性加入document

var fragment = document.createDocumentFragment();
 
var li = document.createElement('li');
li.innerHTML = 'apple';
fragment.appendChild(li);
 
var li = document.createElement('li');
li.innerHTML = 'watermelon';
fragment.appendChild(li);
 
document.getElementById('fruit').appendChild(fragment);

7)尽量不使用table布局

十二.事件委托

事件冒泡

事件冒泡会从当前触发的事件目标一级一级往上传递,依次触发,直到document为止。

事件捕获

事件捕获会从document开始触发,一级一级往下传递,依次触发,直到真正事件目标为止。

事件委托/事件代理

利用事件冒泡的特性,通过监听一个父元素,来给不同的子元素绑定事件,减少监听次数,从而提升速度。

写一个事件委托的函数?
<ul id=“root”>        
<li>1</li>      
<li>2</li>        
<li>3</li>
</ul>

老版本的IE存在兼容问题,根本不支持addEventListener()的添加事件和removeEventListener()的删除事件,它有自己的监听方法:

// 添加事件,事件流固定为冒泡
attachEvent(事件名,事件处理函数)
// 删除事件
detachEvent(事件名,事件处理函数)

IE里的事件对象是window.event,事件源是srcElement,阻止冒泡写法也不一样:

function() {
    // IE里阻止冒泡
    window.event.cancelBubble = true;
    // IE里获取事件源的id
    var srcID = window.event.srcElement.id;
}
function(event) {
    // 非IE里阻止冒泡
    event.stopPropagation();
    // 非IE里获取事件源的id
    var srcID = event.target.id;
}

如果元素被阻止冒泡,不要用事件委托的方式监听事件,因为事件委托的原理是利用事件冒泡,当冒泡被阻止,就无法监听了。

十三、call、apply、bind区别

call和apply都是为了解决改变this 的指向,作用一样,传参不一样。call可以接受一个参数列表,apply只接受一个参数数组;bind和他俩作用一样,只是该方法会返回一个函数,并且可以通过bind实现柯理化。
柯理化函数将接受多个参数的函数可变成接受一个参数的函数,可以理解为函数嵌套,每个函数返回一个新函数并接受一个新的参数,知道最后返回计算结果。普通函数:函数名(参数1,参数2,…);柯理化:函数名(参数1)(参数2)…

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值