• js 面试整合1


    1.防抖和节流(性能优化方式之一)

    防抖作用:

    触发高频事件后n秒内函数只会执行一次,如果n秒内高频事件再次被触发,则重新计算时间。

    思路: 每次触发事件时都取消之前的延时调用方法

    function debounce(fn,time) { // fn: 需要制定防抖的函数,一般是一个禁止在短时间内重复请求的函数
    	let timeout = null; // 创建一个标记用来存放定时器的返回值,闭包变量,不会被外部污染,只能通过内部函数改变它的值
    	return function () { 
    		clearTimeout(timeout); // 每当用户输入的时候把前一个 setTimeout clear 销毁掉
    		timeout = setTimeout(() => { // 然后又创建一个新的 setTimeout 重新开始计时, 这样就能保证触发函数的 interval 间隔内如果还有重复事件的话,就不会执行 fn 函数 
    			fn.apply(this, arguments); // 使用apply改变fn函数的this指向,让fn可以使用debounce下的变量timeout
    		}, time); }; 
    }
    function sayHi(e) { 
    	console.log('防抖成功'); 
    } 
    window.addEventListener('resize', debounce(sayHi,1000)); // 防抖
    
    /*************************分割线************************/
    function f1(...arge) { // 拓展运算符接收全部参数 => arge = [arge1,arge2,...]; 属于函数的独特用法
    	console.log(...arge);  // 拓展运算符展开全部参数 => arge1,arge2,...
    }
    let throttleSon = debounce(f1); // 提前init实例
    setTimeout(()=>{
        throttleSon(1);
        throttleSon(2);
        throttleSon(3);
        throttleSon(4,5)
    },1000); // 4,5
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    节流使用场景:

    持续监听高频事件
    如改变浏览器大小时,要随时拿到该元素的size
    直接使用 resize 事件,则会频繁触发,很容易导致卡顿
    节流:无论拉扯速度多快,每隔1000ms只会触发一次

    思路:每次触发事件时都判断当前是否有等待执行的延时函数,有则return,无则执行一次

    function throttle(fn,time) {
       let timer = null
       return function () {
       console.log(timer)
           if(timer) return false;
           timer = setTimeout(() => {
             fn.apply(this,arguments)
             timer = null
           },time)
       }
    }
    function sayHi(...arge) { // 拓展运算符接收全部参数 => arge = [arge1,arge2,...]; 属于函数的独特用法
    	console.log(...arge);  // 拓展运算符展开全部参数 => arge1,arge2,...
    } 
    window.addEventListener('resize', throttle(sayHi,1000));
    /*************************分割线************************/
    function f1(parmas) {
    	console.log(parmas)
    }
    let throttleSon = throttle(f1); // 提前init实例
    setTimeout(()=>{
        throttleSon(1);
        throttleSon(2);
        throttleSon(3);
        throttleSon(4)
    },1000); // 1 
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28

    防抖节流区别:

    防抖(debounce)和节流(throttle)二者为什么一个是clearTimeout,一个是return?

    防抖是触发间隔大于timer才会触发,所以每次在小于间隔time时(等待阶段)触发事件都要清除定时器,然后添加一个新的等待函数进入等待阶段;
    节流是不管触发多少次,只会每间隔time时间才会触发一次,所以用return,因为在等待函数调用之前的触发事件都不会进入等待阶段;

    举例:假设time = 100ms,一人每间隔50ms输入一个字符,连续输入十个,那么500ms后,防抖触发1次,节流触发5次

    js 函数中的 arguments

    arguments 对象是所有(非箭头)函数中都可用的局部变量。你可以使用arguments 对象在函数中引用函数的参数。它是一个对象,包含了传递给函数的所有实参(有序排列),即便是超出了形参的个数,也会包含在内。
    arguments 是一个对象,不是一个 Array 。它类似于Array ,但除了length属性和索引元素之外没有任何Array 属性。
    arguments上的属性

    • arguments.callee:指向当前执行的函数(在 严格模式 下,第5版 ECMAScript (ES5) 禁止使用 arguments.callee())
    • argunments.length:指向传递给当前函数的参数数量
    • arguments.caller:已移除

    2.get请求传参长度的误区、get和post请求在缓存方面的区别

    误区:我们经常说get请求参数的大小存在限制,而post请求的参数大小是无限制的。
    实际上HTTP 协议从未规定 GET/POST 的请求长度限制是多少。对get请求参数的限制是来源与浏览器或web服务器,浏览器或web服务器限制了url的长度。为了明确这个概念,我们必须再次强调下面几点:

    HTTP 协议 未规定 GET 和POST的长度限制 GET的最大长度显示是因为 浏览器和 web服务器限制了 URI的长度

    不同的浏览器和WEB服务器,限制的最大长度不一样 要支持IE,则最大长度为2083byte,若只支持Chrome,则最大长度 8182byte

    补充一个get和post在缓存方面的区别:
    get请求类似于查找的过程,用户获取数据,可以不用每次都与数据库连接,所以可以使用缓存。
    post不同,post做的一般是修改和删除的工作,所以必须与数据库交互,所以不能使用缓存。因此get请求适合于请求缓存。

    3. ES5的继承和ES6的继承有什么区别?

    ES5的继承通过prototype或构造函数机制来实现。ES5的继承实质上是先创建子类的实例对象this,然后再将父类的属性添加到this上(Parent.apply(this); apply 方法 作用是改变 this 指向 让子类的实例this指向父类)。

    ES6的继承机制完全不同,实质上是先创建父类的实例对象this(所以必须先调用父类的super()方法),然后再用子类的构造函数修改this。
    具体的:ES6通过class关键字定义类,里面有构造方法,类之间通过extends关键字实现继承。子类必须在constructor方法中调用super方法,否则新建实例报错。因为子类没有自己的this对象,而是继承了父类的this对象,然后对其进行加工。如果不调用super方法,子类得不到this对象。
    ps:super关键字指代父类的实例,即父类的this对象。在子类构造函数中,调用super后,才可使用this关键字,否则报错。

    原型链是实现ES5继承的关键

    JS中的函数包含了一个prototype的内部属性,这个属性所对应的就是该对象的原型对象;
    构造函数除了有_proto_属性之外还拥有prototype属性(显式原型);
    当函数实例调用某种方法或查找某种属性时,首先会在自身查找,如果自身并没有该属性或方法,
    则会去它的__proto__属性中调用查找,也就是它构造函数的prototype中调用查找;

    4. 函数原型与原型链

    原型 prototype => 是函数特有的;
    原型链 proto([[prototype]]) => 对象都有的;
    1.原型链是为查找机制指明一个方向、当我们实例化一个对象的时候、实例对象有一个__proto__属性指向原型对象、原型对象的存在是为了实现属性与方法的共享、当我通过__proto__依次往上查找的时候就形成了原型链。它本质上是一个查找属性的过程,如果本身没有,则会去__proto__中查找,也就是构造函数的显式原型中查找
    如果构造函数的显式原型中也没有该属性,因为构造函数的显式原型也是对象,也有__proto__,
    那么会去它的显式原型中查找,一直到null,如果没有则返回undefined

    function A(){}; // 函数未被使用 new 关键字创建实例时, 并不能被称为构造函数;
    let a = new A();
    
    2. a.__proto__.constructor == function A(){}
    
    3. a.__proto__.__proto__== Object.prototype
    
    4. a.__proto__.__proto__.__proto__== Object.prototype.__proto__ == null
    
    5. 通过__proto__形成原型链而非protrotype
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    在这里插入图片描述

    什么是原型继承?
    A.prototype只是一个指针,指向的是原型对象,但是这个原型对象并不特别,它也只是一个普通对象。
    A.prototype 可以不指向最初的原型对象,而是另一个类 (B)的实例
    
    • 1
    • 2
    • 3

    出处:https://baijiahao.baidu.com/s?id=1685587405779644513&wfr=spider&for=pc

    执行该代码 Person.prototype = new Animal() 后,Person的prototype指针指向发生了变化,指向了一个 
    Animal 实例。
    
    当 p 去访问 address 属性时,js会先在 p 的实例属性中查找,发现找不到后,就会去 Person 的原型对象上 
    查找。因为Person的原型对象已经被我们换成一个animal实例,所以就会找到animal实例的属性,
    当发现还是没有 address属性,就会去Animal的原型对象上查找,最终找到。
    
    这就说明,我们可以通过原型链的方式,实现 Person 继承 Animal 的所有属性和方法。
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    4.parseInt 的输出结果

    在这里插入图片描述
    重点:进位制,在javascript当中,常用十进制(0-9),二进制(0,1),十六进制(0x), 八进制(0-7);
    从 parseInt(string,radix) 的定义来看,(不包含 ECMAScript 5 后的浏览器) string以 ‘0’ 开头时默认将其后的数字解析为8进制,以 ‘0x’开头时解析为16进制.

    在这里插入图片描述
    上图为菜鸟教程关于此函数的实例;其中关于解析(“010”)的结果,在谷歌浏览器(102.0.5005.61(正式版本))中发现,在传入数字类型的参数时,parseInt 会将‘0’后的数字解析为8进制,也可能是由于入参是number类型,转换成String类型时使用了八进制换算 (010).toString() == 8 || 010 + ‘’ == 8

    在这里插入图片描述
    最后关于parseInt() 返回NaN的问题,结合进位制与 radix 参数的描述;假设:radix永远取值为 2-36,当radix为2,string第一位数为3时会返回NaN;同理:当radix为8,string第一位数为9时会返回NaN;
    可以判断出:

    传入parseInt方法的string的第一位数字不会大于radix(radix合法范围2-36,结合进制解析),如果超了,则方法会返回NaN。

    5.setTimeout、Promise、Async/Await 的区别

    setTimeout

    setTimeout回调函数放在宏任务队列里,等到执行栈清空后执行,然后等待计时器设置时间到达之后才会把回调放入任务队列。

    Promise
    1. 为什么会有promise?解决什么问题?

    promise 是异步编程的一种解决方案,比传统的回调函数和事件更加合理,ES6将其写进了语言标准,统一了用法,原生提供了Promise对象;
    因为复杂的业务场景,当我们需要连续调用多个接口,后一个接口依赖前一个接口返回数据才能开始请求时,我们会就会用到回调函数,这样如果有多个嵌套的回调会形成地狱回调,使我们的代码难以维护;
    promise就是用来解决回调地狱的,他接受一个function作为参数。function中有两个形参,一个成功的回调函数 resolve 一个失败的回调函数 reject 。

    new Promise((resolve, reject) =>{
    	
    })
    
    • 1
    • 2
    • 3

    new Promise()本身是同步的立即执行函数,会先执行参数函数的代码。参数函数内调用resolve或者reject的时候,并不会终结 Promise的参数函数的执行,而是会把resolve/reject对应的回调函数放入对应宏任务的微任务队列中,在对应宏任务的同步代码执行完毕后执行。
    promise有三个状态:pending、fulfilled、rejected。
    pending: 等待中,或者进行中,表示还没有得到结果。
    resolved(Fulfilled): 已经完成,表示得到了我们想要的结果,可以继续往下执行。
    rejected: 也表示得到结果,但是由于结果并非我们所愿,因此拒绝执行。
    这三种状态不受外界影响,而且状态只能从pending改变为resolved或者rejected,并且不可逆。
    Promise对象原型中的.then方法,可以接收构造函数中处理的状态变化,并分别对应执行。then方法有2个参数,第一个函数接收resolved状态的执行,第二个函数数接收reject状态的执行,如果.then没有接收到 reject 回调参数则会使用Promise.catch返回失败信息;

    Promise链式调用
    let promise1 = new Promise((resolve, reject) =>{
    	resolve(1)
    });
    
    let promise2 = new Promise((resolve, reject) =>{
    	resolve(2)
    });
    promise1.then((res) =>{ 
    	console.log(res); 
    	return promise2 }
    ).then(res => { console.log(res) });
    // 打印结果 1 2
    
    promise1.then((res) =>{ 
    	console.log(res); 
    	return 2 }
    ).then(res => { console.log(res) });
    // 打印结果 1 2
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    ``

    链式调用可以无限的写下去,上一级 onFulfilled return 的值,会变成下一级 onFulfilled 的结果,以此类推,但是返回普通值没有意义,此类写法更适用于上一级onFulfilled return 的值为 promise 的情况;

    Promise 必须调用resolve或者reject,否则一直处于pending状态,Promise与.then的回调函数的内存得不到释放,会导致内存泄漏;
    .then 可以多次执行

    let promise = new Promise((resolve, reject)=>{
    	setTimeout(()=>{
    		console.log(1);
    		resolve(2)
    	})
    });
    promise.then(res=>{
    	console.log(res)
    });
    promise.then(res=>{
    	console.log(res)
    });
    // expected output: 1 2 2
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    iterable:一个 可迭代对象,如 Array 或 Map
    Promise.all(iterable) 此方法在集合多个 promise 的返回结果时很有用

    接收一个 promise 的 iterable 类型(注:Array,Map,Set 都属于 ES6 的 iterable 类型)的输入,并且只返回一个Promise实例,那个输入的所有 promise 的 resolve 回调的结果是一个数组。这个Promise的 resolve 回调执行是在所有输入的 promise 的 resolve 回调都结束,或者输入的 iterable 里没有 promise 了的时候。它的 reject 回调执行时,只要任何一个输入的 promise 的 reject 回调执行或者输入不合法的 promise 就会立即抛出错误,并且 reject 的是第一个抛出的错误信息;
    Promise.all(iterable) 对比 axync、await 可以实现并发;

    function a(time) {
      return new Promise(resolve=>{
        setTimeout(resolve(time),time)
      })
    }
    function chuanXingDemo() {
      Promise.all([a(1000),a(2000),a(3000)]).then(res=>{
        console.log(res)
      })
    };
    chuanXingDemo();// expected output: Array [1000, 2000, 3000]
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    Promise.race(iterable) 返回一个 promise,一旦迭代器中的某个 promise 解决或拒绝,返回的 promise 就会解决或拒绝
    1. race 函数返回一个 Promise,它将与第一个传递的 promise 相同的完成方式被完成。它可以是完成(resolves),也可以是失败(rejects),这要取决于第一个完成的方式是两个中的哪个;
    2. 如果传的迭代是空的,则返回的 promise 将永远等待;
    3. 如果迭代包含一个或多个非承诺值和/或已解决/拒绝的承诺,则 Promise.race 将返回为迭代中找到的第一个值;
    var resolvedPromisesArray = [Promise.resolve(33), Promise.resolve(44)];
    var p = Promise.race(resolvedPromisesArray);
    // immediately logging the value of p
    console.log(p);
    
    // using setTimeout we can execute code after the stack is empty
    setTimeout(function(){
        console.log(p);
    });
    
    // logs, in order:
    // Promise { : "pending" }
    // the stack is now empty
    // Promise { : "fulfilled", : 33 }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    Promise.any() 用于获取首个进入resolved状态的 promise 的值

    用于获取首个进入resolved状态的 promise 的值。只要有一个 promise 进入resolved状态,那么此方法就会提前结束,而不会继续等待其他的 promise 返回状态;
    如果可迭代对象内的 promise 最终都没有兑现(即所有 promise 都被拒绝了),那么该方法所返回的 promise 就会变成拒绝状态,并且它的拒因会是一个 AggregateError 实例,这是 Error 的子类,用于把单一的错误集合在一起

    const pErr = new Promise((resolve, reject) => {
      reject("总是失败");
    });
    
    const pSlow = new Promise((resolve, reject) => {
      setTimeout(resolve, 500, "最终完成");
    });
    
    const pFast = new Promise((resolve, reject) => {
      setTimeout(resolve, 100, "很快完成");
    });
    
    Promise.any([pErr, pSlow, pFast]).then((value) => {
      console.log(value);
      // pFast fulfils first
    })
    // 期望输出:"很快完成"
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    简单使用示例
    new Promise((resolve, reject) => {
    	console.log(1);
    	resolve('yes'); 
    	console.log(2);
    }).then((res)=>{
    	console.log(res);
    },(err)=>{
        console.log(err,2)
    }).catch(err=>{
        console.log(err,3)
    }); // 1,2,yes;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    Async/Await

    async/await只是一个语法糖,其本质依然还是异步的,不过是让我们可以用同步的方式来书写而已;
    它使异步代码看起来像同步代码,方便控制代码执行顺序;
    async 函数表示里面可能有异步方法;
    async 函数返回一个 Promise 实例对象,因此我们也可以使用then来处理后续逻辑
    await 后面跟一个表达式,async方法执行时,遇到await后会立即执行表达式,如果这个表达式返回一个Promise,那么await会把表达式后边的代码放到微任务队列中(实际上后边的代码相当于变成了异步代码),让出执行栈让同步代码先执行,等表达式结果返回之后,再执行表达式后边的代码;
    当await后的表达式返回一个Promise时,等待这个Promise对象resolve 时,线程会休眠;即未返回结果,后面的代码会被阻塞不会触发;
    await相当于promise.then方法,并且只是成功的方法resolve,只是await时直接得到一个值,then需要传值进行回调;
    await后面跟的不是promise对象,会将其他值包装成一个promise对象;
    (async function(){ const res = await 400 //Promise.resolve(400) console.log(res) //400 })()

     async function name(params) {
        console.log(1);
        await setTimeout(()=>{console.log(2)},1000);
        console.log(3);
    }
    name();
    //1,3,2
    
    async function name(params) {
        console.log(1);
        await new Promise((resolve, reject)=>{
    		setTimeout(()=>{
    			console.log(2)
    			resolve(2)
    		},1000)
    	});
        console.log(3);
    }
    name();
    //1,2,3
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    从代码层面看他们的区别

    async function async1() {
        console.log('async1 start')
        let log = await async2()
        console.log(log)
        console.log('async1 end')
    }
    async function async2() {
        return new Promise((resolve, reject)=>{
    		resolve('async2')
    	})
    }
    async1()
    new Promise(function (resolve) {
        console.log('promise1')
        resolve()
    }).then(function () {
        console.log('promise2')
    })
    输出顺序
    async1 start
    promise1
    promise2
    async2
    async1 end
    // 从上代码运行结果来看,await 获取promise返回值会比promise.then 晚;原因是await表达式得到一个promise后在等待它返回结果时(这个promise已经进入任务队列)将await表达式后面的代码也用一个promise(叫它promise2)封装起来,当await表达式得到返回结果后promise2才进入任务队列,所以它会比正常的promise慢;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    async/await 与 Promise

    async/await ,和promise都可以异步编程;
    async await是基于Promise实现的,可以说是改良版的Promise,它不能用于普通的回调函数;

    6.JavaScript 运行机制

    1.单线程

    js代码运行是从上往下按照顺序一行一行来执行的,一行执行完之后才能执行下一行;

    2.js 事件循环

    事件循环又叫做消息循环,是浏览器渲染主线程的工作方式;
    在chrome的源码中,它开启一个不会结束的for循环,每次循环从消息队列中取出第一个任务执行,而其它线程只需要在合适的时候将任务加入到队列末尾即可;
    过去把消息队列简单分为宏任务队列和微任务队列,这种说法目前已无法满足复杂的浏览器环境,取而代之的是一种更加灵活多变的处理方式;
    根据 W3C 官方的解释,每个任务有不同的类型,同类型的任务必须在同一个队列,不同的任务可以属于不同的队列。不同任务队列有不同的优先级,在一次事件循环中,由浏览器自行决定取哪一个队列的任务。但浏览器必须有一个微队列,微队列的任务一定具有最高的优先级,必须优先调度执行;

    3.什么是同步什么是异步?

    区别:异步不会阻塞程序的执行,同步会阻塞程序的执行
    同步异步代码执行流程
    1.在同一轮事件循环当中,同步任务会直接放入到主线程的执行栈立即执行,而异步任务不进入执行栈,而是进入任务队列中
    2.同步任务按顺序执行,执行完毕后,才会去读取队列中的可执行的异步任务,并将可执行的异步任务依次加入到执行栈中执行。如此反复循环

    同步代码:console.log,变量声明,for循环,if;一个执行完在执行下一个,按顺序执行。
    异步代码:定时器,ajax,promise;先运行一部分,等待同步任务执行完之后再执行后续代码。如:

    new Promise(function (resolve) {
        console.log('1'); // new Promise()实际上是同步的代码,
        resolve()
        console.log('2'); 
    }).then(res=>{
    	console.log(3)
    });
    console.log(4)
    // 1,2,4,3
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    如何理解js的异步?

    js是一门单线程的语言,这是因为它运行在浏览器的渲染主线程中,而渲染主线程只有一个;
    并且渲染主线程承担着诸多的工作,渲染页面、执行js代码都在其中运行;
    如果使用同步的方式,就极有可能导致主线程产生阻塞,从而导致消息队列(事件队列)中的很多其他任务无法及时得到执行;
    这样一来,一方面会导致繁忙的主线程白白的消耗时间,另一方面导致页面无法及时更新,给用户造成卡死现象;
    所以浏览器采用异步的方式来避免出现这个问题。具体做法是当某些任务发生时,比如计时器、网络、事件监听,主线程将任务交给其他线程去处理,自身立即结束该任务的执行,转而执行后续代码。当其他线程完成时,将事先传递的回调函数包装成任务,加入到消息队列的末尾排队,等待主线程调度执行;
    在这种异步模式下,浏览器永不阻塞,从而最大限度地保证了单线程的流畅运行。

    4.宏任务和微任务

    ES6 规范中,宏任务(Macrotask) 称为 Task, 微任务(Microtask) 称为 Jobs。宏任务是由宿主(浏览器、Node)发起的,而微任务由 JS 自身发起。

    常见的宏任务与微任务的几种创建方式

    宏任务:setTimeout, setInterval,script标签内代码,setImmediate(Node环境中)等;
    微任务:Promise, callback, process.nextTick(node环境中)等;

    如何理解 script(整体代码块)是个宏任务呢

    实际上如果同时存在两个 script 代码块,会首先在执行第一个 script 代码块中的同步代码,如果这个过程中创建了微任务并进入了微任务队列,第一个 script 同步代码执行完之后,会首先去清空微任务队列,再去开启第二个 script 代码块的执行;其中,两个代码块内创建的即时(定时器会在运行代码时后等待计时时间到达后进入宏任务队列)宏任务都会放在下一次事件循环中执行。

    宏任务

    可以简单的理解为每次执行栈中执行的代码
    1.宏任务在执行过程中可以创建宏任务,也可以创建微任务,创建的宏任务会加入到宏任务队列中,等待下一个循环执行。而创建的微任务会在本次宏任务中的代码全部执行完成以后立刻依次执行。故宏任务中创建的微任务不会影响当前宏任务的执行
    2.当一个宏任务执行完以后,会读取微任务队列中的任务,并依次全部执行完微任务队列中的任务。当微任务队列中的任务全部执行完成后,算是一个完整的task执行完成。
    3.浏览器为了能够使得JS内部task与DOM任务能够有序的执行,会在一个task执行结束后,在下一个 task 执行开始前,对页面进行重新渲染 。如此过程不断重复,便是我们所说的事件循环,Event loop

    微任务

    可以简单的理解为当前宏任务执行完成后立刻被执行的任务
    1.微任务执行期在 本次宏任务执行完之后,下个宏任务执行之前,并且在UI渲染之前
    2.微任务中创建的宏任务,也会被加入宏任务队列中,等待依次执行
    3.微任务中创建的微任务,也会在当前事件循环中执行完成,也就是说微任务中创建的微任务,会在下一个宏任务执行前以及UI渲染前被执行

    任务有优先级吗?

    任务没有优先级,在消息队列中先进先出;
    但消息队列是有优先级的
    根据 W3C 的最新解释:

    • 每个任务都有一个任务类型,同一个类型的任务必须在一个队列里,不同类型的任务可以分属于不同的队列。在一次事件循环中,浏览器可以根据实际情况从不同的队列中取出任务执行;
    • 浏览器必须准备好一个微队列,微队列中的任务优先所有其他任务执行

    随着浏览器的复杂度急剧提升,W3C 不再使用宏队列的说法
    在目前chrome 的实现中,至少包含了下面的队列:

    • 延时队列 :用于存放计时器到达后的回调任务,优先级【中】;
    • 交互队列 :用于存放用户操作后产生的的事件处理任务,优先级【高】;
    • 微队列 :用户存放需要最快执行的任务,优先级【最高】;

    7. js的数据类型

    基本数据类型

    ES5的5种:Null,undefined,Boolean,Number,String,
    ES6新增:Symbol表示独一无二的值
    ES10新增:BigInt 表示任意大的整数

    一种引用数据类型:(本质上是由一组无序的键值对组成)

    引用数据类型: Object。包含Object、Array、 function、Date等。 JavaScript不支持创建任何自定义类型的数据,也就是说JavaScript中所有值的类型都是上面8种之一。

    1. Undefined

    Undefined类型只有一个值,即特殊值undefined。在使用var声明变量,但未对其加以初始化时,这个变量值就是undefined。

    1. Null

    Null类型是第二个只有一个值的数据类型。其特殊值就是Null。从逻辑角度上看,null是一个空的对象指针。而这也正是使用typeof操作符检测null值,会返回“object”的原因。

    1. Boolean

    即布尔类型,该类型有两个值:true、false。需要注意的是,Boolean类型的字面值true和false是区分大小写的。也就是说,True和False(以及其它的混合大小形式)都不是Boolean值,只是标识符。

    1. Number

    该类型的表示方法有两种形式,第一种是整数,第二种为浮点数。整数:可以通过十进制,八进制,十六进制的字面值来表示。浮点数:就是该数值中必须包含一个小数点,且小数点后必须有一位数字。
    Number 有一个特殊值NaN,NaN表示该值不是一个数字,并且NaN!=NaN;

    1. String

    String类型用于表示由零或多个16位的Unicode字符组成的字符序列,即字符串。至于用单引号,还是双引号,在js中还是没有差别的。记得成对出现。

    1. Symbol

    符号 (Symbols) 是 ECMAScript 第 6 版新定义的。符号类型是唯一的并且是不可修改的。Symbol 函数前不能使用 new 命令,否则会报错。这是因为生成的 Symbol 是一个原始类型的值,不是对象。Symbol 函数可以接受一个字符串作为参数,表示对 Symbol 实例的描述

    1. Object

    Object数据类型,称为对象,是一组数据和功能(函数)的集合。可以用new操作符后跟要创建的对象类型的名称来创建。也可以用字面量表示法创建。在其中添加不同名(包含空字符串在内的任意字符串)的属性。

    1. Array

    JavaScript 数组用方括号书写,数组的项目由逗号分隔。

    1. Function

    ECMAScript中的函数是对象,与其他引用类型一样具有属性和方法。因此,函数名实际是一个指向函数对象的指针。

    null 和 undefined 的区别?

    相同:

    在 if 语句中 null 和 undefined 都会转为false两者用相等运算符比较也是相等;
    Undefined 和 Null 都是基本数据类型,这两个基本数据类型分别都只有一个值,就是 undefined 和 null;
    使用 Number 函数转化后 Number(null) == 0, Number(undefined) == NaN

    不同:

    undefined 代表的含义是未定义, 定义了形参,没有传实参,显示undefined
    一般变量声明了但还没有赋值的时候会返回 undefined
    对象属性不存在时,显示undefined
    函数没有写返回值,即没有写return,拿到的是undefined

    null 代表的含义是空对象。也作为对象原型链的终点
    null 主要用于赋值给一些可能会返回对象的变量,作为初始化。

    ES10新增:BigInt(parameter) 表示任意大的整数

    BigInt数据类型的目的是比Number数据类型支持的范围更大的整数值。在对大整数执行数学运算时,以任意精度表示整数的能力尤为重要。使用BigInt,整数溢出将不再是问题。

    要创建BigInt,只需在整数的末尾追加n 或者传入 parameter 即可。
    let num1 = 41564654515151548948n;
    let num2 = BigInt("41564654515151548948");// number 比较: 注意后置位的数字
    41564654515151548948 === 41564654515151550000;    // → true
    console.log(9999999999999999);    // → 10000000000000000
    console.log(num1);    // → 41564654515151548948n
    console.log(num2);    // → 41564654515151548948n
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    BigInt特点:

    构造函数BigInt 只能传入整数
    BigInt是一种特殊的数字类型,它支持任意长度的整数
    由于 number 和 BigInt 属于不同的数据类型,它们可以相等==,但不能严格相等===;
    数学运算符: 不允许在 BigInt 和 Number 之间进行混合操作;可以1n+2n=3n; 但不能1n+1,这样会报错

    判断一个值是什么类型有哪些方法?
    • typeof 运算符typeof 2 == 'number'
    • instanceof 运算符, 用于检测构造函数的 prototype 属性是否出现在某个实例对象的原型链上
      语法示例:object instanceof constructor
      object:某个实例对象
      constructor:某个构造函数
      用来检测 constructor.prototype 是否存在于参数 object 的原型链上。
    • Object.prototype.toString.call 方法 Object.prototype.toString.call([]) == '[object Array]'

    8. js 事件

    事件监听

    addEventListener(‘事件(比如: click)’, function(){ }, flase/true);
    用于监听某个事件,当监听到这个事件时去执行某个方法;
    第一个参数是需要绑定的事件;
    第二个参数是触发事件后要执行的函数;
    第三个参数默认为false,为false时,表示事件冒泡阶段执行,从里往外;为true时,表示事件捕获阶段执行,从外往里。

    事件捕获

    从外向内,在捕获的过程中,最外层(根)元素的事件先被触发,然后依次向内执行,直到触发最里面的元素(事件源);期间所遇到的元素若绑定了事件并且设置为在事件捕获时触发,会逐个触发他们绑定的事件;

    事件冒泡

    在javascript事件传播过程中,当事件在一个元素上出发之后,事件会逐级传播给先辈元素,直到document为止,有的浏览器可能到window为止。并不是所有的事件都有冒泡现象,比如 blur事件 ,focus事件, load事件;

    事件传播

    通过下图可以得出,事件传播过程是从捕获阶段开始,冒泡阶段结束
    此时点击的是holle在这里插入图片描述

    阻止事件冒泡

    平时会用到大量的事件冒泡事件,但是可能我们在某个子级标签不需要传递事件给父级,这时候就需要阻止它事件的冒泡。
    一般,使用stopPropagation来阻止事件的冒泡。
    使用stopImmediatePropagation可以阻止事件捕获, 不过这并不常用。
    在这里插入图片描述在这里插入图片描述

    事件委托

    事件委托,又名事件代理。事件委托就是利用事件冒泡来实现;实现:把子元素需要的事件绑定到父元素上,点击子元素时向外冒泡,到达父元素时触发事件。意外:如果子元素本身绑定的事件阻止了事件冒泡,那么委托也就没法实现了。

    9.this指向的问题

    this指向一般是不确定的、只有在函数调用的时候才能确定、一般指向调用它的对象

    在全局环境中的this—window

    在全局执行环境中(在任何函数体外部)this 都指向全局对象window;

    console.log(this); // window
    
    • 1
    在函数中的this

    在函数内部,this的值取决于函数被调用时的环境,this指向的就是调用函数时所在的对象。

    // 对象中的this指向调用者,谁调用的this就指向谁;若是嵌套调用,则指向当前调用环境,不会向上查找,否则全部this都会指向window
    function fun() {
    	console.log(this);
    	console.log(this.name);
    }
    let obj = {
    	fn: fun,
    	name: 'obj.name'
    }
    
    var name = 'window.name'
    
    fun(); // this指向window,this.name = 'window.name'
    obj.fn(); // this指向obj,同时,在fun函数内通过this可访问obj内的属性,this.name = 'obj.name'
    // obj.fn() 是obj 调用的所以去找obj里面的name 
    // fun() 是window调用的所以去找全局里面的this.name
    
    let obj2 = {
    	name: 'obj2.name',
    	obj: obj
    }
    obj2.obj.fn(); //实际调用者是obj对象,this指向obj,this.name = 'obj.name' 
    
    let obj3 = {
    	obj: obj2,
    	name: 'obj3.name'
    }
    obj3.obj.obj.fn(); //实际调用者仍旧是obj对象,this指向obj,this.name = 'obj.name' ;等同于调用fn的上一个对象
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    构造函数中的this—指向当前实例化对象
    function fun() {
    	this.name = 'fun.name';
    	this.fun = () =>{
    		console.log(this.name)
    	}
    }
    let f = new fun();
    f.name = 'f.name';
    f.fun();
    console.log(fun, f);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    在这里插入图片描述
    简单理解:

    普通函数调用,此时this指向window
    构造函数调用,this指向实例对象
    对象方法调用,this指向该方法所属对象
    事件绑定时,this指向绑定事件的对象
    定时器函数,this指向window
    箭头函数,this指向上下文

    改变this指向的三种方式

    call、apply、bind三者为改变this指向的方法。

    1.call(无数个参数)

    第一个参数:改变this指向
    后续参数:实参(参数列表)
    使用之后会自动执行该函数

    function Person(name,age)  
        {  
            this.name=name;  // this指向Person, 使用 call 强行改变this指向后 this 指向 student1;因此 this.name == student1.name
            this.age=age;  
        };
    function Student(name,age,sex)  
        {  
        	let that = this; // new Student() 时此this将指向 Student 的实例对象(叫它student1 );即 new Student('张三',18,'男');
            Person.call(that,name,age,sex); // 将student1 的this传传递过去并且替换Person的this,然后调用 Person 构造函数,这个时候Person 内的 this 指向 student1 
            that.sex = sex;
        };
        let student1 = new Student('张三',18,'男');
        //student1 = {name: '张三', age: 18, sex: '男'}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    2.apply(两个参数)

    第一个参数:改变this指向
    第二个参数:数组(里面为实参)
    使用之后会自动执行函数

    function Person(name,age)  
        {  
            this.name=name;  // this指向Person, 使用 apply 强行改变this指向后 this 指向 student1;因此 this.name == student1.name
            this.age=age;  
        };
    function Student(name,age,sex)  
        {  
        	let that = this; // new Student() 时此this将指向 Student 的实例对象(叫它student1 );即 new Student('张三',18,'男');
            Person.apply(that,arguments); // 将student1 的this传传递过去并且替换Person的this,然后调用 Person 构造函数,这个时候Person 内的 this 指向 student1 
            that.sex = sex;
        };
        let student1 = new Student('张三',18,'男');
        //student1 = {name: '张三', age: 18, sex: '男'}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    3.bind(无数个参数)

    第一个参数:改变this指向
    后续参数:实参
    返回值为一个新的函数
    使用的时候需要手动调用下返回 的新函数(不会自动执行)

    相同点:第一个参数都为改变this的指针。若第一参数为null/undefined,this默认指向window
    区别:

    call和apply会调用函数
    call和apply参数参数的方式是不一样的,call参数是以罗列的方式传递,apply参数是以数组的方式传递;
    bind不会调用函数,返回值为一个新的函数;
    调用bind时,假如传了后续参数,那么新的函数会一直带着这些参数,在使用新的函数传参时你会发现你传的参无效,而call和apply不会出现这个问题

    let a = function(a,b,c) {
        console.log(this,a,b,c);
    }
    a(1,2); // this 指向window 1 2 undefined  
    let obj = {
        name: 'obj'
    }
    a.call(obj, 5,6); // 输出 {name: 'obj'} 5 6 undefined  this 指向obj
    a.apply(obj, [1,2]); // 输出 {name: 'obj'} 1 2 undefined  this 指向obj
    a(1,2); // 1 2 undefined  this 指向window
    let newA = a.bind(obj, 5); // 输出 {name: 'obj'} 5 undefined undefined  this 指向obj
    newA(); // // 输出 {name: 'obj'} 5 undefined undefined  this 指向obj
    newA(1,2,3); // // 输出 {name: 'obj'} 5 1 2  
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    10.鼠标事件 mouseenter与mouseover区别

    mouseenter: 鼠标进入被绑定事件监听元素节点时触发一次,再次触发是鼠标移出被绑定元素,再次进入时。而当鼠标进入被绑定元素节点触发一次后没有移出,即使鼠标动了也不再触发。
    mouseover: 鼠标进入被绑定事件监听元素节点时触发一次,如果目标元素包含子元素,鼠标移出子元素到目标元素上也会触发。
    mouseenter 不支持事件冒泡 mouseover 会冒泡

    11. 面向对象

    对象:

    属性和方法的集合叫做对象(万物皆对象)。

    创建对象:

    字面量

    let obj= {
    	name:'字面量' 
    }
    
    • 1
    • 2
    • 3

    Object类:

    let obj = new Object()
    
    • 1

    构造函数:

    function a(name) {
    	this.name = name
    }
    let b = new a('b');
    
    • 1
    • 2
    • 3
    • 4
    面向过程:

    面向过程就是分析出实现需求所需要的步骤,通过函数一步一步实现这些步骤,接着依次调用即可

    面向对象:

    面向对象是一种编程思想,面向对象是把整个需求按照特点、功能划分,将这些存在共性的部分封装成类(类实例化后才是对象);
    类:对实体对象的抽象(学生)
    对象:类的具体示例(张三)

    面向对象三大主要特征:

    封装

    隐藏对象的属性和实现细节,仅对外公开接口,强安全性和简化编程,使用者不必了解具体的实现细节,而只是要通过外部接口来使用类的成员。

    继承

    子类继承父类的特征和行为,使得子类对象(实例)具有父类的属性和方法。

    多态

    在父类定义一个方法,在子类中仍然可以定义。使得父子拥有相同的方法,但是方法内容不同。
    多态存在的三个必要条件:
    继承(多态其实是在继承的基础上的)
    重写(子类继承父类后对父类方法进行重新定义)
    父类引用指向子类对象

    优劣

    面向过程:

    优点:性能比面向对象好,因为类调用时需要实例化,开销比较大,比较消耗资源。
    缺点:不易维护、不易复用、不易扩展.

    面向对象:

    优点:易维护、易复用、易扩展,由于面向对象有封装、继承、多态性的特性,可以设计出低耦合的系统,使系统 更加灵活、更加易于维护 .
    缺点:性能比面向过程差

    12.JavaScript 类(class)

    类的声明

    使用 class 关键字

    class Person {
    	constructor(name, fun) {
    		this.name = name; // 此处this指向使用 new Person() 创建的子类
    		this.fun = fun;
    	}
    	fun1() {
    		console.log('fun1')
    	}
    	static sum(a, b) {
            console.log(a + b)
        }
    }
    // static 关键字可以创建静态方法,不需要new 一个实例,父类 Person 可以直接使用Person.sum()、.num调用,但是无法被子实例继承
    let son1 = new Person('son1', function() { console.log(this) })
    son1.fun(); // {name: 'son1', fun: function() { console.log(this) } }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    函数声明和类声明之间的一个重要区别是函数声明会提升,类声明不会
    类声明不可以重复
    类必须使用 new 调用,否则会报错。这是它跟普通构造函数的一个主要区别,就是后者不用 new 也可以执行
    类表达式可以是被命名的或匿名的如:let Person = class {// 匿名类}; let person = class Person {// 命名类}
    类的所有方法都定义在类的 prototype 属性上面,在类的实例上面调用方法,其实就是调用原型链上的方法
    原型方法可以通过实例对象调用,但不能通过类名调用

    constructor 方法

    constructor 方法是类的默认方法,通过 new 命令生成对象实例时,自动调用该方法(默认返回实例对象 this)。一个类必须有 constructor 方法,如果没有显式定义,一个空的 constructor 方法会被默认添加。一个类只能拥有一个名为 “constructor” 的特殊方法,如果类包含多个 constructor 的方法,则将抛出 一个 SyntaxError 。

    extends 关键字
    class Person {
    	constructor(name, fun) {
    		this.name = name; // 此处this指向使用 new Person() 创建的子类
    		this.fun = fun;
    	}
    	fun1() {
    		console.log('fun1')
    	}
    	static sum(a, b) {
            console.log(a + b)
        }
    }
    class Sonclass extends Person {
    	constructor(name, fun) {
    		// super会调用父类的构造函数
    	    // 作为 Person 的 name, fun
    		super(name, fun);
    		// 注意:在派生的类中,在你使用'this'之前,必须先调用 super().忽略这将导致引用错误。
        	this.name = 'cc';
    	}
    }
    let a = new Sonclass('a', ()=>{consoloe.log(this)});// {name: 'a', fun: function() {console.log(this)}}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    extends 关键字用于创建一个类,该类是另一个类的子类。

    子类继承了另一个类的所有方法。

    继承对于代码可重用性很有用:在创建新类时重用现有类的属性和方法。

    super() 方法引用父类的构造方法。

    通过在构造方法中调用super() 方法,我们调用了父类的构造方法,这样就可以访问父类的属性和方法。

    class Person {
    	constructor(name, fun) {
    		this.name = name; // 此处this指向使用 new Person() 创建的子类
    		this.fun = fun;
    	}
    	fun1() {
    		console.log('fun1')
    	}
    	static sum(a, b) {
            console.log(a + b)
        }
    }
    class Sonclass extends Person {
    	constructor(name, fun) {
    		super(name, fun);
    	}
    }
    let a = new Sonclass('a', ()=>{consoloe.log(this)});// {name: 'a', fun: function() {console.log(this)}}
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    13.深拷贝、浅拷贝

    什么是浅拷贝与深拷贝?

    简单来说就是,B复制了A,在改变B时,如果A发生了变化,那就是浅拷贝,反之就是深拷贝。
    深拷贝和浅拷贝都只针对引用数据类型,因为基本类型的数据键值都存在与执行栈中,当B=A复制时,栈内会开辟一个新的内存存放B的值;如此,A改变时B不会发生变化。
    引用数据类型:键存在于栈中,值存于堆中,但是栈会提供一个引用地址(指针)指向堆内存中的值;浅拷贝实际上只是复制了这个引用地址,而堆中的值还是公用的,当B复制了A,然后改变B的值,就相当于改变了A。

    深拷贝,实际是拷贝对象各个层级的属性

    例:
    在这里插入图片描述

    以for循环为例,拷贝完成之后修改arr中的基本类型数据,arr2中对应的数据是不受影响的,但是修改{name: ‘对象’} 这个引用类型数据时,arr2 中对应的值也发生了变化。
    引用数据类型可以包含引用数据类型 {name:‘1’, obj1:{name: ‘obj2’, obj2: []}} 这样层层嵌套,是造成这个问题的关键,但是只要把对象内的所有对象也循环复制一遍,就可以完成深拷贝,使用递归可以减小代码量:

    var arr = [1,2,3,4,5,{name:'对象'}];
    var arr2 = copyArr(arr); 
    function copyArr(obj) {
    	if (!obj || typeof obj != 'object') return obj; // // 进行深拷贝的数据不能为空,并且是对象或者是数组(基本数据类型执行深拷贝没有意义)
        let res = Array.isArray(obj) ? [] : {}; // 判断外壳容器是否是数组
        if(obj && typeof obj === 'object') {
        	//使用 for...in...循环(它可以循环object类型的数据)
    	    for (key in obj) { // key是索引
    	    	if (obj[key] && typeof obj[key] === "object") {
    	          res[key] = copyArr(obj[key]); // 再次调用copyArr,深层循环复制
    	        } else {
    	          res[key] = obj[key]; // 赋值,res[key]等价于对象res['name'];中括号写法,不同于res.name(他必须是一个以属性名称命名的简单标识符);中括号写法可以传入动态的值
    	        }   
            }
        };
        return res
    };
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    在这里插入图片描述

    JS中浅拷贝、深拷贝的几种常见方式
    浅拷贝
    Object.assign(obj1, obj2)//合并对象方法,可以只传一个已声明对象,另一个传{};实现拷贝
    [...arr]; // ...扩展运算符
    Array.prototype.slice(index, num); // slice()会返回一个新的数组;index: 截取开始下标;num: 截取个数;
    lodash 里面的 clone
    
    • 1
    • 2
    • 3
    • 4
    深拷贝
    JSON.parse(JSON.stringify(obj)); //弊端:值为undefined,或者function的时候并不会拷贝过来
    递归方法,上面有使用;
    Jquery.extend();
    lodash 里面的 cloneDeep
    
    • 1
    • 2
    • 3
    • 4

    14. JS递归

    简单通俗的说,递归函数就是在函数体内调用本函数
    递归函数的使用要注意函数终止条件,避免进入死循环
    缺点就是会消耗大量内存

    function num(n) {
    	if(n<=1) return 1;
    	return num(n-1) + n; // 求1到n的和
    }
    
    • 1
    • 2
    • 3
    • 4

    14.闭包

    什么是闭包?——通过一系列方法访问函数内部的变量(局部变量)。

    要理解闭包,首先必须理解 Javascript 特殊的变量作用域。
    变量的作用域无非就是两种:全局变量和局部变量。
    可以延长变量的生命周期 => 闭包会常驻内存;
    具有私密性,每次new一个新的函数实例时都会开辟一个新的内存环境去保存函数的闭包变量;

    常见的闭包

    function f1(){
    	var n=999;   
    	function f2(){
    	 	var b = 'ccc'
    		console.log(n);// 999
    	}
    	console.log(b);// 执行f1函数时会抛出错误 Error: b is not defined
    }
    f1(); // Error: b is not defined
    
    在上面的代码中,函数f2就被包括在函数f1内部,这时f1内部的所有局部变量,对f2都是可见的。
    但是反过来就不行,f2内部的局部变量,对f1就是不可见的。
    这就是Javascript语言特有的"链式作用域"结构(chain scope),子对象会一级一级地向上寻找所有父对象的变量。
    所以,父对象的所有变量,对子对象都是可见的,反之则不成立。
    
    1.将函数作为另外一个函数的返回值,
    function f1() {
    	var a = 1; 
    	return function f2() {
    		a++
    		console.log(a)
    	}
    }
    let f2 = f1(); //先定义一个变量承接f1()返回的function。
    f2();//2, 初始a = 1,调用一次f2() 后a++,此时 a=2;
    f2();//3, 第一次调用f2()时,a被重新赋值为2,此时再次a++,a=3;
    
    2. 将函数作为实参传递给另一个函数调用
    function f1(fun) {
    	var a = 1; 
    	fun(a)
    };
    function f2(a) {
    	console.log(a+2); //成功拿到f1中的a变量
    }
    f1(f2); // 3
    
    3. 箭头函数
    let add = a => b =>  c =>  console.log(a+b+c);
    add(1)(2)(3); // 6
    
    add 等价于:
    (a) =>{
    	return (b) =>{
    		return (c) =>{
    			console.log(a+b+c)
    		}
    	}
    }
    a => b =>  c =>  console.log(a+b+c)写法的原理是箭头函数只有一个返回值时{}可以省略,只有一个形参时()可以省略;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    闭包的作用

    使函数内部的变量在函数执行完后, 仍然存活在内存中 (延长了局部变量的生命周期)
    让函数外部的函数可以操作(读写)到函数内部的数据(变量/函数)
    闭包可以重用一个变量,且保证这个变量不会被污染。这些变量的值始终保持在内存中,不会被垃圾回收机制处理

    闭包的生命周期

    产生: 在嵌套内部函数定义执行完时就产生了(不是在调用)
    死亡: 在嵌套的内部函数成为垃圾对象时

    //闭包的生命周期
    function fn1() {
       var a = 2
       function fn2 () { // 内部函数对象已经创建,闭包产生
         a++
         console.log(a)
       }
       return fn2
     }
     var f = fn1()
     f() // 3
     f() // 4
     f = null //闭包死亡(包含闭包的函数对象成为垃圾对象)
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    闭包应用场景

    定义js模块: 将所有的功能和数据都放在同一个函数内,向外暴露这个函数或者函数内部方法集合;使用者可以通过调用暴露的函数或方法集合来实现功能。

    demo.js 
    function mode() {
    	let a = 1;
    	function seta() {
    		a++
    	}
    	function loga() {
    		log()
    	}
    	function log() {
    		console.log(a)
    	}
    	return {
    		loga,
    		seta
    	}
    }
    
    //调用
    <script type="text/javascript" src="demo.js"></script>
    <script type="text/javascript">
      var modes = mode()
      modes.loga(); // 1
      modes.seta();
      modes.loga(); // 2
    </script>
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    闭包的优缺点

    由于闭包会使得函数中的变量都被保存在内存中,内存消耗很大,所以不能滥用闭包,否则会造成网页的性能问题,在IE中可能导致内存泄露。解决方法是,在退出函数之前,将不使用的局部变量全部删除。

    15. 块级作用域

    1. 什么是作用域?

    作用域是编程中一个非常核心的概念,指程序中变量、函数的有效/可见范围。这是一个抽象概念。在 JS 中是通过执行上下文来实现的。

    1. 全局作用域(Global)、局部作用域(Local)、块作用域(Block)、脚本作用域(Script)

    全局作用域

    全局作用域是指在编程语言中,变量、函数或代码块可以在整个脚本或程序的任意位置被访问和使用的作用域;
    在JavaScript中,全局作用域通常指的是不在任何函数内部定义的变量,这些变量可以在整个脚本中访问;
    全局作用域的特点包括:

     变量可以在脚本的任意位置访问。
     最外层的函数和最外层函数外面的变量拥有全局作用域。
     所有未定义直接赋值的变量拥有全局作用域。
     所有`window`对象的属性拥有全局作用域。
     在浏览器环境中,可以通过`window`对象访问全局变量。
    
    • 1
    • 2
    • 3
    • 4
    • 5

    脚本作用域

    脚本作用域是针对于 let 和const声明的变量
    在函数外部用 let 或者 const 声明的变量,的作用范围为script,也就是当前脚本内有效,跟var的全局变量差不多,但它不是window属性

    局部作用域

    在函数中用 var 、let 、const 声明的所有变量,都是函数的局部变量,作用范围为局部作用域,即:只能在函数内部使用,函数外部无法使用这些变量;

    块级作用域

    在es 6 中新增了块级作用域的概念;
    ES 6 可以使用 let 关键字或者 const 关键字来实现块级作用域;
    let 或 const声明的变量只在 let 或 const命令所在的代码块 {} 内有效,在 {} 之外不能访问;
    块作用域由 { } 包括,if语句和for语句里面的{ }也属于块作用域。但函数funtion(){ }里面的{ }不属于块级作用域;

    3.作用域的上下级关系

    全局作用域最大, 所有的私有作用域都是在全局作用域下面的,函数声明时会生成私有作用域, 函数写在哪个作用域里面,就是哪个作用域的子级, 子级可以访问父级的变量。

    <script>
        // 全局作用域
        var a=1; // 全局可以访问,其中包括他的后代作用域,例如: fun 函数内就可以访问
        if(true) {
    		let bb = 56; // 块作用域, {}外无法访问
    		const cc=66;
    		console.log(bb,cc); // 56, 66
    	}
    	console.log(bb,cc);// bb is not defined
        function fun() {
            // 函数 fun 的私有作用域
            var b = 2; // 函数外访问不到
            console.log(a); //1
            function fun2() {
                // 函数 fun2 的私有作用域,同时它也位于 fun 函数内部,可以访问 fun 的变量
                var c = 3; // 函数外访问不到
                // 可以访问父级作用域的变量,变量使用规则,查找自身作用域,没有就向上一级作用域查找,找到后就使用并停止查找,没找到就报错 xx is not defined
                console.log(a); //1
                console.log(b); //2
            }
            fun2()
        }
        fun()
        console.log(a); //1
        console.log(b); // b is not defined
    script>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    1. 为什么有块级作用域?
    function fun() {
        for(var i=0; i<5; i++) {
            setTimeout(()=>{console.log(i)})
        }
        console.log(i);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    我们先看看以上函数在浏览器运行会发生什么

    在这里插入图片描述

    循环结束后,console.log(i);依旧打印出了 i 的值;也就是说,for 循环声明的i在函数作用域内可访问,由此可见for循环使用var声明变量会造成一个问题:我只想在循环内使用 i 这个变量,但是循环完后变量 i 在函数内部都可见了。
    而setTimeout(()=>{console.log(i)}); 打印出来的结果更加致命,当循环内存在异步操作时,无法保存每次循环的i值.
    结果是这会造成一些看起来违背常理的问题。形成函数作用域甚至全局作用域内的变量污染。
    而使用let 声明i 的值就可以解决这个问题,因为let声明时会产生块级作用域,而各个块级作用域之间同名变量互不干扰,for 循环时使用let声明i,每一次循环开始初始化i时都会形成独立的作用域,因此当前i只在本轮循环有效,且只在本块级作用域有效,所以每一次循环的i其实都是一个新的变量。
    在这里插入图片描述

    1. JS 如何实现的块级作用域

    正常情况下,JS 引擎在编译代码阶段会生成全局执行上下文和函数执行上下文。这其中,每个上下文又分为了两个部分:变量环境和词法环境。

    这两个环境中存储的是不同的东西,举例说明:

    var a = 0
    let b = 1
    function foo() {
        var a = 1
        let b = 2
        if (true) {
            let b = 3
            console.log(a, b)
        }
    }
    foo()                                   // 1, 3
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    在执行 foo 函数时,它的执行上下文是这样的:
    在这里插入图片描述
    在这里插入图片描述
    有几点需要说明:

    1.使用 var 关键字声明的变量、及函数声明,会被放入变量环境中
    2.使用 let 及 const 关键字声明的变量和常量会被放入词法环境中
    3.词法环境内部也类似于一个栈结构,每一个块结构(即有一对大括号,如条件语句、循环等)内的变量和常量会单独保存(使用 var 关键字声明的不会)
    4.所以,如上面的例子,函数 foo 的词法环境里,有两个区域。下面的区域保存了函数体内使用 let 声明的变量,上面的区域保存了 if 语句中使用 let 声明的变量。所以即使它们都叫做 b,但却是两个不同的变量
    5.如此一来,ES6 就通过执行上下文中的词法环境实现了块级作用域

    16.JavaScript 的同源策略以及如何解决跨域问题

    跨域出现的原因是因为:浏览器同源政策的限制(相同的协议、域名、端口),当协议、域名、端口号三者有一个不相同时,就会出现跨域,浏览器拒收不同源的请求
    解决方式:

    • jsonp

    ** jsonp的原理就是利用 script 标签没有跨域限制,通过script标签src属性,发送带有callback参数的GET请求,服务端将接口返回数据拼凑到callback函数中,返回给浏览器,浏览器解析执行,从而前端拿到callback函数返回的数据 **

    jsonp主要是利用了script标签的src属性不受同源策略的影响,通过后端的配合从而解决跨域问题.
    前端部分代码:

    function handle (data) {
                console.log(data)
            }
    
    const script = document.createElement('script')
    script.src = 'http://127.0.0.1:8080/?callback=handle'
    document.body.insertBefore(script, document.body.firstChild)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    这里创建了一个script标签,然后将它的src属性赋值为请求资源的url地址,并且携带query参数过去,这里的query参数callback=handle中的handle在前端是一个函数,随后将script放入页面,一旦放入页面,scr就会去请求资源了;后端解析了query参数,将callback的值单独拿出来,然后通过这个值来返回数据

    • CORS
      跨域资源共享,主要由后端实现

    • Proxy代理服务器
      跨域的问题根本原因就是返回数据的服务器和请求数据的页面不是一个源,那么就申请一个代理服务器,这个代理服务器和页面在同一个源,所以不会出现跨域的问题,那么这个代理服务器上没有我们需要的数据,所以就把这个请求再转发给有这个数据的服务器上,由于服务器和服务器之间通信不会出现跨域的问题,因为同源策略是浏览器上的,和服务器没关系,所以最后就可以成功把数据请求返回给浏览器。

    CORS支持所有类型的HTTP请求,是跨域HTTP请求的根本解决方案
    JSONP只支持get请求,而且无法知晓请求的数据是否成功,如果一直卡在请求中,我们也不知道。
    日常工作中,用得比较多的跨域方案是cors和Proxy代理服务器,Proxy主要就是利用同源策略对服务器不起作用。

    散装知识点

    var

    1.var 具有变量声明提升;

    // 变量声明提升
    console.log(a); // undefined
    var a = 12;
    
    
    // 2. 变量覆盖, 可重复声明同一个变量;
    var a = 12;
    var a = 24;
    console.log(a); // 24
    
    // 3. 无块级作用域, 在循环体外依旧可以访问
    function fn() {
                console.log(i); // undefined
                for(var i=0; i<3; i++) {
                    console.log(i);
                };
                 console.log(i); // 3
            }
            fn(); // 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    let 和 const

    共性

    1.let和const不会发生变量声明提升的现象,所以一定要在定义后使用,否则会报错。
    2.暂时性死区:一个代码区块中在使用let或const声明变量之前的的区域,该变量都是不可用的,被称为暂时性死区。
    3.不允许重复声明,let和const不允许在相同作用域内,重复声明同一个变量。
    4.let和const声明变量都会产生块级作用域(声明后的下一行代码到声明所在区域结束这一块可以访问)。

    if(true) {
        console.log(a); //a is not defined || Cannot access 'a' before initialization
        let a= 1
        console.log(a); // 1
    }
    console.log(a); // a is not defined
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    区别

    1.const声明一个只读的常量,一旦声明,常量的值就不能改变,因此,声明时就要立即初始化,否则会报错

    ES6新特性
    - set

    set实例对象可以储存一组唯一值, 他的每个元素都是唯一的;
    利用Set对象唯一性的特点,可以轻松实现数组的去重:

    let arr = [1,1,2,3,4,4];
    let mySet = new Set(arr);
    let newArr = Array.from(mySet);
    console.log(newArr); // [1,2,3,4]
    //使用拓展运算符:
    let newArr = [...mySet];
    console.log(newArr); // [1,2,3,4]
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    - 箭头函数与普通函数的区别?
    1. 箭头函数比普通函数更加简洁
    2. 箭头函数没有自己的this

    箭头函数不会创建自己的this, 所以它没有自己的this,它只会在自己作用域的上一层继承this。所以箭头函数中this的指向在它在定义时已经确定了,之后不会改变;

    1. 箭头函数继承来的this指向永远不会被改变

    箭头函数会在自己作用域的上一层继承this,作用域分为全局作用域与函数的局部作用域;因此箭头函数的this大多数情况下会指向window,也有些会在函数内定义箭头函数,此时箭头函数的this就指向他父级函数的this;注意:函调用时的this指向与他调用时的上下文有关,构造函数new出来的子实例的this会指向他本身;定义对象的大括号{ }是无法形成一个单独的执行环境的,它依旧是处于全局执行环境中;
    在这里插入图片描述

    1. 箭头函数没有自己的arguments
    2. 箭头函数没有prototype
    - class类的继承目录第12
    - 模板字符串:
    es6以前写法
    var name = '你好'
    var str = "<div>"+name+"div>";
    document.querySelector('body').append(str);
    es6 模板字符串
    var name = '你好'
    var str = `
    	<div>${name}div>
    `;
    document.querySelector('body').append(str);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    - 解构赋值

    面试题:将两个变量的值互换, 不能使用第三方变量以及工具;

    // 变量值互换
    let a = 1;
    let b = 2;
    [a,b] = [b,a];
    console.log(a,b); // 2,1
    // 变量值互换
    
    // 使用
    let [a,b,c] = [1,2];// a=1;b=2,c=undefinded
    let obj = { 
    	name: "name", 
    	age: 12, 
    	sex: "男" 
    };
    let { name, age, sex } = obj;
    console.log(name, age, sex); //'name' 12 '男'
    let { name: myName, age: myAge, sex: mySex } = obj; //自定义变量名
    console.log(myName, myAge, mySex); //'name' 12 '男'
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    - 拓展运算符...
    let a = [];
    let b = [1,2,3];
    a=[...b];
    console.log(a); // [1,2,3]
    与解构赋值结合使用
    let [b1,...c] = b; //...是剩余运算符,表示赋值运算符右边数组除第一个值外剩余的都赋值给c
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    - Promise对象 目录 5
    - 字面量简写(当我们要声明的对象的属性与要引用值的变量同名时,就可以实现简写。)
    let name = 'jk';
    let age = 18;
    let user = {
    	name,
    	age
    }
    等同于
    let user = {
    	name: name,
    	age: age
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    import和export

    • 一个js文件,可以理解成一个模块,这个模块可以被任意其他的模块引入.
    • export的作用,就是用于从模块中导出函数、对象或原始值,以便其他程序可以通过 import 语句使用它们.
    • 在import 一个文件的时候,会获取这个文件对象,默认是空对象,代表我们不能访问文件的东西。使用export,来给这个对象添加内容
    • export default 给文件对象,添加一个 default属性,default属性的值也是一个对象,且和export default导出的内容完全一致.
    • export default向外暴露的成员,可以使用任意变量来接收,但不能使用{} 的形式来接收.
    • 在一个模块中,export default只允许向外暴露一次
    • 在一个模块中,可以同时使用export default 和export 向外暴露成员
    • 使用 export 向外暴露的成员,在import只能使用 {} 的形式来接收,叫做按需导出
    • 在一个模块中,export 可以向外暴露多个,同时,如果导出的某些成员在import时,不需要 则可以不在 {} 中定义
    • 使用export导出的成员,必须严格按照 导出时候的名称,来使用 {} 按需接收
    • 使用export导出的成员,如果要换个名称,可以使用 as 起别名
    • 命名导出是显式的。它们精确地命名了它们导入的内容,所以我们从它们那里得到了这些信息;

    使用

    • 声明前导出,也叫命名导出
      可以通过在声明之前放置 export 来标记任何声明为导出,无论声明的是变量,函数还是类都可以。
      export let a = 1
    • 其他导出声明方式
      先声明,然后再导出它们,这里的导出必须用一个{}包起来
      let a=1; export {a}
    • 导入所有(import *)
      import * as option from ‘./export.js’
    • 导入为(import as)
      import { a as Mya } from ‘./export.js’ // 为导入的变量a重新起一个名字
    • 导出为(export as)
      export { a as Mya }
      此时 import { Mya } from ‘./export.js’
    • 默认导出(export default)
      export defalut 66 // 一个模块只能向外暴露一次export defalut
      import date from ‘./export.js’ // import 导入时不使用花括号
    • 命名导入与默认导入的区别:
      命名导入需要使用花括号,而默认导入不需要。
      命名导出必须(理应)具有名称,而 export default 可能是匿名的(没有名称)
    • 默认导入
      import defalutDate from ‘./export.js’ // 导入 export defalut 的数据, export defalut 导出的数据
      import { defalut as defalutDate } from ‘./export.js’ // 实际上defalut 就是export default 给文件对象添加的一个属性,我们可以拿到他并重新命名, 但是不能直接用defalut 这个名字,因为它是一个关键字
    //在export.js中导出
    let a = 1;
    let b = function(name) {
    	console.log(`hello ${name}`)
    }
    let c = {
    	aa:'aa',
    	bb:'bb',
    }
    // export 导出写法
    export {
        a,b,c
    } // 导出变量列表
    export const d = 66 // 声明前导出--命名导出
    // export defalut 66 // 导出数据,此时导出什么,import就会接收到什么--默认导出,导入时可自定义变量名接收数据
    --------------------------------------------------------------
    export defalut c // 导出数据,此时导出什么,import就会接收到什么
    
    
    //在import.js中导入
    // 命名导出强制我们在导入时使用正确的名称
    // import {Mya, Myb} from './export.js' // The requested module './export.js' does not provide an export named 'default'
    --------------------------------------------------------------
    import {a,b,c,d} from './export.js' // 变量名必须跟export中一致
    console.log(a,b,c,d); // 1 [Function: b] { aa: 'aa', bb: 'bb' } 66
    --------------------------------------------------------------
    import {a} from './export.js' // 按需导入
    --------------------------------------------------------------
    import {a as AAA} from './export.js' // 导入时使用as起别名
    console.log(AAA) // 1
    --------------------------------------------------------------
    import defalutDate from './export.js' // 导入 export defalut 的数据=
    // 也可以这样写 import { defalut as defalutDate } from './export.js'   // 实际上defalut 就是export default 给文件对象添加的一个属性,我们可以拿到他并重新命名, 但是不能直接用defalut 这个名字,因为它是一个关键字
    console.log(defalutDate) // 66 or {aa:'aa', bb:'bb'}
    --------------------------------------------------------------
    import * as option from './export.js' // 导入 export.js 向外暴露的所有数据,它必须使用as name 起个别名, export defalut 导出的数据会存在defalut属性中.可以使用option.defalut 来拿到它的数据
    console.log(option) // {
    //     a: 1,
    //     b: [Function: b],
    //     c: {aa:'aa',bb:'bb'},
    //     d: 66,
    //     default: { aa: 'aa', bb: 'bb' }
    //   }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    Re-export

    " Re-export "语法:从…允许导入并立即导出,像这样:

    // demo.js
    export {fn} from './demo.js'; // 重新导出demo.js的命名导出
    export {default as User} from './user.js'; // 重新导出user.js的默认导出
    export {default as app} from './app.vue'; // 重新导出vue文件
    
    • 1
    • 2
    • 3
    • 4

    这写法非常实用,举个栗子:
    有一个包含大量模块的文件夹,其中一些功能被导出到外部,而许多模块只是“助手”,供其他包模块内部使用。
    文件结构可能会有多层

    tools/
    	index.js
    	user.js // content: export default function user() {}
    	components/
    		age.js // content: export function age() {}
    		sex.js // content: export function sex() {}
    		...
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    我们希望通过单个入口点公开包的功能。
    换句话说,想要使用我们的包的人,应该只从“主文件”tools/index.js导入。
    只从主文件‘tools/index.js’ 导入开放的功能

    import {age, sex} from 'tools/index.js'
    
    • 1

    由于实际导出的功能分散在包中,我们可以将其导入tools/index.js并从中导出:

    // tools/index.js
    import {age} from './components/age.js';
    export {age};
    import {defalut as user} from './user.js;
    export {user};
    // 上面写法等价于 export {age} from './components/age.js' | export {defalut as user} from './user.js
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    重新导出与导入/导出相比,重新导出的模块在当前文件中不可用。所以在上面的tools/index.js示例中,我们不能使用重新导出的 age 和 user 函数。

    http缓存

    强缓存:在访问资源时、浏览器中有它的缓存副本、如果这个副本有效、直接获取缓存资源、不向服务器发送请求;是否强缓存由 Expires、Cache-Control 和 Pragma 3 个 Header 属性共同来控制。

    协商缓存: 当浏览器的强缓存失效的时候或者请求头中设置了不走强缓存,并且在请求头中设置了If-Modified-Since 或者 If-None-Match 的时候,会将这两个属性值到服务端去验证是否命中协商缓存,如果命中了协商缓存,会返回 304 状态,加载浏览器缓存,并且响应头会设置 Last-Modified 或者 ETag 属性。

    es6新增方法

    from: 将伪数组或可遍历对象转换为真正的数组

    语法: Array.from(object, mapFunction, thisValue)
    // object: 必需,要转换为数组的对象
    // mapFunction: 可选,数组中每个元素要调用的函数
    // thisValue: 可选,映射函数(mapFunction)中的 this 对象
    
    • 1
    • 2
    • 3
    • 4

    find: 用于找出第一个符合条件的数组成员,如果没有找到返回undefined

    array.find(item => item.id == id)
    
    • 1

    findIndex: 用于找出第一个符合条件的数组成员的索引位置,如果没有找到返回-1

    array.findIndex(item => item.id == id)
    
    • 1

    includes: 判断某个数组是否包含给定的值,返回布尔值

    [1,2,3].includes(1); // true
    [1,2,3].includes(4); // false
    
    • 1
    • 2
    webpack
    • webpack 在本质上是一个用于现代javascript应用程序的静态模块打包工具;

    1、依赖管理:方便引用第三方模块、让模块更容易复用,避免全局注入导致的冲突、避免重复加载或者加载不需要的模块。会一层一层的读取依赖的模块,添加不同的入口;同时,不会重复打包依赖的模块。
    2、合并代码:把各个分散的模块集中打包成大文件,减少HTTP的请求链接数,配合UglifyJS(压缩代码)可以减少、优化代码的体积。
    3、各路插件:统一处理引入的插件,babel编译ES6文件,TypeScript,eslint 可以检查编译期的错误。
    总结:webpack 的作用就是处理依赖,模块化,打包压缩文件,管理插件。

    • 打包原理

    webpack打包原理是根据文件间的依赖关系对其进行静态分析,将这些模块按指定规则生成静态资源,当 webpack处理程序时,它会递归地构建一个依赖关系图,其中包含应用程序需要的每个模块,将所有这些模块打包成一个或多个bundle。

    js中i++和++i的区别

    ++i是先执行 i = i +1 再使用 i 的值,而 i++ 是先使用 i 的值再执行 i = i + 1;
    例:

    	var i=1;
        var a=i++;// 使用一个变量保存下表达式 ++i 的值
        console.log(i); //输出2
        console.log(a); //输出1
    
    	var i=1;
        var a=++i; // 使用一个变量保存下表达式 ++i 的值
        console.log(i); //输出2
        console.log(a); //输出2
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    当运算符在操作数之前,称为前增量运算符,它对操作数进行增量操作,并返回操作数计算后的值。
    当运算符在操作数之后,称为后增量运算符,它对操作数进行增量操作,但返回操作数未做增量计算的值。

    for (var i = 0, fn; fn = fns[ i++ ]; ) { } 这句代码的意思是什么?

    下标i从0开始,依次将数组fns的第i个元素的值赋给fn,然后i自加1,直到fn的值为假(或转换成布尔值后为假,其它类型的元素会自动转换成布尔值)时,退出for循环。

    观察者模式

    观察者模式定义了对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都将得到通知,并自动更新。观察者模式属于行为型模式,行为型模式关注的是对象之间的通讯,观察者模式就是观察者和被观察者之间的通讯。

    观察者模式有一个别名叫“发布-订阅模式”,或者说是“订阅-发布模式”,订阅者和订阅目标是联系在一起的,当订阅目标发生改变时,逐个通知订阅者。我们可以用报纸期刊的订阅来形象的说明,当你订阅了一份报纸,每天都会有一份最新的报纸送到你手上,有多少人订阅报纸,报社就会发多少份报纸,报社和订报纸的客户就是上面文章开头所说的“一对多”的依赖关系。

    发布订阅模式

    其实24种基本的设计模式中并没有发布订阅模式,上面也说了,他只是观察者模式的一个别称。
    但是经过时间的沉淀,似乎他已经强大了起来,已经独立于观察者模式,成为另外一种不同的设计模式。
    在现在的发布订阅模式中,称为发布者的消息发送者不会将消息直接发送给订阅者,这意味着发布者和订阅者不知道彼此的存在。在发布者和订阅者之间存在第三个组件,称为调度中心或事件通道,它维持着发布者和订阅者之间的联系,过滤所有发布者传入的消息并相应地分发它们给订阅者。

    订阅者把自己想订阅的事件注册到调度中心,当发布者发布该事件到调度中心,也就是该事件触发时,由调度中心同意调度订阅者注册到调度中心的处理代码。

    观察者模式与发布订阅模式区别

    发布订阅模式相比观察者模式多了个事件通道,事件通道作为调度中心,管理事件的订阅和发布工作,彻底隔绝了订阅者和发布者的依赖关系。即订阅者在订阅事件的时候,只关注事件本身,而不关心谁会发布这个事件;发布者在发布事件的时候,只关注事件本身,而不关心谁订阅了这个事件。

    代码实例:
    发布订阅模式:

    <body>
        <span>菜品上架span>
        <button class="potato">土豆上新button>
        <button class="radish">萝卜上新button>
        <button class="cabbage">白菜上新button>
    body>
    <script>
        let potato = document.querySelector('.potato');
        let radish = document.querySelector('.radish');
        let cabbage = document.querySelector('.cabbage');
        class Release { // 以class的方式写发布者部分代码,可复用,适配性好,可以 new 多个发布者
            constructor() {
                this.on = {}; 
                // 事件集合;里面结构大概是{
                        // type: []; // type: 事件描述; [] 内是订阅者回调;
                // }
            }
            // 监听
            $on = function(type, fn) {
                if(!this.on[type]) { // 没有此事件,新建一个
                    this.on[type] = [];
                }
                this.on[type].push(fn);
            }
            // 解除监听
            $off = function(type, fn) {
                if(!this.on[type]) { // 没有此事件,滚蛋
                    return false;
                }
                if(!fn) { // 没传回调,可以执行删除事件
                    delete this.on[type]
                }
                this.on[type] = this.on[type].filter(item=> item !== fn); // 过滤, 把此回调排除出去
            }
            // 发布
            $release = function(type) {
                if (!this.on[type]) {// 没有此事件,滚蛋
                    return false;
                }
                this.on[type].forEach(item => {
                    item()
                });
            }
        }
        let res = new Release();
    
        // 绑定发布事件
        potato.onclick = function() {
            res.$release('potato')
        }
        radish.onclick = function() {
            res.$release('radish')
        }
        cabbage.onclick = function() {
            res.$release('cabbage')
        }
    
        // 监听
        res.$on('potato',function() {
            console.log('土豆上新啦!快来买')
        });
        res.$on('radish',function() {
            console.log('萝卜上新啦!快来买')
        });
        res.$on('cabbage',function() {
            console.log('白菜上新啦!快来买')
        });
    
    script>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69

    观察者模式:

    class Subject {
        constructor() {
            this.observers = [];
        }
     
        add(observer) {
            this.observers.push(observer);
        }
     
        notify(...args) {
            this.observers.forEach(observer => observer.update(...args));
        }
    }
     
    class Observer {
        update(...args) {
            console.log(...args);
        }
    }
     
    // 创建观察者ob1
    let ob1 = new Observer();
    // 创建观察者ob2
    let ob2 = new Observer();
    // 创建目标sub
    let sub = new Subject();
    // 目标sub添加观察者ob1 (目标和观察者建立了依赖关系)
    sub.add(ob1);
    // 目标sub添加观察者ob2
    sub.add(ob2);
    // 目标sub触发SMS事件(目标主动通知观察者)
    sub.notify('I fired `SMS` event');
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    vue中使用场景

    发布/订阅模式核心:

    • 当某个任务执行完成,就向事件中心“发布”一个信号,其它任务可以向事件中心“订阅”(subscribe)这个事件,从而知道什么时候可以开始执行,这就是发布/订阅模式
      发布/订阅模式在Vue中的应用场景:子组件与父组件的通信方式、兄弟组件通信

    观察者模式核心:

    • 观察者(Watcher): 每个观察者必须有一个 update() 方法,当事件发生时,执行观察者的update()。观察者可以理解为发布/订阅模式的订阅者。
      观察者模式在Vue中应用场景:数据实时更新视图
      Vue组件中被模板引用的属性会收集为依赖,比如< div >{{ msg }}< /div>
      当渲染模板时,msg将会触发getter函数,即收集依赖的地点就是在getter中。
      在某种情况下对msg进行了重新赋值,那么将会触发setter函数,既然msg改变了,那么视图就要更新了,即通知更新的地点在setter中
      如何通知更新?在setter中使用dep的notify方法通知Watcher,即调用观察者的update方法,使得组件重新渲染。
    new的三步
    1. 创建一个空对象
    2. 继承构造函数的属性与方法
    3. 返回this指向的新对象
    src与href的区别

    href用于与资源建立连接
    src用于获取当前资源

    数组排序
    • array.sort()
      sort()方法有一个可选参数,是用来确定元素顺序的函数。如果这个参数被省略,那么数组中的元素将按照ASCII字符顺序进行排序。如:
     let arr = ['A','C','B','D']
     arr.sort()
     console.log(arr) // ['A','B','C','D']
    
    
    • 1
    • 2
    • 3
    • 4

    但是数组的元素为数字时 就不行了

    let arr = [15,8,25,3]
     arr.sort()
     console.log(arr) // [15,25,3,8]
    
    • 1
    • 2
    • 3

    原因是sort方法会调用每个数组项的toString()方法,得到字符串,然后再对得到的字符串进行排序。虽然数值15比3大,但在进行字符串比较时"15"则排在"3"前面(ASCII字符顺序)。
    这时,sort()方法的回调函数就起到了作用,我们把这个回调函数叫做比较函数。

    
     let arr = [15,8,25,3]
     arr.sort((x,y)=> x - y) // 正序
     console.log(arr) // [3,8,15,25]
     arr.sort((x,y)=> y - x) // 倒序
    
    • 1
    • 2
    • 3
    • 4
    • 5

    比较函数接收两个参数,如果第一个参数应该位于第二个之前则返回一个负数,
    如果两个参数相等则返回0,如果第一个参数应该位于第二个之后则返回一个正数。

    • array.reverse()
      反转数组

    • 冒泡排序
      原理: 一次比较两个相邻的数,如果不符合规则互换位置,一次比较就能够将最大或最小的值放在数组最后一位,继续对除 已排好序 之外的所有元素重复上述过程

    var arr = [123,203,23,13,34,65,65,45,89,13,1];
    for(var i=0; iarr[j+1]){
    	        var temp = arr[j];
    	        arr[j] = arr[j+1];
    	        arr[j+1] = temp;
    	    }
    	}    
    }
    console.log(arr); // [1, 13, 13, 23, 34, 45, 65, 65, 89, 123, 203]
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 选择排序
      原理:循环获取最小值所在位置(即索引),然后通过索引交换数据。
    
    var arr = [8,6,3,4,7];
    for(var i=0;iarr[j]){
    			var temp = arr[i];
    			arr[i] = arr[j];
    			arr[j] = temp;
    		}
    	}
    }
    console.log(arr); //(5) [3, 4, 6, 7, 8]
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    性能优化

    vue

    • 单页面采用keep-alive缓存组件
    • 尽可能拆分组件,来提高复用性、增加代码的可维护性
    • key的唯一性
    • 合理使用路由懒加载、异步组件

    加载性能
    按需加载、图片懒加载、图片压缩、 骨架屏 、cdn域名加速

    移动端适配

    amfe-flexible 和 postcss-pxtorem 结合使用
    amfe-flexible 是配置可伸缩布局方案,主要是将 1rem 设为 viewWidth/10
    postcss-pxtorem是postcss的插件,用于将像素单位生成rem单位

    for in、 for…of区别

    for in: 遍历key,遍历对象时会访问原型上的属性

    let obj = {
    	name:1
    }
    obj.__proto__.age=6; // 等价于 Object.prototype.age=6
    for(let item in obj) {
        console.log(item,obj[item])
    }
    // name 1 
    // age 6
    let arr = [{
    	name:1
    }]
    for(let item in arr) {
        console.log(item,arr[item])
    }
    // 0 {name:1}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    for…of: 只能遍历数组,不能遍历对象、对象类型没有实现迭代器

    MVC 和 MVVM 区别

    MVC表示“模型-视图-控制器”,MVVM表示“模型-视图-视图模型”;MVVM是由MVC衍生出来的。MVC中,View会直接从Model中读取数据;MVVM各部分的通信是双向的,而MVC各部分通信是单向的;MVVM是真正将页面与数据逻辑分离放到js里去实现,而MVC里面未分离。

    MVC(Model-View-Controller)

    MVC模式将应用程序划分为三个部分:

    Model: 模型(用于封装与应用程序的业务逻辑相关的数据以及对数据的处理方法)
    View: 视图(渲染页面)
    Controller: 控制器(M和V之间的连接器,用于控制应用程序的流程,及页面的业务逻辑)

    MVC特点:

    MVC模式的特点在于实现关注点分离,即应用程序中的数据模型与业务和展示逻辑解耦。在客户端web开发中,就是将模型(M-数据、操作数据)、视图(V-显示数据的HTML元素)之间实现代码分离,松散耦合,使之成为一个更容易开发、维护和测试的客户端应用程序
    其逻辑走向:

    1. 用户操作View 传送指令到 Controller ;
    2. Controller 完成业务逻辑后,要求 Model 改变状态 ;
    3. Model 将新的数据发送到 View,用户得到反馈;
    MVVM(Model-View-ViewModel)

    MVVM是将“数据模型数据双向绑定”的思想作为核心,因此在View和Model之间没有联系,通过ViewModel进行交互,而且Model和ViewModel之间的交互是双向的,因此视图的数据的变化会同时修改数据源,而数据源数据的变化也会立即反应到View上
    MVVM包括view视图层、model数据层、viewmodel层。各部分通信都是双向的。采用双向数据绑定,View的变动,自动反映在 ViewModel,反之亦然。其中ViewModel层,就是View和Model层的粘合剂,他是一个放置用户输入验证逻辑,视图显示逻辑,发起网络请求和其他各种各样的代码的极好的地方。说白了,就是把原来ViewController层的业务逻辑和页面逻辑等剥离出来放到ViewModel层

    我们在浏览器地址栏输入url并回车之后浏览器会干嘛??
    浏览器的工作原理

    从输入一个url到页面加载完成需要经过DNS查找--TCP握手-http请求-构建dom树---构建css模型树---合并dom和css对象模型树-布局-绘制

    DNS查找–把域名转化为真实的IP地址、根据IP地址找到所对应的服务器、
    TCP握手–在找到服务器之后浏览器根据握手机制与服务器建立连接、
    http请求–在建立了浏览器与服务器的连接后、浏览器会发起请求、来获取服务器响应、响应就是服务器返回的html网页代码;

    在收到html代码后浏览器开始渲染网页、这里一共有五步,这五步统称为关键渲染路径

    一、构建DOM树
    第一步是解析HTML并构建DOM数据、DOM树是html文档在浏览器中的对象表示、可以使用js来操作它、浏览器在解析html的时候是按顺序执行的、并且只有一个主线程进行解析、
    如果遇到Script标签、那么浏览器会加载javascript文件、并执行里面的代码、这个时候主线程会暂停解析html、只有js代码执行完成之后才继续
    对于图片和css文件、或者是设置了defatl、或者是异步的script标签、那么它不会影响主线程、而是会异步的加载、
    另外浏览器有一个预扫描线程、它会扫描html代码、提前把css文件、字体、已经js文件下载下来、这里的下载是异步的、不会影响主线程
    二、构建cssdom(对象模型)树
    第二步是构建cssObjectModel树 cssObjectModel是css在浏览器中的对象表示、也是树状结构
    三、合并dom树和css对象模型树
    第三步浏览器会从DOM的根节点开始合并cssObjectModel中的样式到DOM中的每个节点形成一颗渲染树
    四、布局
    第四步生成渲染树之后浏览器会根据样式去算每个可见节点、也就是没有设置display为none的节点、它们的宽高和位置等、对所有的节点进行布局规划、
    对于像图片这样的节点、如果没有指定宽高、那么浏览器会先忽略它的大小、在图片加载完成之后、浏览器会根据图片的宽高和位置再次计算受影响的节点的大小和位置、这个过程叫做重排reflow
    五、重绘
    第五步在第一次布局完成之后、浏览器会真正的把节点和节点的样式 例如背景、阴影、边框等绘制到屏幕上、这个过程要十分的快速、否则会影响动画和交互的性能、
    如果之前的布局发生了回流、也就是加载了像图片这样的节点之后、浏览器还会发生重绘、把变化的布局重新绘制到屏幕上
    在绘制期间也可能会有组合发生、因为在渲染节点是可能会产生新的图层、比如 例如设置了 opacit、transform等属性的节点、浏览器还需要把这些涂层组合起来、
    按正确的堆叠顺序渲染、同样的回流和重绘操作也会引发重新组合操作、

    这面五步完成后设置了defer、或者是async 的js文件开始加载并执行、完成之后整个网页就加载完成了、并且可以和用户进行交互了

    什么是重绘、回流

    重绘(repaint):当元素样式的改变不影响页面布局时,比如元素的颜色,浏览器将对元素进行的更新,称之为重绘。

    回流(reflow):也叫做重排。当元素的尺寸或者位置发生了变化,就需要重新计算渲染树,这就是回流,比如元素的宽高、位置,浏览器会重新渲染页面,称为回流,又叫重排(layout)。
    回流是影响浏览器性能的关键因素。因为一个元素的回流,可能会导致了其所有子元素以及紧随其后的节点、或祖先节点元素,或部分页面或整个页面的回流

    关系:回流必定会触发重绘,重绘不一定会触发回流。重绘的开销较小,回流的性能消耗较高;

    17. TCP传输的三次握手、四次挥手策略

    实质就是TCP通信的连接和断开

    三次握手

    1. 发送端首先发送一个带SYN的标志的数据包给服务端;
    2. 服务端收到后,回传一个带有SYN/ACK标志的数据包传达确认信息;
    3. 客户端再回传一个带有ACK标志的数据包,表示我知道了,握手结束;

    其中:SYN标志位数置1,表示建立TCP连接;ACK标志表示验证字段

    四次挥手

    1. 客户端发送一个FIN,用来关闭客户端到服务端的数据传送,客户端进入FIN_WAIT_1状态;
    2. 服务端收到FIN后,发送一个ACK给客户端,确认序号为收到序号+1(与SYN相同,一个FIN占用一个序号),服务端进入CLOSE_WAIT状态;
    3. 服务端发送一个FIN,用来关闭服务端到客户端的数据传送,服务端进入LAST_ACK状态;
    4. 客户端收到FIN后,客户端t进入TIME_WAIT状态,接着发送一个ACK给Server,确认序号为收到序号+1,服务端进入CLOSED状态,完成四次挥手

    其中:FIN标志位数置1,表示断开TCP连接

    18. 数组常用方法

    1. Array.push(…item)

    向数组的末尾添加一个或多个元素,并返回新的数组长度,原数组改变;

    2. Array.pop()

    删除并返回数组的最后一个元素,若该数组为空,则返回undefined,原数组改变;

    3. Array.unshift(…item)

    向数组的开头添加一个或多个元素,并返回新的数组长度。原数组改变;

    4. Array.shift()

    删除数组的第一项,并返回第一个元素的值。若该数组为空,则返回undefined。原数组改变;

    5. Array.concat(arr1,arr2,…)

    合并两个或多个数组,生成并返回一个新的数组。原数组不变;

    6. Array.join(‘-’)

    将数组的每一项用指定字符连接形成一个字符串。默认连接字符为 “,” 逗号。返回数组元素拼接的字符串,原数组不变;

    7. Array.reverse()

    将数组倒序排列。返回改变排序后的数组,原数组改变;

    8. Array.sort(fun(a,b))

    接受一个比较函数(可选),比较a、b或者a、b内部的值function(a,b) {return a-b},表示从小到大排序; function(a,b) {return b-a},表示从大到小排序,不传的话默认按照a-z字符编码的顺序进行排序,返回改变排序后的数组,原数组改变;

    9. Array.map(fun(item,index,arr))

    item: 当前元素; index:当前元素索引; arr:原数组;
    循环原数组的每一项元素作为实参执行回调函数后,返回一个新的数组。原数组不变;
    无法用break 跳出循环;

    10. Array.slice()

    入参:
    array.slice(n, m),从索引n开始查找到索引m处(不包含m,可以理解为从索引n开始,查找m-n个元素);
    array.slice(n) 第二个参数省略,则一直查找到末尾;
    array.slice(-n,-m) slice支持负参数,从最后一项开始算起,-1为最后一项,-2为倒数第二项;
    返回查找到的元素组成的新数组,不改变原数组;

    11. Array.splice(index,howmany,item1,item2,…)

    用于添加或删除数组中的元素。从index位置开始删除howmany个元素,并将item1、item2, …数据从index位置依次插入。howmany为0时,则不删除元素;不传howmany时,从index位置开始删除后面所有元素
    返回一个数组里面是删除掉的元素,原数组改变;

    12. Array.forEach(function(item,index,arr))

    item: 当前元素; index:当前元素索引; arr:原数组;
    用于循环数组的每个元素,并将元素传递给回调函数做响应处理。无返回值,原数组不变;

    13. Array.filter(function(current, index, arr){ return current>0 })

    筛选arr中符合条件的元素并返回一个集合;
    current: 当前元素; index: 当前元素的下标; arr: 原数组对象;current>0: 判断表达式,符合条件的元素会被包装到一个新的数组中返回,无表达式则会返回一个空数组;
    filter方法会返回一个符合筛选条件的数据的数组;
    无法用break 跳出循环;

    let newArr = [1,1,2,3].filter(function(current, index, arr){ return current>1 })
    console.log(newArr);// [2, 3]
    
    • 1
    • 2

    14. Array.every(function(item,index,arr))

    item: 当前元素; index:当前元素索引; arr:原数组;
    对数组中的每一项进行判断,若都符合则返回true,否则返回false,原数组不变;

    15. Array.includes(val)

    Array: 当前数组; val:用于判断的值;
    判断一个数组Array是否包含一个指定的值val,如果是返回 true,否则false,原数组不变;

    16. Array.reduce(function(total,currentValue, index,arr){return total+currentValue }, init)

    total: 初始值, 或者每次循环计算结束后的返回值; currentValue:当前元素; index:当前元素的索引; init: 初始值(未设置时 total 等于数组第一项);
    reduce方法为数组中的一个高阶函数,接受两个参数,第一个参数为回调函数,第二个为初始值。如果不给入初始值则会以数组第一项为初始值;
    reduce会循环数组每一项,并且会保留上一次循环的结果供下一次循环使用,最终结果为数组循环完毕后的最终返回(return)的值;
    reduce是从左往右循环执行,还有一个 reduceRight 是从右往左循环执行;
    返回 total 的最终返回结果;
    原数组不变;

    17. Array.from({})

    Array.from()方法就是将一个类数组对象或者可遍历对象转换成一个真正的数组,也是ES6的新增方法;
    那么什么是类数组对象呢?所谓类数组对象,最基本的要求就是具有length属性的对象;
    from 方法会返回一个数组;
    原对象不变;

    19.JavaScript的装箱和拆箱

    1. 包装类与原始值转换过程叫做「装箱」和「拆箱」,装箱是将值类型包装为对象类型,拆箱是将对象类型转换为值类型;
    2. 装箱和拆箱是 JS 来应对「在值类型数据上调用对象方法」的处理技术,使得基础类型与相应的对象类型被统一;
    3. 至于为什么要装箱和拆箱,从内存使用的角度来看,是为了更好的性能。值类型存在栈里,引用类型存在堆里,在使用值类型的时候从栈里取的速度快,且大部分时候不需要调用对象类型中的方法,调用的时候用包装类型包装一下,调用完删除;

    20. axios 封装

    主要处理内容:

    • 发出请求时自动在请求头中添加token;
    • 对服务器响应进行全局性错误处理;
    • 对axios请求进行统一封装;
      关键api:
    const instance = axios.create({
      baseURL: target,
      timeout: 300000,
    });
    
    // 请求拦截
    instance.interceptors.request.use(fn1, fn2); // fn1: 请求正常进入拦截; fn2:请求异常,可能传输URL,或者data有问题
    
    // 响应拦截
    instance.interceptors.response.use(fn1, fn2); // fn1: 请求正常,服务器返回数据,根据code值做数据处理; fn2:请求异常,服务器响应超时,401等
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    21. 如何实现接口请求时在请求头添加一个date参数,已知web请求不能在请求头添加date参数(因为date是一个关键字)?

    可使用服务端语言发送请求,如nodejs;实际上运用了服务器代理的方式,服务器之间请求数据不受浏览器限制;

    22. 前端如何压缩img图片大小?

    可使用 cavans 实现图片压缩:

    先通过js中img构造函数,实例化img对象,后将图片的路径传入img.src,再建立一个canvas画布,将 img 实例绘制上去,后对画布进行各方面的数值的设置;

      /* file 图片压缩方法-canvas压缩  压缩图片--根据 宽 高 画质压缩图片
    */
       function compressUpload(file, config) { // file: file文件对象
          let read = new FileReader();
          read.readAsDataURL(file);
          const fileName = file.name;
          return new Promise((resolve, reject) => {
            // 生成canvas
            let canvas = document.createElement("canvas");
            let ctx = canvas.getContext("2d");
            let _this = this;
            read.onload = function (e) {
              let img = new Image();
              img.src = e.target.result;
              img.onload = function () {
                let w = this.width;
                let h = this.height;
                let scale = w / h;
                w = config.width || config.height * scale || w;
                h = config.height || config.width / scale || h;
                // 最大宽高如有限制时的处理
                w = config.maxWidth && w > config.maxWidth ? config.maxWidth : w;
                h = config.maxHeight && h > config.maxHeight ? config.maxHeight : h;
                w = Math.min(w, h * scale) || w;
                h = Math.min(h, w / scale) || h;
    
                let quality = 0.7; // 默认图片质量
                // 创建属性节点
                let anw = document.createAttribute("width");
                anw.nodeValue = w;
                let anh = document.createAttribute("height");
                anh.nodeValue = h;
                canvas.setAttributeNode(anw);
                canvas.setAttributeNode(anh);
                ctx.drawImage(this, 0, 0, w, h);
                if (config.quality && config.quality <= 1 && config.quality > 0) {
                  quality = config.quality;
                }
                let base64 = canvas.toDataURL("image/jpeg", quality);
                // 回调函数返回base64的值,也可根据自己的需求转换
                resolve(base64);
                canvas = null;
              };
            };
          });
        };
        
        /*  图片压缩方法-canvas压缩   根据画质压缩图片 */
    function compressUpload(image, file, quality) { // image: img实例对象
      let canvas = document.createElement("canvas");
      let ctx = canvas.getContext("2d");
      let { width } = image,
        { height } = image;
      canvas.width = width;
      canvas.height = height;
      ctx.fillRect(0, 0, canvas.width, canvas.height);
      ctx.drawImage(image, 0, 0, width, height);
      let base64 = canvas.toDataURL(file.type || "image/jpeg", quality);
      // 压缩后调用方法进行base64转Blob,方法写在下边
      return  base64;
      canvas = null;
    };
    
    
    //调用:
    
    let file, // file 图片文件
    	image = new Image(),
        resultBlob = "",
        config = {quality: .5,width: 200, height: 150, maxWidth: 400, maxHeight : 300};
    image.src = URL.createObjectURL(file);
    image.onload = () => {
    	resultBlob = compressUpload(file, config );
    	resultBlob = compressUpload(image, file, quality);
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
  • 相关阅读:
    EMCC 删除配置错误的数据库信息 以及修改度量METRICS
    Web3.0实战(02)-联盟链入门讲解
    【LeetCode每日一题合集】2023.10.9-2023.10.15(贪心&⭐位运算的应用:只出现一次的数字)
    SpringIoc依赖查找-5
    LeetCode【28. 找出字符串中第一个匹配项的下标】
    补充游戏思考13:游戏服务器杂谈(主要讲mmorpg,年更系列,未完待续10/20)
    回归预测 | MATLAB实现SSA-DELM和DELM麻雀算法优化深度极限学习机多输入单输出回归预测对比
    网络程序通信的流程---socket与TCP的简单认识
    SpringBoot整合POI实现Excel文件读写操作
    leetcode日记(32)字符串相乘
  • 原文地址:https://blog.csdn.net/weixin_45734686/article/details/124844373