异步

MDN:异步指两个或两个以上的方针或工作不一起存在或产生(或多个相关事物的产生无需等候其前一事物的完结)。

怎样了解异步和同步

同步:同步是指一个进程在履行某个恳求的时分,假如该恳求需求一段时刻才干回来信息,那么这个进程会一向等候下去,直到收到回来信息才持续履行下去。

异步:异步是指进程不需求一向等候下去,而是持续履行下面的操作,不论其他进程的情况,当有信息回来的时分会通知进程进行处理,这样就能够进步履行的效率了,即异步是咱们宣布的一个恳求,该恳求会在后台主动宣布并获取数据,然后对数据进行处理,在此过程中,咱们能够持续做其他操作,不论它怎样宣布恳求,不关心它怎样处理数据。

js是单线程言语,可是有时分需求比及某个时机才会履行某些操作,也便是异步操作。比方:

console.log('one');
setTimeout(() => {
    console.log('two');
    setTimeout(() => {
        console.log('three')
    }, 2000);
}, 2000);
console.log('six')
// one
// six
// two
// three

假如不断需求延时回调,就会呈现一列回调小火箭,咱们称之为回调地狱。虽然功用上不存在问题,可是难以保护。

promise

prmoise是一个方针,专门用于处理异步操作,该方针有三个情况:进行中(Pending)、已完结(Resolved)、已失利(Rejected),当时情况仅由异步操作的成果决议,不受任何其他操作操控,且一旦履行就无法取消。

Promise 有两个特点:

  • 方针情况不受外界影响;
  • 一旦情况改动了就不会再变。

也便是说,Promise任何时分都只要一种情况。

function timeout(data){
    return new Promise((resolve,reject) => {
        setTimeout(function() {
            if(data){
                resolve(data)
            }else{
                reject(data)
            }
        }, 2000);
    })
}
<!--resolve函数履行后,-->
<!--promise方针情况将会从“进行中”变为“已完结”,-->
<!--reject则是变为“已失利”,-->
<!--基于此咱们能够运用then办法别离指定“成功”与“失利”情况的回调函数-->
timeout("hello").then(function(){
    console.log("resolve");
},function(){
    console.log("reject")
})
// resolve
timeout().then(function(){
    console.log("resolve");
},function(){
    console.log("reject")
})
// reject

Promise根本用法

能够通过Promise结构函数创立Promise方针,Promise结构函数承受两个参数:resolve和reject,由JS引擎供给。当Promise方针转移到成功的时分,调用resolve函数并将操作成果作为参数传递出去,当Promise方针情况变成失利时,reject函数将报出的过错作为参数传递出去。

只要Promise方针中调用resolve和reject的时分,才会履行then里边的内容。

function greet() {
  var promise = new Promise(function (resolve, reject) {
    var greet = "hello  world";
    resolve(greet);
  });
  return promise;
}
greet().then((v) => {
  console.log('resolve',v); 
},(s)=>{
  console.log('reject',s)
});

注意:创立一个Promise方针会当即履行里边的代码,所认为了更好操控代码运转的时刻,能够将其包含在一个函数中,并将这个promise回来。

Promise的then办法有三个参数:成功回调,失利回调,前进回调。一般情况下只完结第一个,后边的是可选的。

catch用法

function greet() {
  var promise = new Promise(function (resolve, reject) {
    var greet = "hello  world";
    reject(greet);
  });
  return promise;
}
greet()
  .then((v) => {
    console.log("resolve", v);
  })
  .catch(function () {
    console.log("catch");
  });

这个时分catch履行的是和reject相同的,也便是说假如Promise的情况变为reject时,会被catch捕捉到,不过需求特别注意的是假如前面设置了reject办法的回调函数,则catch不会捕捉到情况变为reject的情况。catch还有一点不同的是,假如在resolve或许reject产生过错的时分,会被catch捕捉到,这与java,c++的过错处理时相同的,这样就能避免程序卡死在回调函数中了。

promise的api

  • Promise.all()中的Promise序列会悉数履行通过才认为是成功,不然认为是失利;
  • Promise.race()中的Promise序列中第一个履行结束的是通过,则认为成功,假如第一个履行结束的Promise是回绝,则认为失利;
  • Promise.any()中的Promise序列只要有一个履行通过,则认为成功,假如悉数回绝,则认为失利;
  • Promise.allSettled()只要比及参数数组的一切 Promise 方针都产生情况改动(不论是fulfilled仍是rejected),回来的 Promise 方针才会产生情况改动。

完结契合 Promise/A+ 规范的 Promise

参阅

Promise/A+规范

Promise/A+规范概况看这里

  1. promise 有三个情况:pending,fulfilled,or rejected;「规范 Promise/A+ 2.1」
  2. new promise时, 需求传递一个executor()履行器,履行器当即履行;
  3. executor承受两个参数,别离是resolve和reject;
  4. promise 的默许情况是 pending;
  5. promise 有一个value保存成功情况的值,能够是undefined/thenable/promise;「规范 Promise/A+ 1.3」
  6. promise 有一个reason保存失利情况的值;「规范 Promise/A+ 1.5」
  7. promise 只能从pending到rejected, 或许从pending到fulfilled,情况一旦确认,就不会再改动;
  8. promise 有必要有一个then办法,then 接收两个参数,别离是 promise 成功的回调 onFulfilled, 和 promise 失利的回调 onRejected;「规范 Promise/A+ 2.2」
  9. 假如调用 then 时,promise 现已成功,则履行onFulfilled,参数是promise的value;
  10. 假如调用 then 时,promise 现已失利,那么履行onRejected, 参数是promise的reason;
  11. 假如 then 中抛出了反常,那么就会把这个反常作为参数,传递给下一个 then 的失利的回调onRejected;

根本的Promise

针对以上规范,咱们大体上能够总结出如下promise完结的计划:

const PENDING = 'pending';
const FULFILLED = 'fulfilled';
const REJECTED = 'rejected';
class Promise {
    // 4.传入一个executor履行器
    constructor(executor) {
        // 1.寄存情况
        this.status = 'pending';
        // 2.寄存成功情况的值
        this.value = undefined;
        // 3.寄存失利情况的值
        this.reason = undefined;
        // 6.为executor供给resolve函数
        let resolve = (value) => {
            // 情况为 PENDING 时才干够更新情况,避免 executor 中调用了两次 resovle/reject 办法
            if (this.status === PENDING) {
                this.status = FULFILLED;
                this.value = value
            }
        }
        // 7.为executor供给reject函数
        let reject = (reason) => {
            // 情况为 PENDING 时才干够更新情况,避免 executor 中调用了两次 resovle/reject 办法
            if (this.status === PENDING) {
                this.status === REJECTED;
                this.reason = reason
            }
        }
        try {
            // 5.当即履行executor,将resolve和reject传递给运用者
            executor(resolve, reject)
        } catch (error) {
            // 产生反常时履行失利逻辑
            reject(error)
        }
    }
    // 8.promise有必要有then办法,调用成功和失利时对应的办法
    then(onFulfilled, onRejected) {
        if (this.status === FULFILLED) {
            onFulfilled(this.value)
        }
        if (this.status === REJECTED) {
            onRejected(this.reason)
        }
    }
}

简单测验一下:

// test
const promise = new Promise((resolve, reject) => {
    resolve('成功');
}).then(
    (data) => {
        console.log('success', data)
    },
    (err) => {
        console.log('faild', err)
    }
)
// 操控台输出--->success 成功

假如executor是异步操作呢:

const promise = new Promise((resolve, reject) => {
    // 传入一个异步操作
    setTimeout(() => {
        resolve('成功');
    }, 1000);
}).then(
    (data) => {e
        console.log('success', data)
    },
    (err) => {
        console.log('faild', err)
    }
)

操控台什么都没有打印。原因是当调用then办法时,promise还处于pending情况,所以并没有履行onFulfilled或许onRejected。

所以,咱们应当在履行then函数时,将成功或许失利的回调函数存储起来,在executor()的异步使命被履行时,触发 resolve 或 reject,顺次调用成功或失利的回调。

满足异步的Promise

const PENDING = 'pending';
const FULFILLED = 'fulfilled';
const REJECTED = 'rejected';
class Promise {
    // 4.传入一个executor履行器
    constructor(executor) {
        // 1.寄存情况
        this.status = 'pending';
        // 2.寄存成功情况的值
        this.value = undefined;
        // 3.寄存失利情况的值
        this.reason = undefined;
        this.onResolvedCallbacks = []
        this.onRejectedCallbacks = []
        // 6.为executor供给resolve函数
        let resolve = (value) => {
            // 情况为 PENDING 时才干够更新情况,避免 executor 中调用了两次 resovle/reject 办法
            if (this.status === PENDING) {
                this.status = FULFILLED;
                this.value = value;
                this.onResolvedCallbacks.forEach(fn => fn())
            }
        }
        // 7.为executor供给reject函数
        let reject = (reason) => {
            // 情况为 PENDING 时才干够更新情况,避免 executor 中调用了两次 resovle/reject 办法
            if (this.status === PENDING) {
                this.status === REJECTED;
                this.reason = reason;
                this.onRejectedCallbacks.forEach(fn => fn())
            }
        }
        try {
            // 5.当即履行executor,将resolve和reject传递给运用者
            executor(resolve, reject)
        } catch (error) {
            // 产生反常时履行失利逻辑
            reject(error)
        }
    }
    // 8.promise有必要有then办法,调用成功和失利时对应的办法
    then(onFulfilled, onRejected) {
        if (this.status === FULFILLED) {
            onFulfilled(this.value)
        }
        if (this.status === REJECTED) {
            onRejected(this.reason)
        }
        if (this.status === PENDING) {
            // 假如promise的情况是 pending,需求将 onFulfilled 和 onRejected 函数寄存起来,等候情况确认后,再顺次将对应的函数履行
            this.onResolvedCallbacks.push(() => {
                onFulfilled(this.value)
            });
            // 假如promise的情况是 pending,需求将 onFulfilled 和 onRejected 函数寄存起来,等候情况确认后,再顺次将对应的函数履行
            this.onRejectedCallbacks.push(() => {
                onRejected(this.reason);
            })
        }
    }
}

测验一下:


// test
const promise = new Promise((resolve, reject) => {
    // 传入一个异步操作
    setTimeout(() => {
        resolve('成功');
    }, 3000);
}).then(
    (data) => {
        console.log('success', data)
    },
    (err) => {
        console.log('faild', err)
    }
)

三秒之后操控台打印内容:success 成功

then 的链式调用&值穿透特性

在咱们运用 Promise 的时分,当 then 函数中 return 了一个值,不论是什么值,咱们都能鄙人一个 then 中获取到,这便是所谓的then 的链式调用。并且,当咱们不在 then 中放入参数,例:promise.then().then(),那么这今后边的 then 依旧能够得到之前 then 回来的值,这便是所谓的值的穿透

for example: 链式调用:

const promise = new Promise((resolve, reject) => {
    resolve('promise resolve')
}).then(
    (data) => {
        console.log('success', data)
        return '第一个then resolve'
    },
    (err) => {
        console.log('faild', err)
    }
).then(res=>{
    console.log(res)
})
// success promise resolve
// 第一个then resolve

穿透性:

const promise = new Promise((resolve, reject) => {
    // 传入一个异步操作
    resolve('promise resolve')
}).then()
    .then(res => {
        console.log(res)
    })
// promise resolve

那详细怎样完结链式调用和穿透性呢?简单思考一下,假如每次调用 then 的时分,咱们都重新创立一个 promise 方针,并把上一个 then 的回来成果传给这个新的 promise 的 then 办法,不就能够一向 then 下去了么?

结合 Promise/A+ 规范整理一下思路:

  1. then 的参数 onFulfilled 和 onRejected 能够缺省,假如 onFulfilled 或许 onRejected不是函数,将其疏忽,且依旧能够鄙人面的 then 中获取到之前回来的值;「规范 Promise/A+ 2.2.1、2.2.1.1、2.2.1.2」
  2. promise 能够 then 屡次,每次履行完 promise.then 办法后回来的都是一个“新的promise”;「规范 Promise/A+ 2.2.7」
  3. 假如 then 的回来值 x 是一个一般值,那么就会把这个成果作为参数,传递给下一个 then 的成功的回调中;
  4. 假如 then 中抛出了反常,那么就会把这个反常作为参数,传递给下一个 then 的失利的回调中;「规范 Promise/A+ 2.2.7.2」
  5. 假如 then 的回来值 x 是一个 promise,那么会等这个 promise 履行完,promise 假如成功,就走下一个 then 的成功;假如失利,就走下一个 then 的失利;假如抛出反常,就走下一个 then 的失利;「规范 Promise/A+ 2.2.7.3、2.2.7.4」
  6. 假如 then 的回来值 x 和 promise 是同一个引证方针,造成循环引证,则抛出反常,把反常传递给下一个 then 的失利的回调中;「规范 Promise/A+ 2.3.1」
  7. 假如 then 的回来值 x 是一个 promise,且 x 一起调用 resolve 函数和 reject 函数,则第一次调用优先,其他一切调用被疏忽;「规范 Promise/A+ 2.3.3.3.3」

async

promise方针的链式串联办法虽然处理了回调函数的层层嵌套问题,可是then操作过多过长时仍是会呈现冗余臃肿的情况,此时能够运用async函数。

function timeout(data){
    //async 和 promise 结合运用 
    return new Promise((resolve,reject) => { 
        setTimeout(function(){
            if(data){
                resolve(data);
            }else{
                reject(data);
            }
        }, 2000);
    })
}
async function run(){
    console.log('aget run start');
    await timeout('agent');
    console.log('agent run end');
} 
console.log('no_await');
run();
// no_await
// aget run start
// agent run end

async 函数是 Generator 函数的语法糖。运用 要害字 async 来表明,在函数内部运用 await来表明异步,await要害字只能用在async界说的函数内。async函数便是将 Generator 函数的星号(*)替换成async,将yield替换成await。比Generator更语义化。

相对于promise的优势

  • 语法简练,同步代码履行异步操作。
  • 过错处理

promise中,try/catch不能处理promise内部的过错,因为promise方针抛出的过错不会传递到外层。所以捕获反常需求运用.catch,这样过错处理代码非常冗余。并且,在咱们的实际出产代码会愈加杂乱。

//promise捕获过错
const makeRequest = () => {
  try {
    getJSON()
      .then(result => {
        // JSON.parse或许会犯错
        const data = JSON.parse(result)
        console.log(data)
      })
      // 处理异步代码的过错
      .catch((err) => {
        console.log(err)
      })
  } catch (err) { //此处catch无效
    console.log(err)
  }
}
//async捕获过错
const makeRequest = async () => {
  try {
    // this parse may fail
    const data = JSON.parse(await getJSON())
    console.log(data)
  } catch (err) {
    console.log(err)
  }
}

这里有一点需求注意,当 async 函数中只要一个 await 呈现 reject 情况,则后边的 await 都不会被履行。所以要把第一个await放在try…catch结构里边,这样不论这个异步操作是否成功,第二个await都会履行。假如有多个await指令,能够统一放在try…catch结构中

async function main() {
  try {
    const val1 = await fetchDataA();
    const val2 = await fetchDataB();
    const val3 = await fetchDataC();
  }
  catch (err) {
    console.error(err);
  }
}

async/await 的履行次序

async回来什么

先看看看以下代码的输出值:

async function asyncReturn() {
     return 'async回来的是什么?'
}
var result = asyncReturn();
console.log(result); //Promise { 'async回来的是什么?' }

从成果中能够看到async函数回来的是一个promise方针,假如在函数中 return 一个直接量,async 会把这个直接量通过 Promise.resolve()封装成 Promise 方针。

假如async函数没有回来值:

async function testAsync1() {
    console.log("hello async");
}
let result1 = testAsync1();
console.log(result1); //Promise{<fulfilled>: undefined}
await做了什么处理

从字面意思上看await便是等候,await 等候的是一个表达式,这个表达式的回来值能够是一个promise方针也能够是其他值。

很多人认为await会一向等候之后的表达式履行完之后才会持续履行后边的代码,实际上await是一个让出线程的标志。await后边的函数会先履行一遍,然后就会跳出整个async函数来履行后边js栈的代码。等本轮工作循环履行完了之后又会跳回到async函数中等候await后边表达式的回来值,假如回来值为非promise则持续履行async函数后边的代码,不然将回来的promise放入promise行列(Promise的Job Queue)。

function testSometing() {
    console.log("2-履行testSometing");  //3--> 打印2-履行testSometing
    return "5-testSometing";            //4--> 回来值,await让出线程,向下履行
}
async function testAsync() {
    console.log("6-履行testAsync");     //11-->打印6-履行testAsync
    return Promise.resolve("8-hello async"); //12-->回来值,await让出线程,履行别的代码(promise)
}
async function test() {                 //全体从调用test开端
    console.log("1-test start...");     //1-->打印1-test start...
    const v1 = await testSometing();    //2-->履行到这去履行testSometing()
    console.log(v1);                    //9-->打印v1:5-testSometing
    const v2 = await testAsync();       //10-->履行testAsync()
    console.log(v2);                    //14-->打印8-hello async
    console.log(v1, v2);                //15-->打印5-testSometing,8-hello async,结束
}
test();
var promise = new Promise((resolve)=> { //5-->履行promise
console.log("3-promise start..");       //6--> 打印3-promise start..
resolve("7-promise");});//要害点2       //7-->将promise放入promise的行列
promise.then((val)=> console.log(val)); //13-->打印7-promise,回来test()
console.log("4-test end...")            //8-->打印4-test end... 本轮工作循环履行结束,跳回到async函数test()中。

加上seTimeout看看成果怎样:

async function async1() {
    console.log("async1 start");
    await async2();
    console.log("async1 end");
    await async3()
    await async4()
}
async function async3() {
    console.log("async3")
}
async function async4() {
    console.log('async4')
}
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
async3
promise2
async4
settimeout*/

再看一个比方:

var a = 0
var b = async () => {
  a = a + await 10
  console.log('2', a) // -> '2' 10
  a = (await 10) + a
  console.log('3', a) // -> '3' 20
}
b()
a++
console.log('1', a) // -> '1' 1
输出成果:
1 1
2 10
3 20

解析:

  • 首要函数 b 先履行,在履行到 await 10 之前变量 a 仍是 0,因为在 await 内部完结了 generators ,generators 会保留堆栈中东西,所以这时分 a = 0 被保存了下来
  • 因为 await 是异步操作,遇到await就会当即回来一个pending情况的Promise方针,暂时回来履行代码的操控权,使得函数外的代码得以持续履行,所以会先履行 console.log(‘1’, a)
  • 这时分同步代码履行结束,开端履行异步代码,将保存下来的值拿出来运用,这时分 a = 10
  • 然后后边便是常规履行代码了

generator

Generator 函数是 ES6 供给的一种异步编程处理计划,语法行为与传统函数彻底不同

特征:

  • function 指令与函数名之间有一个星号
  • 函数体内部运用 yield 句子界说不同的内部情况
function *f(){
    yield "hello";
    yield "world";
    return "!"
}
let fg=f();
  // 调用遍历器方针的 next 办法,使得指针移向下一个情况,直到遇到下一条 yield 句子(或 return 句子)停止
  // done 为 true 时遍历结束
console.log(fg.next());
console.log(fg.next());
console.log(fg.next());
// { value: 'hello', done: false }
// { value: 'world', done: false }
// { value: '!', done: true }

模块化

咱们都知道在前期JavaScript模块这一概念,都是通过script标签引进js文件代码。当然这写根本简单需求没有什么问题,但当咱们的项目越来越庞大时,咱们引进的js文件就会越多,这时就会呈现以下问题:

  • js文件效果域都是顶层,这会造成变量污染
  • js文件多,变得欠好保护
  • js文件依靠问题,稍微不注意次序引进错,代码全报错

为了处理以上问题JavaScript社区呈现了CommonJs,CommonJs是一种模块化的规范,包含现在的NodeJs里边也选用了部分CommonJs语法在里边。那么在后来Es6版别正式加入了Es Module模块,这两种都是处理上面问题,那么都是处理什么问题呢。

  • 处理变量污染问题,每个文件都是独立的效果域,所以不存在变量污染
  • 处理代码保护问题,一个文件里代码非常明晰
  • 处理文件依靠问题,一个文件里能够清楚的看到依靠了那些其它文件

CommonJS

CommonJS的一个模块便是一个脚本文件,通过履行该文件来加载模块。CommonJS规范规则,每个模块内部,module变量代表当时模块。这个变量是一个方针,他的exports特点(即module.exports)是对外接口。加载某个模块,其实便是加载该模块的module.exports特点。

咱们见过这样模块引证:

var myModule = require('module');
myModule.sayHello();

这是因为咱们把模块的办法界说在了模块的特点上:

//module.js
module.exports.sayHello = function(){
    console.log('Hello');
}

还能够换另外一种形式:

module.exports = sayHello;
//调用
var sayHello = require('module');
sayHello();

require指令第一次加载该脚本时就会履行整个脚本,然后在内存中生成一个方针(模块能够屡次加载,可是在第一次加载时才会运转,成果被缓存),这个成果长这样:


{
    id:'...',
    exports:{...},
    loaded:true,
    ...
}

常用形式:

//myModel.js
var name = 'Byron';  
function printName(){  
    console.log(name);  
}  
function printFullName(firstName){  
    console.log(firstName + name);  
}  
module.exports = {  
    printName: printName,  
    printFullName: printFullName
}
//引证
var nameModule = require('./myModel.js');
nameModule.printName();

Node.js的模块机制完结便是参照CommonJS的规范。可是Node.js额外做了一件事,即为每个模块供给了一个exports变量,以指向module.exports,这相当于在每个模块最开端,写这么一行代码:

var exports = module.exports;

CommonJS的特点:

  • 一切代码都运转在模块效果域,不会污染大局效果域。
  • 独立性是模块的重要特点,模块内部最好不与程序的其他部分直接交互。
  • 模块能够屡次加载,可是只会在第一次加载时运转一次,然后运转成果就被缓存了,今后再加载,就直接读取缓存成果。要想让模块再次运转,有必要清除缓存。
  • 模块加载的次序,依照其在代码中呈现的次序。

ES Modules

ES Modules 的模块化才干由 export 和 import 组成,export 指令用于规则模块的对外接口,import 指令用于输入其他模块供给的功用。咱们能够这样界说一个模块:

// 第一种办法
export var firstName = 'Michael';
export var lastName = 'Jackson';
export var year = 1958;
// 第二种办法
var firstName = 'Michael';
var lastName = 'Jackson';
var year = 1958;
export { firstName, lastName, year };

然后再这样引进他们:

import { firstName, lastName, year } from 'module';
import { firstName as newName } from 'module';
import * as moduleA from 'module';

咱们常用的办法:

// file a.js
export function a() {}
export function b() {}
// file b.js
export default function() {}
import {a, b} from './a.js'
import XXX from './b.js'

CommonJs和Es Module的差异

CommonJs

  • CommonJs能够动态加载句子,代码产生在运转时
  • CommonJs混合导出,仍是一种语法,只不过不必声明前面方针而已,当我导出引证方针时之前的导出就被覆盖了
  • CommonJs导出值是复制,能够修正导出的值,这在代码犯错时,欠好排查引起变量污染

Es Module

  • Es Module是静态的,不能够动态加载句子,只能声明在该文件的最顶部,代码产生在编译时
  • Es Module混合导出,单个导出,默许导出,彻底互不影响
  • Es Module导出是引证值之前都存在映射关系,并且值都是可读的,不能修正

参阅链接

防抖和节省

防抖和节省的效果都是避免函数屡次调用。差异在于,假设一个用户一向触发这个函数,且每次触发函数的距离小于wait,防抖的情况下只会调用一次,而节省的情况会每隔必定时刻(参数wait)调用函数。

防抖技术便是能够把多个次序的调用合并成一次,也便是在必定时刻内,操控工作被触发的次数。比方,500ms内没有接连触发两次scroll工作时才会触发函数,即一向滚动过程中不会一向触发,只要中止500ms时才干够触发。

可是防抖函数也存在问题。比方咱们希望下滑过程中图片被不断加载出来,而不是停下时才被加载。又或许下滑时分数据的ajax恳求也是同理。这类场景咱们就会用到另一种技巧,节省函数。

节省函数答应一个函数在X秒内履行一次。与防抖比较多了一个mustRun特点,代表mustRun毫秒内,必然会触发一次函数,大概功用便是假如在一段时刻内 scroll 触发的距离一向短于 500ms ,那么能确保工作咱们希望调用的 handler 至少在 1000ms 内会触发一次。

防抖

<!DOCTYPE html>
<html>
<head> 
<meta charset="utf-8"> 
<title>Debouncing 防抖</title> 
</head>
<body>
<div style="width: 100%;height: 500px;background-color: aqua;">div1</div>
<div style="width: 100%;height: 500px;background-color: brown;">div2</div>
<script>
    function debounce(func,wait){
        var timeout;
        return function(){
            clearTimeout(timeout);
            timeout=setTimeout(func,wait);
        }
    };
    function realFunc(){
        console.log('success!!!!!!!')
    };
    // 选用了防抖
    window.addEventListener('scroll',debounce(realFunc,500));//500ms内没有接连触发两次scroll工作,才会真实触发咱们真实想在scroll工作中触发的函数。
    // 没选用防抖
    window.addEventListener('scroll',realFunc);//只要是出发了scroll就打印
</script>
</body>
</html>

防抖技术便是能够把多个次序的调用合并成一次,也便是在必定时刻内,操控工作被触发的次数。上边比方中,500ms内没有接连触发两次scroll工作时才会触发函数,即一向滚动过程中不会一向触发,只要中止500ms时才干够触发。

可是防抖函数也存在问题。比方咱们希望下滑过程中图片被不断加载出来,而不是停下时才被加载。又或许下滑时分数据的ajax恳求也是同理。这类场景咱们就会用到另一种技巧,节省函数。

节省

节省函数答应一个函数在X秒内履行一次。与防抖比较多了一个mustRun特点,代表mustRun毫秒内,必然会触发一次函数,看简单实例:

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title>throttling 节省</title>
</head>
<body>
    <div style="width: 100%;height: 5000px;background-color: aqua;">div1</div>
    <div style="width: 100%;height: 5000px;background-color: brown;">div2</div>
    <script>
        function throrttle(func, wait, mustRun) {
            var timeout,
                startTime = new Date();
            return function () {
                var context = this,
                    args = arguments,
                    curTime = new Date();
                clearTimeout(timeout);
                if (curTime - startTime >= mustRun) {
                    func.apply(context, args);
                    startTime = curTime;
                } else {
                    timeout = setTimeout(func, wait);
                }
            }
        };
        function realFunc(){
            console.log('success!!!');
        };
        window.addEventListener('scroll',throrttle(realFunc,500,1000));
    </script>
</body>
</html>

大概功用便是假如在一段时刻内 scroll 触发的距离一向短于 500ms ,那么能确保工作咱们希望调用的 handler 至少在 1000ms 内会触发一次。

map办法

map 办法会给原数组中的每个元素都按次序调用一次callback 函数。callback 每次履行后的回来值(包含 undefined)组合起来构成一个新数组。 callback 函数只会在有值的索引上被调用;那些历来没被赋过值或许运用 delete 删去的索引则不会被调用。

var arr = [1,2,3];
var new_arr = arr.map(e => e*2);
console.log(new_arr); //[ 2, 4, 6 ]
var another_arr =arr.forEach(e => e*3)
console.log(another_arr) //undefined
console.log(arr)//[ 1, 2, 3 ]

map生成一个新数组,当你不计划运用回来的新数组却运用map是违反规划初衷的,请用forEach或许for-of替代。

不该运用map:

  • 你不计划运用回来的新数组
  • 你没有从回调函数中回来值。
callback 函数会被主动传入三个参数:数组元素,元素索引,原数组本身。
var arr = [1,2,3];
var map_canshu = arr.map((a,b,c) => {
    console.log('a-->',a);
    console.log('b-->',b);
    console.log('c-->',c);
})
/*
a--> 1
b--> 0
c--> [ 1, 2, 3 ]
a--> 2
b--> 1
c--> [ 1, 2, 3 ]
a--> 3
b--> 2
c--> [ 1, 2, 3 ]
*/

flatMap办法

FlatMap 和 map 的效果几乎是相同的,可是对于多维数组来说,会将原数组降维。

var arr1 = [1, 2, 3, 4];
arr1.map(x => [x * 2]); 
// [[2], [4], [6], [8]]
arr1.flatMap(x => [x * 2]);
// [2, 4, 6, 8]

目前该函数在浏览器中还不支持。

reduce办法

reduce()办法对数组中的每个元素履行一个由您供给的reducer函数(升序履行),将其成果汇总为单个回来值。

var redArr = [1,2,3,4];
var reducer = (accumulator, currentValue) => accumulator + currentValue;
console.log(redArr.reduce(reducer));  //10

reducer 函数接收4个参数:

  • Accumulator (acc) (累计器)
  • Current Value (cur) (当时值)
  • Current Index (idx) (当时索引)
  • Source Array (src) (源数组)

reducer函数的回来值分配给累计器,该回来值在数组的每个迭代中被记住,并最后成为终究的单个成果值

proxy

什么是Proxy

Proxy 也便是署理,能够协助咱们完结很多工作,例如对数据的处理,对结构函数的处理,对数据的验证,说白了,便是在咱们拜访方针前增加了一层阻拦,能够过滤很多操作,而这些过滤,由你来界说。

语法

let p = new Proxy(target , handler)
  • target :需求运用Proxy包装的方针方针(能够是任何类型的方针,包含原生数组,函数,乃至另一个署理)

  • handler: 一个方针,其特点是当履行一个操作时界说署理的行为的函数(能够了解为某种触发器)。

办法

handler.get()

该办法用于阻拦方针的读取特点操作。

var p = new Proxy(target, {
  get: function(target, property, receiver) {
  }
});

参数:

  • target:方针方针。
  • property:被获取的特点名。
  • receiver:Proxy或许继承Proxy的方针
  • 回来值:能够回来恣意值

束缚:假如违反了以下的束缚,proxy会抛出 TypeError:

  • 假如要拜访的方针特点是不可写以及不可装备的,则回来的值有必要与该方针特点的值相同。
  • 假如要拜访的方针特点没有装备拜访办法,即get办法是undefined的,则回来值有必要为undefined。

示例:

var p = new Proxy({}, {
  get: function(target, prop, receiver) {
    console.log("called: " + prop);
    return 10;
  }
});
console.log(p.a); // "called: a"

违反束缚情况:

var obj = {};
Object.defineProperty(obj, "a", { 
  configurable: false, 
  enumerable: false, 
  value: 10, 
  writable: false 
});
var p = new Proxy(obj, {
  get: function(target, prop) {
    return 20;
  }
});
p.a; //会抛出TypeError

handler.set()

该办法用于阻拦设置特点值的操作。

语法:

 new Proxy(target, {
  set: function(target, property, value, receiver) {
  }
});

参数:

  • target:方针方针。
  • property:被设置的特点名。
  • value:被设置的新值。
  • receiver:最初被调用的方针。一般是proxy本身,但handler的set办法也有或许在原型链上或以其他办法被间接地调用(因此不必定是proxy本身)。比方,假设有一段代码履行 obj.name =”jen”,obj不是一个proxy且本身不含name特点,但它的原型链上有一个proxy,那么那个proxy的set阻拦函数会被调用,此时obj会作为receiver参数传进来。
  • 回来值:set办法应该回来一个布尔值,回来true代表此次设置特点成功了,假如回来false且设置特点操作产生在严厉形式下,那么会抛出一个TypeError。

束缚:假如违反以下的束缚条件,proxy会抛出一个TypeError:

  • 若方针特点是不可写及不可装备的,则不能改动它的值。
  • 假如方针特点没有装备存储办法,即set办法是undefined的,则不能设置它的值。
  • 在严厉形式下,若set办法回来false,则会抛出一个 TypeError 反常。

示例:

var p = new Proxy({}, {
  set: function(target, prop, value, receiver) {
    target[prop] = value;
    console.log('property set: ' + prop + ' = ' + value);
    return true;
  }
})
console.log('a' in p);  // false
p.a = 10;               // "property set: a = 10"
console.log('a' in p);  // true
console.log(p.a);       // 10

根底事例

var handler = {
    get:function(target,name){
    return name in target ? target[name] : 37;
    }
};
var p = new Proxy({},handler);
p.a = 1;
p.b = undefined;
console.log(p.a,p.b); //1 undefined
console.log('c' in p, p.c); //false 37

无操作转发署理

在以下比方中,咱们运用了一个原生 JavaScript方针,署理会将一切应用到它的操作转发到这个方针上。

let target = {};
let p = new Proxy(target, {});
p.a = 37;   // 操作转发到方针
console.log(target.a);    // 37. 操作现已被正确地转发

署理p将一切应用于他的操作转发到target

验证

通过署理,你能够轻松地验证向一个方针的传值。这个比方运用了set。

let handler = {
    set: function (obj, prop, value) {
        if (prop === 'age') {
            if (!Number.isInteger(value)) {
                throw new TypeError('The age is not an integer');
            }
            if (value > 200) {
                throw new RangeError('The age seems invalid');
            }
        }
        // The default behavior to store the value
        obj[prop] = value;
        // 表明成功
        return true;
    }
};
        let person = new Proxy({}, handler);
        person.age = 100;
        console.log(person.age);
        // 100
        person.age = 'young';
        // 抛出反常: Uncaught TypeError: The age is not an integer
        person.age = 300;
        // 抛出反常: Uncaught RangeError: The age seems invalid

上边示例相当于,每逢向person里边增加特点时,会阻拦操作,判别特点值是否满足要求,满足要求就存起来,不然就抛出反常。

0.1+0.2!==0.3

先来个示例:

console.log(0.1 + 0.2 == 0.3) //false
console.log(0.1 + 0.2 > 0.3) //true

原因在于在JS中选用的IEEE 754的双精度规范,将数字存储为双精度浮点数,核算机内部存储数据的编码的时分,0.1在核算机内部根本就不是准确的0.1,而是一个有舍入差错的0.1。当代码被编译或解释后,0.1现已被四舍五入成一个与之很接近的核算机内部数字,以至于核算还没开端,一个很小的舍入过错就现已产生了。这也便是 0.1 + 0.2 不等于0.3 的原因。

另外要注意,不是一切浮点数都有舍入差错。二进制能准确地表明位数有限且分母是2的倍数的小数,比方0.5,0.5在核算机内部就没有舍入差错。所以0.5 + 0.5 === 1

怎样处理这类问题

用整数表明

最好的办法便是咱们想办法规避掉这类小数核算时的精度问题就好了,那么最常用的办法便是将浮点数转化成整数核算。因为整数都是能够准确表明的。一般的处理办法 便是 把核算数字提升 10 的N次方倍,再除以 10的N次方。一般都用 1000 就行了。

console.log((0.1*1000 + 0.2 * 1000)/1000 == 0.3) //true

bignumber.js

bignumber.js会在必定精度内,让浮点数核算成果契合咱们的希望。

{
  let x = new BigNumber(0.1);
  let y = new BigNumber(0.2)
  let z = new BigNumber(0.3)
  console.log(z.equals(x.add(y))) // 0.3 === 0.1 + 0.2, true
  console.log(z.minus(x).equals(y)) // true
  console.log(z.minus(y).equals(x)) // true
}

原生

parseFloat((0.1 + 0.2).toFixed(10));
console.log(parseFloat((0.1 + 0.2).toFixed(10))) //0.3

script标签中的defer和async

(1)defer特点规则是否推迟履行脚本,直到页面加载停止。async特点规则脚本一旦可用,就异步履行。

(2)defer并行加载JavaScript文件,会依照页面上script标签的次序履行。async并行加载JavaScript文件,下载完结当即履行,不会依照页面上script标签的次序履行。

详细如下图:

JS面试题(二)

蓝色线代表网络读取,赤色线代表履行时刻,这俩都是针对脚本的;绿色线代表 HTML 解析。

也便是说async是乱序的,而defer是次序履行,这也就决议了async比较适用于百度分析或许谷歌分析这类不依靠其他脚本的库。从图中能够看到一个一般的<script> 标签的加载和解析都是同步的,会堵塞DOM的烘托,这也便是咱们经常会把<script>写在<body>底部的原因之一,为了避免加载资源而导致的长时刻的白屏,另一个原因是js或许会进行DOM操作,所以要在DOM悉数烘托完后再履行。

js和css是怎样堵塞DOM解析和页面烘托的

参阅链接

css不会堵塞DOM的解析,会堵塞页面烘托

浏览器是解析DOM生成DOM Tree,结合CSS生成的CSS Tree,终究组成render tree,再烘托页面。由此可见,在此过程中CSS彻底无法影响DOM Tree,因此无需堵塞DOM解析。可是,DOM Tree和CSS Tree会组合成render tree。所以也能够得到,css是会堵塞页面烘托的。

看接下来代码:

<header>
    <link rel="stylesheet" href="/css/sleep3000-common.css">
    <script src="/js/logDiv.js"></script>
</header>

答案是浏览器会转圈圈三秒,但此过程中不会打印任何东西,之后呈现出一个浅蓝色的div(common.css),再打印出null。成果好像CSS不单堵塞了页面烘托,还堵塞了DOM 的解析。

其实堵塞DOM解析的是js,假如js脚本中要获取元素的款式,宽高等css操控的特点,浏览器是需求核算的,也便是依靠于css的,只好等一切的款式加载完了之后再履行js。

<script><link>一起在头部的话,<script>在上或许会更好,之所所以或许,是因为假如<link>的内容下载更快的话,是没影响的,但反过来的话,JS就要等候了,可是这些等候的时刻是彻底不必要的。

js堵塞DOM的解析和页面的烘托

浏览器并不知道脚本的内容是什么,假如先行解析下面的DOM,万一脚本内全删了后边的DOM,浏览器就白干活了。更别谈丧尽天良的document.write。浏览器无法预估里边的内容,那就干脆悉数停住,等脚本履行完再干活就好了。

对此的优化其实也很显而易见,详细分为两类:

  • 假如JS文件体积太大,一起你确认没必要堵塞DOM解析的话,无妨按需求加上defer或许async特点,此时脚本下载的过程中是不会堵塞DOM解析的。
  • 假如是文件履行时刻太长,无妨分拆一下代码,不必当即履行的代码,能够运用一下曾经的黑科技:setTimeout()。当然,现代的浏览器很聪明,它会“偷看”之后的DOM内容,碰到如<link>、<script>和<img>等标签时,它会协助咱们先行下载里边的资源,不会傻比及解析到那里时才下载。

定论:

  • CSS 不会堵塞 DOM 的解析,但会堵塞页面烘托。
  • JS 堵塞 DOM 解析,但浏览器会”偷看”DOM,预先下载相关资源。
  • 浏览器遇到 <script>且没有defer或async特点的 标签时,会触发页面烘托(浏览器不知道脚本的内容,因此碰到脚本时,只好先烘托页面,确保脚本能获取到最新的DOM元素信息,虽然脚本或许不需求这些信息),因此假如前面CSS资源没有加载结束时,浏览器会等候它加载结束在履行脚本。
  • <script>最好放底部,<link>最好放头部,假如头部一起有<script><link>的情况下,最好将<script>放在<link>上面

嵌入js和外部js的差别

内部js会堵塞整个DOM的解析,外部js只会堵塞这今后边js的解析:

<!--index.js-->
var count = 0;
for (var i = 0; i < 100000; i++) {
    for (var j = 0; j < 10000; j++) {
        count++;
    }
}
console.log(count);
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <style>
        div {
            width: 100px;
            height: 100px;
            background: lightgreen;
        }
    </style>
</head>
<body>
    <div>div1</div>
    <script src="https://juejin.im/post/index.js"></script>
    <div>div2</div>
    <div>div3</div>
</body>
</html>
<!-- 呈现浅绿色div1,之后转圈,打印100000,呈现浅绿色div1,div2 -->
<!-- ------------------------------------------------------ -->
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <style>
        div {
            width: 100px;
            height: 100px;
            background: lightgreen;
        }
    </style>
</head>
<body>
    <div>div1</div>
    <script>
        var count = 0;
        for (var i = 0; i < 100000; i++) {
            for (var j = 0; j < 10000; j++) {
                count++;
            }
        }
        console.log(count);
    </script>
    <div>div2</div>
    <div>div3</div>
</body>
</html> 
<!-- 转两圈之后,打印100000,呈现浅绿色div1,div2,div3-----嵌入的js堵塞整个DOM解析 -->
<!-- ------------------------------------------------------------------ -->
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <script defer src="https://juejin.im/post/index.js"></script>
    <style>
        div {
            width: 100px;
            height: 100px;
            background: lightgreen;
        }
    </style>
</head>
<body>
    <div>div1</div>
</body>
</html> 
<!-- 加了defer之后:先呈现浅绿色div,在打印100000----defer将及时履行延时到了DOM加载完结时(并未延时下载) -->
<!-- ---------------------------------------------------------------------- -->
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <script src="https://juejin.im/post/index.js"></script>
    <style>
        div {
            width: 100px;
            height: 100px;
            background: lightgreen;
        }
    </style>
</head>
<body>
    <div>div1</div>
</body>
</html>
<!-- 转两圈,打印100000,呈现浅绿色div---js堵塞DOM解析和烘托 -->

encodeURL和decodeURL

encodeURL()用于将URL转换为十六进制编码

decodeURL()用于将编码的URL转换为正常的URL

浏览器多个标签页之间的通讯

调用localStorage

在一个标签页里边运用 localStorage.setItem(key,value)增加(修正、删去)内容;

在另一个标签页里边监听 storage 工作。即可得到 localstorge 存储的值,完结不同标签页之间的通讯。

<!--index1.html -->
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <style></style>
</head>
<body>
    <input type="button" id="btn" value="提交">
    <script>
        var bt = document.getElementById('btn')
        bt.onclick = function () {
            localStorage.setItem("name", 'I am the value of name');
        };
    </script>
</body>
</html>
<!-- index2.html -->
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <style></style>
</head>
<body>
    <script>
        window.addEventListener("storage", function (event) {
            console.log(event.key + "=" + event.newValue);
        });
    </script>
</body>
</html>

调用cookie + setInterval()

即将传递的信息存储在cookie中,每隔必定时刻读取cookie信息,即可随时获取要传递的信息。

<!--index1.html-->
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <style></style>
</head>
<body>
    <input type="button" id="btn" value="提交">
    <script>
        var bt = document.getElementById('btn')
        bt.onclick = function () {
            document.cookie="username=John Doe";
        };
    </script>
</body>
</html>
<!--index2.html-->
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <style></style>
</head>
<body>
    <script>
        function getCookie(key) {
           return document.cookie;
        }
        setInterval(function () {
            console.log("name=" + getCookie("name"));
        }, 10000);    
    </script>
</body>
</html>

null 和undefined

首要看一个判别题:null和undefined 是否持平

console.log(null==undefined)//true
console.log(null===undefined)//false

观察能够发现:null和undefined 两者持平,可是当两者做全等比较时,两者又不等。

null类型,代表“空值”,代表一个空方针指针,运用typeof运算得到object,能够认为它是一个特别的方针值。null用来表明没有存在的方针,常用来表明函数企图回来一个不存在的方针。是不该该有值。

undefined类型,当一个声明晰一个变量未初始化时,得到的便是undefined。是应该有值,可是没有赋值

null表明”没有方针”,即该处不该该有值。典型用法是:

  • 作为函数的参数,表明该函数的参数不是方针。
  • 作为方针原型链的结尾。

undefined表明”短少值”,便是此处应该有一个值,可是还没有界说。典型用法是:

  • 变量被声明晰,但没有赋值时,就等于undefined。
  • 调用函数时,应该供给的参数没有供给,该参数等于undefined。
  • 方针没有赋值的特点,该特点的值为undefined。
  • 函数没有回来值时,默许回来undefined。

JavaScript中不同类型的过错有几种

Load time errors :该过错产生于加载网页时,例如呈现语法过错等情况,称为加载时刻过错,并且会动态生成过错。

Run time errors :因为在HTML言语中滥用指令而导致的过错。

Logical Errors :这是因为在具有不同操作的函数上履行了过错逻辑而产生的过错。

  1. EvalError – Eval过错 该方针表明大局函数 eval()中产生的过错。能够通过结构函数创立这个方针的实例。

  2. ReferenceError-引证过错 该方针会在引证未界说的变量时触发,也能够通过结构函数创立这个方针的实例。

  3. RangeError-规模过错 该过错方针会在值超过有用规模时触发,也能够通过结构函数创立这个方针的实例。

  4. SyntaxError-语法过错 该过错方针在运用不合法的语法结构时触发,也能够通过结构函数创立这个方针的实例。

  5. TypeError-类型过错 该方针会在方针用来表明值的类型非预期类型时触发,也能够通过结构函数创立这个方针的实例。

  6. URIError-URI过错 该过错会在过错运用大局URI函数 如encodeURI()、decodeURI()等时触发。也能够通过结构函数创立该方针的实例。

为什么会呈现ES6新特性

每一次规范的诞生都意味着言语的完善,功用的加强。JavaScript言语本身也有一些令人不满意的当地。

  • ES6的方针,是使得JavaScript言语能够用来编写大型的杂乱的应用程序,成为企业级开发言语。

  • 变量提升特性增加了程序运转时的不可预测性

  • 语法过于松懈,完结相同的功用,不同的人或许会写出不同的代码

set和map差异

  1. 初始化需求的值不相同,Map需求的是一个二维数组,而Set 需求的是一维 Array 数组
  2. Map 是键值对的存在,值不作为健;而 Set 没有 value 只要 key,value 便是 key;
  3. Map 和 Set 都不答应键重复
  4. Map的键是不能修正,可是键对应的值是能够修正的;Set不能通过迭代器来改动Set的值,因为Set的值便是键。

根本数据类型和引证数据类型的差异

  • 贮存方位不同
  • 复制成果不同
  • 拜访办法不同

不必promise,等候一切恳求回来后履行回调

  • 设置一个flag变量,然后在各自的ajax的成功回调内去保护这个变量数量(比方flag++),当满足条件时,咱们来触发后续函数
  • 使⽤Generator
    // 使⽤Generator次序执⾏三次异步操作
    function* r(num) {
      yield ajax1(num);
      yield ajax2();
      yield ajax3();
    }
    // ajax为异步操作,结合Promise使⽤能够轻松完结异步操作行列
    function ajax1(num) {
      return new Promise(resolve => {
        setTimeout(() => {
          console.log('第1个异步恳求'); // 输出处理成果
          resolve()
        }, 1000);
      });
    }
    function ajax2() {
      return new Promise(resolve => {
        setTimeout(() => {
          console.log('第2个异步恳求'); // 输出处理成果
          resolve(); // 操作成功
        }, 1000);
      });
    }
    function ajax3() {
      return new Promise(resolve => {
        setTimeout(() => {
          console.log('第3个异步恳求'); // 输出处理成果
          resolve(); // 操作成功
        }, 1000);
      });
    }
    // 不使⽤递归函数调⽤
    let it = r();
    // 修正为可处理Promise的next
    function next(data) {
      let { value, done } = it.next(data); // 发动
      if (!done) {
        value.then(() => {
          next();
        });
      } else {
        console.log('履行结束!');
      }
    }
    next();
    

new出来方针、结构函数、一般函数、object之间原型和原型链的差异和联系

new出来的方针原型指向结构函数的原型方针

结构函数的原型指向其结构函数的原型方针,终究指向Object的结构函数的原型方针,原型链的顶端都是null

Number创立出来的数值,instanceof判别出来是什么类型

instanceof这个运算符是用来测验一个方针的原型链上是否有该原型的结构函数,即instanceof左表达式要是一个方针,右侧表达式要是一个结构函数,并且左边是右侧实例化出来的方针才会回来true

123 instanceof Number
new Number(123) instanceof Number     
Number(123) instanceof Number

第一个 首要左边为Number类型,并不是一个方针,更不是由Number实例化出来的(根本包装类型),所认为false

第二个 左边运用Number结构实例化方针 右侧为Number结构 ,所认为true

第三个 左边没有运用new所以并不是运用结构函数实例化 而是运用Number这个函数回来了一个数字, 所认为false

箭头函数和一般函数的差异

  • 箭头函数不能作为结构函数
  • 箭头函数不绑定arguments,取而代之用rest参数处理
  • this取决于父效果域的this
  • 箭头函数的this不通过call,apply,bind绑定
  • 箭头函数没有prototype

箭头函数为什么不能作为结构函数

new一个结构函数的时分一般会通过以下几步:

  • 创立空方针
  • __proto __指向结构函数的prototype
  • 确认this指向
  • 向方针中增加特点
  • 回来方针

首要箭头函数没有prototype,所以不能确认原型指向;

其次箭头函数的this是取决于父效果域的this,并且不能通过call,apply或许bind进行绑定的

字符串转换成json方针

  1. javascript函数eval() 语法:

    var obj = eval ("(" + txt + ")");  //有必要把文本包围在括号中,这样才干避免语法过错
    

    eval() 函数可核算某个字符串,并履行其中的的 JavaScript 代码。因为 JSON 语法是 JavaScript 语法的子集,JavaScript 函数eval()可用于将 JSON 文本转换为 JavaScript 方针。

    注意:当字符串中包含表达式时,eval() 函数也会编译并履行,转换会存在安全问题。

  2. 浏览器自带方针JSON.parse() 语法:

var obj = JSON.parse(text[, reviver])
//text:必需, 一个有用的 JSON 字符串。解析前要确保你的数据是规范的 JSON 格式,不然会解析犯错。
//reviver: 可选,一个转换成果的函数, 将为方针的每个成员调用此函数。

JSON.parse()比eval()安全,并且速度更快。支持主流浏览器:Firefox 3.5,IE 8,Chrome,Opera 10,Safari 4。

注意:IE8兼容形式,IE 7,IE 6,会存在兼容性问题。

json和xml两者的差异

JSON 与 XML 的相同之处:

  • JSON 和 XML 数据都是 “自我描绘” ,都易于了解。
  • JSON 和 XML 数据都是有层次的结构
  • JSON 和 XML 数据能够被大多数编程言语运用
  • JSON 和 XML 都用于接收 web 服务端的数据。

JSON 与 XML 的不同之处:

  • JSON 不需求结束标签
  • JSON 愈加简略
  • JSON 读写速度更快
  • JSON 能够运用数组

最大的不同是:XML 需求运用 XML 解析器来解析,JSON 能够运用规范的 JavaScript 函数来解析。

  • JSON.parse(): 将一个 JSON 字符串转换为 JavaScript 方针。
  • JSON.stringify(): 于将 JavaScript 值转换为 JSON 字符串。

Promise先resolve再reject,promise是什么情况;Promise先resolve后打印东西会成功吗

示例一:

const promise = new Promise((resolve, reject) => {
    setTimeout(() => {
        reject();
    },1000)
});
console.log(promise);//rejected

示例2:

const promise = new Promise((resolve, reject) => {
    setTimeout(() => {
        console.log('resolve前Promise的情况:',promise);//pending
        resolve();
        console.log('resolve后Promise的情况:',promise);//fulfilled
    })
});

示例3:

const promise = new Promise((resolve, reject) => {
    setTimeout(() => {
        console.log("Promise的情况:resolve前:",promise);//pending
        resolve();
        console.log("Promise的情况:resolve后:",promise); //fulfilled
        reject();
        console.log("Promise的情况:reject后:",promise);//fulfilled
    })
});

示例4:

const promise = new Promise((resolve, reject) => {
    setTimeout(() => {
        console.log("Promise的情况:reject前:",promise);//pending
        reject();
        console.log("Promise的情况:reject后:",promise);//rejected
        resolve();
        console.log("Promise的情况:resolve后:",promise);//rejected
    })
});
console.log('终究情况',promise)//pending

Promise怎样处理反常

promise.catch()能够捕获promise一切情况的反常。包含:

  1. 履行resolve()和reject()对应的promise.then(()=>{},()=>{}) 中的俩回调函数中的反常
  2. Promise.resolve(err)触发的
  3. Promise.reject(err)触发的
// 界说Promise
const initPromise = (status) => {
    return new Promise((resolve, reject) => {
        // status 成功 200,失利 其它
        if (status === 200) {
            resolve(); // 由"pending"变为"fulfilled"
        } else {
            reject('reject reason'); // 由"pending"变为"rejected"
        }
    });
};
// 实例化并调用promise
let testPromise = (status) => {
    const promise = initPromise(status);
    try {
        promise.then(
            () => {
                // resolve走这个回调
                console.log('resolve回调')
                throw new Error('error from then resolve');
            },
            (err) => {
                // rejected走这个回调
                console.log('reject回调',err)
                throw new Error('error from then reject');
            })
            .catch(e => {
                console.log('promise catch捕获:' + e);
            });
    } catch (e) {
        console.log('try catch捕获:' + e);
    }
}
testPromise(100)
//reject回调 reject reason
//promise catch捕获:Error: error from then reject
testPromise(200)
//resolve回调
//promise catch捕获:Error: error from then resolve
// 界说Promise
const initPromise = (status) => {
    return new Promise((resolve, reject) => {
        // status 成功 200,失利 其它
        setTimeout(() => {
            if (status === 200) {
                resolve(); // 由"pending"变为"fulfilled"
            } else {
                reject('reject reason'); // 由"pending"变为"rejected"
            }
        }, 0)
    });
};
// 实例化并调用promise
let testPromise = (status) => {
    const promise = initPromise(status);
    try {
        promise.then(
            () => {
                console.log('resolve回调')
                throw new Error('error from then resolve');
            },
            (err) => {
                // rejected走这个回调
                console.log('reject回调', err)
                throw new Error('error from then reject');
            })
            .catch(e => {
                console.log('promise catch捕获:' + e);
            });
    } catch (e) {
        console.log('try catch捕获:' + e);
    }
}
testPromise(200)
//resolve回调
//promise catch捕获:Error: error from then resolve

由上能够总结出:

  1. promise.catch能够处理resolve和reject回调函数抛出的反常;setTimeout中的 throw new Error不能够处理,可是能够处理setTimeout中的reject
  2. try…catch只能捕获try中抛出的反常

try…catch 无法捕获 setTimeout 和ajax恳求中的异步使命中的过错。能够用window.onerror处理

// 界说Promise
const initPromise = (status) => {
    return new Promise((resolve, reject) => {
        // status 成功 200,失利 其它
        if (status === 200) {
            resolve(); // 由"pending"变为"fulfilled"
        } else {
            reject('reject reason'); // 由"pending"变为"rejected"
        }
    });
};
// 实例化并调用promise
let testPromise = (status) => {
    const promise = initPromise(status);
    try {
        promise.then(
            () => {
                // -------------------------
                try {
                    // resolve走这个回调
                    setTimeout(() => {
                        console.log('resolve回调')
                        throw new Error('error from then resolve');
                    }, 0)
                } catch (e) {
                    console.log('resolve里边的catch')
                }
                // -------------------------
            },
            (err) => {
                // rejected走这个回调
                console.log('reject回调', err)
                throw new Error('error from then reject');
            })
            .catch(e => {
                console.log('promise catch捕获:' + e);
            });
    } catch (e) {
        console.log('try catch捕获:' + e);
    }
}
testPromise(200)
//resolve回调
//Uncaught Error: error from then resolve
// 界说Promise
const initPromise = (status) => {
    return new Promise((resolve, reject) => {
        throw new Error('error!!!')
    });
};
// 实例化并调用promise
let testPromise = (status) => {
    const promise = initPromise(status);
    try {
        promise.then(
            () => {
            },
            (err) => {
                // rejected走这个回调
                console.log('reject回调', err)
                throw new Error('error from then reject');
            })
            .catch(e => {
                console.log('promise catch捕获:' + e);
            });
    } catch (e) {
        console.log('try catch捕获:' + e);
    }
}
testPromise(200)
//reject回调 Error: error!!!
//index1.js:32 promise catch捕获:Error: error from then reject

try…catch可不能够捕获promise反常

不能,try…catch只能捕获同步反常,对于异步反常不能捕获;

promise中的反常能够用promise.catch进行处理;

async/await

async function run() {
    try {
        await Promise.reject(new Error("Oops!"));
    } catch (error) {
        console.log(error.message)// "Oops!"
    }
}
run()

TypeScript

typeScripttypeinterface的差异

选择:能用interface就用interface,不能用的用type

相同点:

  • 都能够用来描绘一个函数或许方针

    interface User {
      name: string
      age: number
    }
    interface SetUser {
      (name: string, age: number): void;
    }
    type User = {
      name: string
      age: number
    };
    type SetUser = (name: string, age: number)=> void;
    
  • 都答应拓宽:可是语法不同

    //interface extends interface
    interface Name { 
      name: string; 
    }
    interface User extends Name { 
      age: number; 
    }
    // type extends type
    type Name = { 
      name: string; 
    }
    type User = Name & { age: number  };
    // interface extends type
    type Name = { 
      name: string; 
    }
    interface User extends Name { 
      age: number; 
    }
    //type extends interface
    interface Name { 
      name: string; 
    }
    type User = Name & { 
      age: number; 
    }
    

不同点:

  1. type能够而interface不可

    • type 能够声明根本类型别名,联合类型,元组等类型

      // 根本类型别名
      type Name = string
      // 联合类型
      interface Dog {
          wong();
      }
      interface Cat {
          miao();
      }
      type Pet = Dog | Cat
      // 详细界说数组每个方位的类型
      type PetList = [Dog, Pet]
      

  • type 句子中还能够运用 typeof 获取实例的 类型进行赋值

    // 当你想获取一个变量的类型时,运用 typeof
    let div = document.createElement('div');
    type B = typeof div
    
  1. interface 能够而 type 不可

    • interface 能够合并重复声明,而重复声明 type ,会报错

      interface User {
        name: string
        age: number
      }
      interface User {
        sex: string
      }
      /*
      User 接口为 {
        name: string
        age: number
        sex: string 
      }
      */
      

typeScript界说挂载在window上的变量类型

在index.d.ts文件中声明:

declare global{
	interface Window{
		val:string
	}
}

.d.ts文件中声明的变量或许模块,在其他文件中不需求运用import导入,能够直接运用。

.d.ts文件中咱们常常能够看到declaredeclare左右便是告知TS编译器你担保这些变量和模块存在,并声明晰相应类型,编译的时分不需求提示过错!

declare 界说的类型只会用于编译时的查看,编译成果中会被删去。

咱们在编写 TS 的时分会界说很多的类型,可是主流的库都是 JS编写的,并不支持类型系统。这个时分你不能用TS重写主流的库,这个时分咱们只需求编写仅包含类型注释的 d.ts 文件,然后从您的 TS 代码中,能够在仍然运用纯 JS 库的一起,取得静态类型查看的 TS 优势。