二 JS部分面试题

428 阅读23分钟

一 JS中的原型和原型链的理解?

补充 image.png image.png

// 补充 判断属性和方法是否是本身的:xx.hasOwnProperty('属性名'|| 方法名)

二 闭包(在防抖节流中使用了)

image.png

image.png 闭包的缺点

变量会驻留在内存中,造成内存损耗问题。
解决:把闭包的函数设置为null

二 防抖节流(补充)

项目中:用户名密码登录使用了,uview提供好的。
防抖:(只执行最后一次)用户触发事件过于频繁,只要最后一次事件的操作,比如:实时搜索,拖拽
   防抖代码:
	<input type="text">
        
let inp = document.querySelector('input')
inp.oninput = debounce(function () {
	console.log(this.value);
}, 500)

function debounce(fn, delay) {
	let t = null;
	return function () {
		if (t !== null) {
			clearTimeout(t)
		}
		t = setTimeout(() => {
			fn.call(this);
		}, delay);
	}
}



节流:(控制执行次数) 短时间内大量执行事件,就只执行一次(比如页面滚动)

window.onscroll = throttle(function () {
        console.log("hello");
}, 500)

function throttle(fn, delay) {
    let flag = true;
    return function () {
        if (flag) {
            setTimeout(() => {
                fn.call(this)
                flag = true
            }, delay);
        }
        flag = false
    }
}




防抖:在搜索框 会监听搜索内容的变化,有变化就发送请求,这样比较耗费资源,
使用第三方工具包 lodash 的 debounce 方法,在设定的防抖时间内输入内容,不需要发送请求,直到停下的时候,
超过设定的时间到了才发送请求,没停下之前,一直输入是不会发送请求的
应用场景:实时搜索,拖拽
完整的写法要写上handler 才能配置 immediate: true 和 deep:true

// debounce 函数
// 参数1:函数
// 参数2:防抖时间
// 返回值:防抖之后的函数,和参数1功能是一样的

 toutiao-m: views search search-suggess.vue
    // 子组件监听父组件搜索框内容的变化
    // lodash 支持按需加载,有利于打包结果优化  1、安装 lodash
    import { debounce } from "lodash"
  watch: {
    searchText: {
      // handler(value) { 
      //   console.log(value)
      //   this.loadSearchSuggess(value) // 调用搜索请求
      // },
      handler: debounce(function (value) {  // debounce 是防抖优化的功能 1、安装 lodash
        // console.log(value)
        this.loadSearchSuggess(value)
      }, 200),
      immediate: true // 该回调将会在侦听开始之后被立即调用
    }
  },

二(补充) 闭包 和 高阶函数有什么区别?

**高阶函数**他**接收函数作为参数**或者**将函数作为返回值输出**, 是对其他函数进行操作的函数,

高级函数使用场景:数组的很多方法都是
sort some every find findIndex filter 

promise
ajax回调函数
var getUserInfo = function( userid, callback) {
$.ajax('http://xxx.com/getUserInfo?' + userid, function( data ){
    if (typeof callback === 'function') {
        callback( data )
    }
});
}
getUserInfo(1233,function( data ){
    console.log( data )
});

数据类型检测:
var isType = function( type ){
return function( obj ){
    return Object.prototype.toString.call( obj ) === '[object ' + type +']';
    }
}

var isString = isType( 'String' );
var isArray = isType( 'Array' );
var isNumer = isType( 'Number' );
console.log( isArray([1,2,3]) );

创建100个div之后 一次性隐藏
var appendDiv = function( callback ){
for (var i = 0; i < 100; i++){
    var div = document.createElement('div');
    div.innerHTML = i;
    document.body.appendChild( div );
    if (typeof callback === 'function'){
        callback( div )
   }
  }
}
appendDiv( function( node ){
    node.style.display = 'none'
});

三 ES6新增特性?

1 let/const, var区别:
    1.1 var声明变量存在变量提升,letconst不存在变量提升
    1.2 letconst都是块级局部变量 就是只在当前代码块起作用
    1.3 const 声明时候必须赋值 ,let 只能进行一次赋值,
        const 声明后不能再修改 ,如果声明的是复合类型数据,可以修改其属性
    同一作用域下
    1.4 letconst不能声明同名变量,而var可以
    
2 箭头函数 和 普通函数的区别:
    2.1 普通函数可以有匿名函数,也可以有具体名函数,但是箭头函数都是匿名函数。
    2.2 箭头函数不能用于构造函数,因为创建构造函数的过程中,会将this指向创建的对象,但是箭头函数本身没有this,所以会报错。
    2.3 箭头函数中this的指向不同
        在普通函数中 this总是指向调用它的对象,如果用作构造函数,this指向创建的对象实例。
        箭头函数本身没有this,但是它在声明时可以捕获其外层执行执行环境的thisthis一旦被捕获,就不再发生变化,任何方法都改变不了其指向,如 call() , bind() , apply()

    箭头函数在全局作用域声明 所以它捕获全局作用域中的thisthis指向window对象。
    2.4 箭头函数不绑定arguments,取而代之用rest参数…解决
    2.5 箭头函数不具有prototype原型对象。
    
    2.6 箭头函数不具有super2.7 箭头函数不具有new.target2.8 箭头函数不能Generator函数,不能使用yeild表达式关键字。
    
3 模板字符串
4 解构赋值,
5 模块的导入(import)和导出(export default/export),
6 Promise 用于更优雅地处理异步请求。
    Promise是异步编程的一种解决方案,有了它就可以避免层层嵌套的回调函数。Promise是一个容器,
    通常保存着一个异步操作的结果。

    1.生成Promise实例。例:

    const promise = new Promise(function(resolve, reject) {

    // ... some code

    if (/* 异步操作成功 */){

    resolve(value);

    } else {

    reject(error);

    }});

    其中resolve和reject是两个由js引擎提供的函数,分别用于将状态从“进行中”变为“成功”或“失败”,
    调用它们并不会终结Promise的参数函数的执行。

    2.待实例生成后,用then分别指定两种状态的回调函数:

    promise.then(function(value) {

    // success

    }, function(error) {

    // failure

    });

    3.还可以用catch()指定发生错误时的回调函数,用finally()指定不管Promise对象最后状态如何都会执行的操作。例:

    promise

    .then(result => {···})

    .catch(error => {···})

    .finally(() => {···});

    4.Promise.all()用于将多个Promise实例包装成一个新的Promise实例,多个请求会并发执行,只有每个状态都变成fulfilled(成功)才会变成fulfilled,只要有一个rejected(失败)则合起来状态变成rejected。例:

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


7 async/await 区别  
    async特点:是用来定义异步函数的,直接打印函数名会得到一个promise对象,就可以使用函数名称去调用.then方法,其实就是promise对象在调用这个方法了。
  
    await特点:await后面跟的是任意表达式,但是一般使用的时候,一般放的是promise的表达式。
    
    async 内部实现,如果执行成功了,有返回值,其实执行的是 promise.resolve() 如果出错的话,调用的是 promise.reject(), 最后都会返回promise对象,用catch捕获到。
    await绑定的事promise对象,等待后面的promise对象执行完毕,拿到 promise.resolve()返回值,再往下继续执行后面的代码,所有有一个等待的意思,如果Promise对象变为reject状态,整个async函数都会中断执行,因此可以将await放在try...catch里面。
   
  优势:es7的语法,  比promise更好的解决了回调地狱,使得异步操作更加方便,async函数完全可以看作多个异步操作包装成的一个Promise 对象

补充: promise async await的区别:

 promise 是 es6的,链式操作
 promise 中有自己的捕获机制,包含 catch ,因为我们知道 如果reject或resolve,如果出了错,他会自己报错的
而 asyncawait 的话主要是在函数内自己定义才能正常使用

promise 提供的方法比async多一些,比如常见的 all方法,race方法,
Promse.race就是赛跑的意思,)里面哪个结果获得的快,就返回那个结果,不管结果本身是成功状态还是失败状态

这就是他们的一些区别。
async await比较好用,这是将来一定的主流,但是promise可以达到同样的效果,这是对 promise asyncawait区别的了解。

 

   
   以前的答案:
   
   ES2017引入async使得异步操作更加方便,async函数完全可以看作多个异步操作包装成的一个Promise 对象。
    async表示函数里有异步操作,await表示后面的表达式需要等待结果。例:

    async function getStockPriceByName(name) {

    const symbol = await getStockSymbol(name);

    const stockPrice = await getStockPrice(symbol);

    return stockPrice;

    }

    getStockPriceByName('goog').then(

    function (result) {console.log(result);});

    async函数返回一个Promise对象,因此可以使用then添加回调函数,async内部返回的值会成为then的参数。该Promise 对象必须等到内部所有await命令后面的Promise对象执行完,才会发生状态改变,除非遇到return语句或者抛出错误。也就是说,只有async函数内部的异步操作执行完,才会执行then方法指定的回调函数。

    函数执行时一旦遇到await就会先返回,等异步操作完成再接着执行函数体内后面的语句。如果多个异步操作不存在继发关系,最好用Promise.all()让它们同时触发,缩短程序执行时间。

    任何一个await语句后面的Promise对象变为reject状态,整个async函数都会中断执行,因此可以将await放在try...catch里面。


8 for of遍历的是键值对中的值
9 for in遍历的是键值对中的键

10 Symbol 一种新的原始数据类型Symbol,表示独一无二的值

    Symbol通过Symbol函数生成,可接受一个字符串作为参数,该字符串只是作为对当前Symbol值的描述,因此相同参数的Symbol函数的返回值并不相等。例:
    let s = Symbol('foo');
    s.description //'foo'


11 SetMap

    Set 存储任何类型的唯一值,即集合中所保存的元素是不重复的。类数组结构。
    Map 是一组键值对的结构,具有极快的查找速度
    const set = new Set();
    const map = new Map();

    Set的属性和方法有:
    size属性返回结构成员总数,
    add()添加某个值,
    delete()删除某个值,
    has()查找某值是否为其成员,
    clear()清除所有成员;

    Map的属性和方法有:
    size、
    set(key, value)、
    get(key)、
    delete(key)、
    has(key)、
    clear()。
    
    与数组一样SetMap都有forEach()使用回调函数遍历每个成员,参数依次为键值、键名(set的键名就是键值),例:

    mySet.forEach((value, key) => console.log(key + ' : ' + value));

    另外,扩展运算符...也可以用于SetMap,例:let arr = [...set];
    
12 还有一些数组字符串的新方法

   
   Array.of() 可以解决array参数不一样的时候 返回结果的问题。
   Array.of(1,2,3) // [1,2,3] 
   Array.of(3) // [3]
   Array(3) // [,,,] 
 
   
    Array.from()
    1.会把一个类数组或对象,转成真正的数组,但是这个类数组必须具有length属性
    var list={
        0:"韩信",
        1:"李白",
        2:"诸葛亮",
        3:"赵云",
        length:4
    }
    var arr = Array.from(list)
    console.log(arr);//输出结果: ["韩信", "李白", "诸葛亮", "赵云"]
    
    2.可以讲一个字符串转成数组的形式
    var str = 'chenbin'
    var arr1 = Array.from(str)
    console.log(arr1);// 输出结果: ["c", "h", "e", "n", "b", "i", "n"]

    3.可以传入第二个参数,类似于map()方法(Array.from(obj,mapFn,thisArg)的格式相当于Array.from(obj).map(mapFn,thisArg)),将每个元素进行处理,将处理后的元素放回数组
     var arr3 = [1,2,3,4,5]
     var newarr = new Set(arr3)
     console.log(Array.from(newarr,item=>item+1));//输出结果: [2, 3, 4, 5, 6]

   
    forEach() 方法用于调用数组的每个元素,并将元素传递给回调函数,对于空数组是不会执行回调函数的。
    let arrInfo=[4,6,6,8,5,7,87]
    arrInfo.forEach((item,index,arr)=>{
        //遍历逻辑
    })
    其中:
      item代码遍历的每一项,
      index:代表遍历的每项的索引,
      arr代表数组本身
    
    filter() 方法创建一个新的数组,新数组中的元素是通过检查指定数组中符合条件的所有元素。
        array.filter(function(currentValue,index,arr), thisValue)
        注意: filter() 不会对空数组进行检测。
        注意: filter() 不会改变原始数组。
        
    every() 方法用于检测数组所有元素是否都符合指定条件(通过函数提供)。
    every() 方法使用指定函数检测数组中的所有元素:
       如果数组中检测到有一个元素不满足,则整个表达式返回 *false* ,且剩余的元素不会再进行检测。
       如果所有元素都满足条件,则返回 true。
       array.every(function(currentValue,index,arr), thisValue)
       注意: every() 不会对空数组进行检测。
       注意: every() 不会改变原始数组。
       
    some() 方法用于检测数组中的元素是否满足指定条件(函数提供)。
    some() 方法会依次执行数组的每个元素:
      如果有一个元素满足条件,则表达式返回true , 剩余的元素不会再执行检测。
      如果没有满足条件的元素,则返回false。
      array.some(function(currentValue,index,arr),thisValue)
      注意: some() 不会对空数组进行检测。
      注意: some() 不会改变原始数组。
    find()查找数组中符合条件的元素,若有多个符合条件的元素,则返回第一个元素。
        let arr = Array.of(1, 2, 3, 4); 
        console.log(arr.find(item => item > 2)); // 3 
        // 数组空位处理为 undefined 
        console.log([, 1].find(n => true)); // undefined
        
    findIndex()查找数组中符合条件的元素索引,若有多个符合条件的元素,则返回第一个元素索引。
        let arr = Array.of(1, 2, 1, 3);
        // 参数1:回调函数
        // 参数2(可选):指定回调函数中的 this 值
        console.log(arr.findIndex((item) => item == 2)); // 1

        // 数组空位处理为 undefined
        console.log([, 1].findIndex((n) => true)); //0
    
    fill() 一定范围索引的数组元素内容填充为单个指定的值。 
        let arr = Array.of(1, 2, 3, 4);
        // 参数1:用来填充的值
        // 参数2:被填充的起始索引
        // 参数3(可选):被填充的结束索引,默认为数组末尾
        console.log(arr.fill(0,1,2)); // [1, 0, 3, 4]
        
    includes() 数组是否包含指定值。如果找到匹配的字符串则返回 true,否则返回 falseincludes() 方法区分大小写
       注意:与 SetMap 的 has 方法区分;Set 的 has 方法用于查找值;Map 的 has 方法用于查找键名。

13 对象新方法:
    Object.assign(target, source_1, ···)   
    let target = {a: 1};
    let object2 = {b: 2};
    let object3 = {c: 3};
    Object.assign(target,object2,object3);  
    // 第一个参数是目标对象,后面的参数是源对象
    target;  // {a: 1, b: 2, c: 3}

    如果目标对象和源对象有同名属性,或者多个源对象有同名属性,则后面的属性会覆盖前面的属性。
    如果该函数只有一个参数,当参数为对象时,直接返回该对象;当参数不是对象时,会先将参数转为对象然后返回。
    Object.assign(3); // Number {3} 
    typeof Object.assign(3); // "object"
    
    注意:因为 nullundefined 不能转化为对象,所以会报错:
    Object.assign(null); // TypeError: Cannot convert undefined or null to object 
    Object.assign(undefined); // TypeError: Cannot convert undefined or null to object 
    当参数不止一个时,nullundefined 不放第一个,即不为目标对象时,会跳过 nullundefined ,不报错 
    Object.assign(1,undefined); // Number {1} Object.assign({a: 1},null); // {a: 1} 
    Object.assign(undefined,{a: 1}); // TypeError: Cannot convert undefined or null to object
    
    **注意点** 
    assign 的属性拷贝是浅拷贝:
    let sourceObj = { a: { b: 1}};
    let targetObj = {c: 3};
    Object.assign(targetObj, sourceObj);
    targetObj.a.b = 2;
    sourceObj.a.b;  // 2
    
    同名属性替换
    targetObj = { a: { b: 1, c:2}};
    sourceObj = { a: { b: "hh"}};
    Object.assign(targetObj, sourceObj);
    targetObj;  // {a: {b: "hh"}}
    
    数组的处理
    Object.assign([2,3], [5]); // [5,3]
    会将数组处理成对象,所以先将 [2,3] 转为 {0:2,1:3} ,然后再进行属性复制,
    所以源对象的 0 号属性覆盖了目标对象的 0

四:数组的方法有哪些?哪些改变原数组?

splice sort push pop unshift shift slice concat filter map some every  join,forEach,

变的有:splice sort push pop unshift shift
    splice(index,n,item..) 会改变原始数组, 从第几个开始删除,删除多少个,第三个是可选参数,向数组添加元素

    sort(fn(a,b)=>{return a-b}) 数组排序 这种方法会改变原始数组!  

    push,pop ,unshift,shift改变数组的长度

不变有:6 slice concat filter map some every

    slice(start,end) 不会修改数组,从第几个开始删,删多少个,返回一个新数组

    concat() 不会改变现有的数组,返回一个新组数。展开运算符...也是这样

    filter() 不会改变原始数组,方法创建一个新的数组,新数组中的元素是通过检查指定数组中符合条件的所有元素。

    map() 不会改变原始数组,数组中的元素为原始数组元素调用函数处理后的值。

    some() 不会改变原始数组,如果有一个元素满足条件,则表达式返回true, 没有满足条件的元素,则返回false。

    every() 不会改变原始数组,组中检测到有一个元素不满足,则整个表达式返回 false ,且剩余的元素不会再进行检测

五 for 和 foreach 和 map 有什么区别

    1、中止循环:
    for 通过 break 关键字来中止循环,
    forEach 和 map 不可以,用 try catch/every/some 代替。
    
    2、跳过此次循环:
    for 通过 continue 来跳过,
    forEach 通过 return 跳过,
    map 不能跳过
    
    3、返回值
    map 返回一个数组,在 map 的回调函数中,不使用 return 返回值的话,会返回 undeifned。
    for 和 forEach 没有返回值。
    
    4、改变原数组。
    map 不改变原数组,for 和 forEach 可以改变原数组。
    
  

六 for in 和 for of 区别 他们都可以使用 它可以正确响应break、continue和return语句

    for in 通常用来遍历对象
    for in 遍历数组的毛病
     index索引为字符串型数字,不能直接进行几何运算
     会遍历数组所有的可枚举属性,包括原型
    
    for of遍历的只是数组内的元素,而不包括数组的原型属性和索引,

七 说一下call,apply,bind区别 可以改变函数中的this指向:call apply bind

新答案:
   共同点:功能一致,可以改变this指向
   区别:
   1. call、apply可以立即执行。bind不会立即执行,因为bind返回的是一个函数需要加入()执行。
   2. 参数不同:apply第二个参数是数组。call和bind有多个参数需要挨个写,逗号隔开。
    

函数.call(调用函数时内部this具有值-也就是调用的函数, 参数1,参数2)

函数.apply(调用函数时内部this具有值-也就是调用的函数, [参数1,参数2])
var arr1 = [1,1112,4,1112,5,7,3,321];
console.log( Math.max.apply(null,arr1) )

函数.bind(调用函数时内部this具有值-也就是调用的函数, 参数1,参数2)()

八 说一下作用域和作用域链的理解?

答:JS作用域也就是JS识别变量的范围,作用域链也就是JS查找变量的顺序

先说作用域,JS作用域主要包括全局作用域、局部作用域和ES6的块级作用域
全局作用域:也就是定义在window下的变量范围,在任何地方都可以访问,

局部作用域:是只在函数内部定义的变量范围

块级作用域:简单来说用letconst在任意的代码块中定义的变量都认为是块级作用域中的变量,例如在for循环中用let定义的变量,在if语句中用let定义的变量等等

注:尽量不要使用全局变量,因为容易导致全局的污染,命名冲突,对bug查找不利。

2️. 而所谓的作用域链就是由最内部的作用域往最外部,查找变量的过程.形成的链条就是作用域链

九 深拷贝 和 浅拷贝 juejin.cn/editor/draf…

新答案:

    共同点都是:复制
    浅拷贝:只复制引用,而未复制真正的值。
    比如声明一个变量 
                var a = {
                        a: 1
                }
                var b = a
                console.log(a, b); 
                a.a = 2
                console.log(a, b);
    变量声明的变量a的内存块,指向一个内存空间,
    变量b是的值是a,所以也跟a一样指向同一个内存空间
    所以浅拷贝就是 这种直接赋值的.
    
    可以用以下方式来实现:
    用es6的Object.assign({},{})进行对象合并,一般用于数据类型比较简单,层数不大于1的数据
    如果是数组可以用es6的Array.from,
    或是es6的扩展运算符...arr, 如果使用es5需要用循环来做浅拷贝,
    还有一种就是:Object.assign()
                var a = {
                        a: 1
                }
                let b = Object.assign(a);
                a.a = 2
                console.log(a, b);
                
深拷贝:是复制真正的值 ,但使用的是不同引用
    深拷贝的方法有:
    1. JSON.parse(JSON.stringify(对象))的方式实现深拷贝,有些类型的值是不支持的(undefined , function, RegExp)
        var a = {
                  a: 1
                }
        var b = JSON.parse(JSON.stringify(a))
        console.log(a, b);
        a.a = 2
        console.log(a, b);
        
    2. 使用递归的形式来封装函数
    

旧答案:
浅拷贝:
    原对象
    const oldObj = {
        name: "张三",
        age:20,
        colors:['orange','green','blue'],
        friend:{
        
        }
    }
    const newObj = oldObj
    newObj.name = "lisi"
    console.log(newObj); // {name: "lisi"}
    console.log(oldObj); // {name: "lisi"}
    
    用es6的Object.assign({},{})进行对象合并,如果是数组可以用es6的Array.from,
    或是es6的扩展运算符...arr, 如果使用es5需要用循环来做浅拷贝,
   

深拷贝: 可以用递归的形式来实现.
  // 原对象
    const oldObj = {
        name: "张三",
        age: 20,
        colors: ['orange', 'green', 'blue'],
        friend: {
            name: '小明'
        }
    }
    
   // 定义一个深拷贝函数deepClone
    function deepClone(obj = {}) {
        // 判断要拷贝的数据类型 是不是对象 ,如果是对象 直接返回
        if (typeof obj !== "object" || obj == null) {
            return obj
        }

        let result;
        // 判断 要拷贝的数据是否为数组 如果是数组 默认等于一个空数组
        if (obj instanceof Array) {
            result = [];
        } else {
            result = {}
        }

        // for in 循环拷贝的对象  并再次调用自己deepClone的函数
        for (let key in obj) {
            result[key] = deepClone(obj[key] )  // result[name] = obj[name]  ==> result = {name: '张三'} 
             // 这里在调用一次这个方法就是递归
            
        }
        return result;
    }

    const newObj = deepClone(oldObj)
    newObj.name = "lisi"
    newObj.friend.name = 80
    newObj.colors[0] = 80

    console.log(oldObj,"oldObj 原始值"); 
    console.log(newObj,"newObj 新的值"); 
    

image.png

当然也可以使用JSON.parse(JSON.stringify(对象))的方式实现深拷贝,有些类型的值是不支持的(有缺陷)

image.png

十 js变量类型:

原始数据类型(栈):string number boolean undefiend null symbolES6新增)
引用类型(栈 和 堆):object(包括function、array、Date...)
typeof 检查 对象 数组 null 都是 object
[] instanceof Array // true
[] instanceof Object // true
 ##### 一、数据类型:

1、基本数据类型:StringNumberBooleanNullUndefinedSymbolBigInt  
2、引用数据类型:ObjectArrayFunctionDateRegExp
二、检测数据类型的四种方法
1、typeof:

(1)检测方法:变量 typeof 数据类型(a typeof string)
(2)总结:只能准确检测:string、number、boolean、undefined、symbol、function,弊端是其他(object、array、date、regExp、null)都会返回object;

2、instanceof:

(1)检测方法:对象 instanceof 引用数据类型(obj instanceof Object)
(2)总结:instanceof的本质作用是判断某个对象是由哪个类(构造函数)产生的,所以只能用在引用数据类型上,如果在原型上找到返回true,所以用它可以区分是引用类型还是基本类型数据;不能判断null,undefined

3、constructor:

(1)检测方法:数据类型.constructor 数据类型([].constructor == Array)
(2)总结:不能判断null,undefined,其它的都可以,由于类的constructor可以随意更改,此时会存在判断不准确的问题

4、Object.prototype.toString.call():

(1)检测方法:Object.prototype.toString.call(数据类型)
(2)总结:该方法是最准备的检测数据类型的方法。由于Object.prototype.toString()本身允许被修改,所以需要调用Object.prototype.toString.call(arg)来判断arg的类型,call将arg的上下文指向Object,所以arg执行了Object的toString方法。

十 js事件循环

事件执行顺序:

  1. 同步代码for循环,主线程的运行战中先执行
  2. nextTick --> 
  3. 异步任务,在任务队列中执行 
          微任务:promise.then
          宏任务:settimeOut,ajax,读取文件
  4. setImmediate(当前事件循环结束,然后执行)

image.png image.png

十一 setTimeout/setInterval 区别

setInterval()会不停的调用函数
setTimeout()只执行函数一次

setTimeout()方法只运行一次,也就是说当达到设定的时间后就出发运行指定的代码,运行完后就结束了,
            如果还想再次执行同样的函数,可以在函数体内再次调用setTimeout(),可以达到循环调用的效果。
setInterval()是循环执行的,即每达到指定的时间间隔就执行相应的函数或者表达式,是真正的定时器。

十二 说一下从输入URL到页面加载完中间发生了什么?

答:大致过程是这样的:
1. DNS解析
2. TCP连接
3. 发送HTTP请求
4. 服务器处理请求并返回需要的数据
5. 浏览器解析渲染页面
6. 连接结束

输入了一个域名,域名要通过DNS解析找到这个域名对应的服务器地址(ip),通过TCP请求链接服务,
通过WEB服务器(apache)返回数据,浏览器根据返回数据构建DOM树,通过css渲染引擎及js解析引擎将页面渲染出来,
关闭tcp连接

十三 JS继承:待补充

    1.class继承
    优点:语法简单易懂,操作更方便。
    缺点:并不是所有的浏览器都支持class关键字
    2.原型链继承
        让子类构造函数的prototype属性指向其父类构造函数的实例;
        优点:简单易实现
        缺点:不能传递参数,因为对象是一次性创建的。
    3.借用构造函数继承
            在子类构造函数中使用call,apply调用父类构造函数并改变this指向子类;
            优点:子类构建时可以向父类传参  
            缺点:无法复用父类原型属性和方法
            由于构造函数每次执行都重新运行私有方法,所以各个实例私有属性相互独立。

    4.组合继承
        优点:既可以向父类传参,又可以共享父类原型方法与属性  
        缺点:调用两次构造函数

十四 JS设计模式有哪些?

答:JS设计模式有很多,但我知道的有单例模式,观察者模式
 单例模式:就是保证一个类只有一个实例,实现的方法一般是先判断实例存在与否,如果存在直接返回,
 如果不存在就创建了再返回,这就确保了一个类只有一个实例对象。在JavaScript里,单例作为一个命名空间提供者,
 从全局命名空间里提供一个唯一的访问点来访问该对象。

观察者模式: 观察者的使用场合就是:当一个对象的改变需要同时改变其它对象,
并且它不知道具体有多少对象需要改变的时候,就应该考虑使用观察者模式。

总的来说,观察者模式所做的工作就是在解耦,让耦合的双方都依赖于抽象,而不是依赖于具体。
从而使得各自的变化都不会影响到另一边的变化

十五 说一下JS事件代理(也称事件委托)是什么,及实现原理?

答:JS事件代理就是通过给父级元素(例如:ul)绑定事件,不给子级元素(例如:li)绑定事件,
然后当点击子级元素时,通过事件冒泡机制在其绑定的父元素上触发事件处理函数,
主要目的是为了提升性能,因为我不用给每个子级元素绑定事件,只给父级元素绑定一次就好了,
在原生js里面是通过event对象的targe属性实现

十六 事件冒泡 和 事件默认行为

阻止冒泡事件:
e.stopPropagation(); 
cancelBubble = ture
阻止默认事件: e.preventDefault();

十七 cookie,localStorage和sessionStorage的区别

新答案:
公共点:在客户端存放数据
区别:
1. 数据存放有效期
    sessionStorage : 仅在当前浏览器窗口关闭之前有效。【关闭浏览器就没了】
    localStorage   : 始终有效,窗口或者浏览器关闭也一直保存,所以叫持久化存储。
     cookie	: 只在设置的cookie过期时间之前有效,即使窗口或者浏览器关闭也有效。
2. localStorage、sessionStorage不可以设置过期时间
   cookie 有过期时间,可以设置过期(把时间调整到之前的时间,就过期了)
3. cookie需要环境支持才可以运行 
   localStorage、sessionStorage 不需要环境
3. 存储大小的限制
        cookie存储量不能超过4k
        localStorage、sessionStorage不能超过5M
        根据不同的浏览器存储的大小是不同的。
        
使用场景:
    在项目中token存储是比较多的,token都在本地存储中,
    token过期时间2种使用方式:
        localStorage + token ==> 后端判断 给前端返回cood码
        cookie + token ==》后端判断是否过期,修改过期时间(前后端不分离可以采用这个,前端直接处理,不需要请求,直接判断)
        
旧答案:

image.png

补充 浏览器缓存

### 强制缓存
不会向服务器发起请求,会看缓存中是否有未过期的缓存,当浏览器去请求某个资源时,
通过服务器设置响应头字段[cache-control]或expires来设置缓存的时间、缓存类型。
当缓存中有未过期的缓存,读取缓存并返回statecode200状态。

cache-control(HTTP1.1)
常用值设置:
max-age(缓存时间)、
public(客户端和代理服务器都可以缓存该资源)/private(仅客户端可以缓存该资源)、
immutable(在有效期内直接读取缓存)、
no-cache(跳过强缓存,直接进入协商缓存)/no-store(跳过强缓存和协商缓存)

expires(HTTP1.0)
可以通过expires指定资源的过期时间。是一个绝对时间,由服务器决定。

1.3 cache-control和expires优先级
当cache-control和expires发生冲突时,cache-control的优先级更高。

原因有两点,如下:
1.expires通过具体的时间戳定义缓存的过期时间,由服务器决定。
但是客户端时间可以修改,当客户端和服务端时间不一致的时候会出现问题。

2.当cache-control中设置了“max-age”这类时,expires头会被忽略。

### 协商缓存
当强缓存失效或者被跳过时,进入协商缓存,协商缓存需要和服务器进行交互。
在客户端第一次向服务器发起请求时,通过服务器设置响应头etag(HTTP1.1)和
last-modified(HTTP1.0)来指明资源标识和文件修改的时间。
当浏览器再向服务器发起请求时,请求头会携带If-None-Match(对应etag)和
If-Modified-Since(对应last-modified)来匹配资源和记录上次资源修改的时间
(即上个响应头中的last-modified)。
当匹配成功时,statecode为304,不会返回资源,客户端读取缓存。
当匹配失败时,statecode为200,返回资源。

十九 new操作符具体做了什么?

image.png image.png image.png

研究一下 函数

image.png

研究一下对象:

image.png image.png