JS总结

184 阅读17分钟

1.简单数据类型和复杂数据类型

  • 简单数据类型:Number、String、Boolean、Null、Undefind和Symbol、BigInt。
    • 在栈中存储,会被自动清理,可以改变原始值
  • 复杂(引用)数据类型:Object(Array、Function、Date)
    • 在栈中储存了指针,指针指向堆中的实体,不会被清理,不能改变原始值

1.1数据类型的判断

  • typeof:能判断所有值类型,函数。不可对 null、对象、数组进行精确判断,因为都返回 object 。
  • instanceof:能判断对象类型,不能判断基本数据类型,其内部运行机制是判断在其原型链中能否找到该类型的原型
  • Object.prototype.toString.call() :所有原始数据类型都是能判断的,还有 Error 对象,Date 对象等。 判断arr是否为数组
Array.isArray(arr); // true 
arr.__proto__ === Array.prototype; // true 
arr instanceof Array; // true 
Object.prototype.toString.call(arr); // "[object Array]"

1.2数组API

Array.from(浅复制,第二个参数可以写映射参数),Array.of(参数->数组),length(可增删元素),fill(第2参从哪开始),push,pop,shift,unshift,reverse,sort,concat,slice,splice(删除插入替换),

  • 三种严格相等的方法:indexOf,lastIndexOf,includes
  • 数组原型上暴露了3个检索数组的方法:keys返回索引,values返回数组元素,entries返回索引值对
  • 断言函数:find(element,index,array)返回第一个匹配元素,findIndex返回第一个匹配元素索引
  • 迭代(element,index,array):every(&&),filter(返回组成的新数组),foreach(遍历,没有返回值),map,some(||).这些方法都不返回调用他们的数组
  • 归并方法(pre,cur,index,array):reduce,reduceRight(方向取反)

1.3字符串API

length,charAt(index)【返回指定索引】=str[index],concat=+, slice,substring,substr,indexOf,lastIndexOf,trim,repeat,toLocaleLowerCase,toLocaleUpperCase,replace

  • 关于slice substring substr区别
    • 只有一个index时,都表示从index开始取
    • 2个index,slice&substring表从a->b,substr表示从a开始取b个字符
    • 有负参数时,slice表示倒数a->b,substing把负值转成0,substr把第二个参数的负值转成0
  • 数组->字符串:1.toString2.join
  • 字符串->数组:split

1.4for-in和for-of的区别

for-in遍历的是索引值,for-of遍历的是元素。for-in适合遍历对象,for of适用遍历数/数组对象/字符串/map/set等拥有迭代器对象(iterator)的集合,但是不能遍历对象.

1.5 break可以跳出哪些API,哪些不能跳出

for、for-in、for-of中,break和continue适用,但是return 不能正常执行。

forEach、map、filter、every、some中break和continue会出现异常,return会跳出当前循环,但是会继续接下来的循环,可以利用try&catch来结束循环。对于some:return true/false结果会不一样,涉及短路运算。

1.6 '=='和'==='

juejin.cn/post/704619… == 如果双方类型不一样,就进行强制类型转换,而 === 不允许; 做题注意点:

  • null、undefined 是相等的,且等于自身
  • false 、 0、 '' 、 [] 是相等的(但是Boolean([])=>true)
  • NaN、{} 和什么都不相等,自己跟自己都不相等
  • 两个都是字符串的话,则比较字符串对应的字符编码值
  • 复杂类型转string:先调用valueOf()获取原始值,如果原始值不是string类型,则调用toString()转成string

1.6.1 Object.is()和‘==’,‘===’

Object.is()===之间的主要区别在于它们如何处理NaN-0

  • 对于NaNObject.is(NaN, NaN)返回true,而NaN === NaN返回false
  • 对于-0+0Object.is(-0, 0)返回false,而-0 === 0返回true

Object.is()==之间的主要区别在于:

  • == 运算符在判断相等前对两边的变量(如果它们不是同一类型)进行强制转换(这种行为将 "" == false 判断为 true
  • Object.is 不会强制转换两边的值。

为什么0.1+0.2 !== 0.3

这是由于数字的存储是二进制存储的,而0.1和0.2转换成二进制之后,都是循环无限的二进制树,所以在相加的时候他们不会等于0.3,还要说的一点就是在js中存储number的话是采用IEEE 754标准的,是64位存储,其中1位用来存储符号,12位用了存储指数,剩下的52位用了存储小数,解决上述问题可以通过toFixed()来解决,也可以通过Number.EPSILON来判断机器精度

2.ES6新特性

const和let,块级作用域,箭头函数,class,模板字符串,展开运算符,Set和Map

2.1const&let

  • var的区别:var 是es5的特性,变量提升至全局,会挂载到window上,造成全局污染,它是可以被重复声明的。const&let变量不提升,暂时性死区,不可重复声明,块级作用域,效率更好。
  • const必须在声明时幅值,简单数据类型常量不可更改,而引用类型的数据不可以修改指针的地址值,只能修改内部的属性。 www.bookstack.cn/read/es6-3r…

2.2箭头函数

  • 箭头函数的this永远不会变,call、apply、bind也无法改变。
  • 箭头函数没有原型prototype。
  • 箭头函数不能当成一个构造函数,因为没有prototype。
  • 没有自己的this,内部的this就是定义时上层作用域中的this

2.2.1new内部实现

创建一个空对象->将函数的prototype设置为对象的原型->绑定this->返回新对象

function mynew(fn,...args) {
    //创建一个空对象,并且把该对象的_proto__指向fn.prototype
    let obj = Object.create(fn.prototype);
    //let obj = {};
    //obj.__proto__=fn.prototype
    let res = fn.apply(obj,args);
    // 如果返回一个非对象值,则将obj返回作为新对象
    return res instanceof Object?res:obj
}

2.3Set、Map

2.3.1Set:

(1)Set成员的值是唯⼀的,可以做数组去重 (2)只有键值没有键名

2.3.2Map:

(1)本质上是健值对的集合(2)可以遍历,可以跟各种数据格式转换

Map和Object区别:

  1. map可以获取到长度,object没法获取长度
  2. map上有iterable接口,object没有
  3. map的键可以是任意的值而object的不行(string,symbol)
  4. 在频繁删减的情况下map的性能更好一些

2.3.3weakSet&weakMap

  • 成员只能是对象
  • 成员都是弱引用,即垃圾回收机制不考虑 weakSet 对该对象的引用,也就是说,如果其他对象都不再引用该对象,那么垃圾回收机制会自动回收该对象所占用的内存,不考虑该对象还存在于 WeakSet 之中。
  • 对象不能遍历

3.this指向

3.1判断输出

箭头函数、new、bind、apply 和 call、欧比届点(obj.)、直接调用、不在函数里。juejin.cn/post/694602…

3.2call/apply/bind区别

call和apply都是调用后立即执行,bind返回一个改变this后的函数。call接收的参数是以逗号分隔,apply第二个参数接收数组,bindcall一样。

4.原型对象和原型链

在js中,每个构造函数身上都有一个prototype属性,这个属性是一个对象,这个对象包含着这个构造函数创造出来的所有实例共享的属性和方法,当用实例创建对象的时候,这个对象会有一个指针__proto__指向构造函数的prototype属性。在访问一个对象的时候,如果该对象上没有想要的方法或属性,就可以通过__proto__进行原型查找,一直查找下去,直到找到原型链终点null。 image.png

5.闭包

在 JavaScript 中,每当创建一个函数,闭包就会在函数创建的同时被创建出来。可以在一个内层函数中访问到其外层函数的作用域。就是因为作用域链的存在,所以内部函数才可以访问外部函数中定义的变量。

5.1 使用场景

return返回一个函数,函数柯里化,防抖和节流,函数作为一个参数

6.异步

6.1浏览器/JS事件循环机制(Event Loop)

一个进程由一个或多个线程组成,线程是一个进程中代码的不同执行路线; JS是单线程,但是⼀些⾼耗时操作就带来了进程阻塞问题。为了解决这一问题,把同步和异步任务分开处理

  • 首先函数入栈,执行同步任务,当执行到异步任务时,就将它丢给WebAPIs,接着执行同步任务,直到栈被清空
  • 此期间WebAPIs把回调函数放入对应队列中等待执行,微任务方法放入微任务队列,宏任务放到宏任务队列。
  • 执行栈为空时,会把微任务队列也执行清空。微任务队列清空后,进入宏任务队列,取出第一项放入执行栈中执行,如果中间有微任务,就把它添加到微任务队列中,宏任务执行完成后立即清空微任务队列。然后继续从宏任务中取出任务进行执行。
  • 上述过程会不断重复,也就是常说的Event Loop(事件循环)。

6.1.1宏任务与微任务

  • 宏任务:script(整体代码)、setTimeout、setInterval、DOM事件、setImmediate(Node.js 环境)
  • 微任务:可以理解是在当前 宏任务 执行结束后立即执行的任务。Promis.then或catch、process.nextTick(),fetch API,V8的垃圾回收过程。
  • 执行顺序(执行script结束后,在异步任务中会先执行微任务队列,执行完在执行宏任务)
    • 执行一个宏任务
    • 执行过程中如果遇到微任务,就将它添加到微任务的任务队列中
    • 宏任务执行完毕后,立即执行当前微任务队列中的所有微任务(依次执行)
    • 浏览器渲染 image.png

6.2 Node.js的Event Loop

Node的Event Loop还要处理一些I/O,比如新的网络连接等,所以Node的Event Loop(事件环机制)与浏览器的是不太一样。

  • timers:执行setTimeout/setInterval的回调
  • I/O callbacks:执行上一轮中少数未执行的I/O回调
  • poll轮询:获取的I/O事件,执行与I/O相关的回调
  • check:执行setImmediate的回调
  • close callbacks:执行一些关闭的回调函数,如socket的close事件回调

6.2Promise

6.2.1 理解

  • 阮一峰es6.ruanyifeng.com/#docs/promi…
  • promise有三种状态--pending、fulfilled、rejected,只能从pending到另外两种状态,一旦发生状态改变不可二次修改。promise中使用resolve和reject两个函数来修改状态。

6.2.2 题目

  • 题目juejin.cn/post/684490…
  • .then是状态改变才执行的微任务(先不执行,触发后执行),.then和catch都返回一个新的promise,直接传到最后。new Promise是同步任务,整体这个宏任务执行完,在执行微任务,在执行宏任务队列。
  • Promise中,返回任意一个非 promise 的值都会被包裹成 promise 对象,例如return 2会被包装为return Promise.resolve(2),.then和catch参数期望是函数,传入非函数则会发生值透传,后面有.then的话直接传到最后。
  • 如果在async函数中抛出了错误,则终止错误结果,不会继续向下执行。使用try-catch不会影响后续代码执行。
  • .finally的返回值如果在没有抛出错误的情况下默认会是上一个Promise的返回值。且它收不到参数。
  • 链式调用,可以使用链式的then,也可以使用await和async来实现链式调用。

6.2.3 async/await和promise关系

  • async/await 是消灭异步回调的终极武器。
  • 但和 Promise 并不互斥,反而,两者相辅相成。
  • 执行 async 函数,返回的一定是 Promise 对象。
  • await 相当于 Promise 的 then。
  • try...catch 可捕获异常,代替了 Promise 的 catch。 Async 是 Generator 的一个语法糖。async 对应的是 * 。await 对应的是 yield。async/await 自动进行了 Generator 的流程控制

6.2.4 Ajax、axios和promise关系

  • Ajax 可以实现网页的异步更新,意味着可以不用重新加载整个页面的情况下,对页面实现局部刷新xmlHttpRequest是ajax的一种实现方式。存在回调地狱问题
  • Fetch 是es6提出的,它是基于 promise 的,采用.then的链式调用方式处理结果,解决了回调地狱问题。
  • Axios 是一个基于 promise 封装的网络请求库,它是基于 XHR 进行二次封装。是xhr的一个子集。

6.2.5 手写ajax

function myAjax (options) { 
    // 1. 创建异步对象 
    const xhr = new XMLHttpRequest(); 
    const url = options.url; 
    const type = (options.type || 'GET').toUpperCase(); 
    const sendData = options.data; 
    // 2. 设置 请求行 open(请求方式,请求url): 
    xhr.open(type,url,true); 
    // 3. 设置请求体 
    send() if(type === 'GET') { 
        xhr.send(); 
        } else { 
        xhr.send(sendData); } 
    // 4. 让异步对象接收服务器的响应数据 
    xhr.onreadystatechange = function() { 
        if(xhr.readyState === 4) { 
            if(xhr.status>=200 && xhr.status<300) { 
                options.success && options.success(xhr.responseText,xhr.responseXML); 
                } else { 
                        options.error && options.error(xml.status) } 
         } 
    } 
}

7.垃圾回收机制

7.1两种垃圾回收策略

  • 标记清除:分为两个阶段--标记和清除阶段。标记阶段为所有活动对象做上标记,清除阶段则把没有标记(也就是⾮活动对象)销毁。
  • 引⽤计数:它把对象是否不再需要-->对象有没有其他对象引⽤到它。如果没有引⽤指向该对象(引⽤计数为0),对象将被垃圾回收机制回收。

7.1.1标记清除的缺点

  • 内存碎⽚化,空闲内存块是不连续的,容易出现很多空闲内存块,还可能会出现分配所需内存过⼤的对象时找不到合适的块。
  • 分配速度慢,因为即便是使⽤ First-fit 策略,其操作仍是⼀个 O(n)  的操作,最坏情况是每次都要遍历到  最后,同时因为碎⽚化,⼤对象的分配效率会更慢。

解决以上的缺点可以使⽤ 标记整理(Mark-Compact)算法 ,标记结束后,标记整理算法会将活着的对  象(即不需要清理的对象)向内存的⼀端移动,最后清理掉边界的内存。

7.1.2引用计数的缺点

  • 需要一个计数器,所占内存空间大,因为我们也不知道被引用数量的上限。
  • 解决不了循环引用导致的无法回收问题。

解决方法,v8回收机制。

  • 针对新生区采用并行回收。
  • 针对老生区采用增量标记与惰性回收。

8.深拷贝与浅拷贝

浅拷贝:创建一个新对象,这个对象有着原始对象属性值的一份精确拷贝,只能拷贝一层。如果属性是基本类型,拷贝的就是基本类型的值,如果属性是引用类型,拷贝的就是内存地址 ,所以如果其中一个对象改变了这个地址,就会影响到另一个对象。例如:slice(不改变原始数组,浅拷贝一份新的数组),Object.assign,展开运算符

深拷贝:将一个对象从内存中完整的拷贝一份出来(无限层次的拷贝),从堆内存中开辟一个新的区域存放新对象,且修改新对象不会影响原对象

9.JS继承

原型链继承

原型链方案存在的缺点:子类实例上共享父类所有的引用类型的实例属性,容易造成修改的混乱。不能给父类构造函数传参

SubType.prototype = new SuperType();

借用构造函数继承

只能继承父类的实例属性和方法,不能继承原型属性/方法。无法实现复用,每个子类都有父类实例函数的副本,影响性能

function SubType(){ 
//继承自SuperType,`SubType`的每个实例都会将SuperType中的属性复制一份。
    SuperType.call(this); }

组合继承

组合上述两种方法就是组合继承。用原型链实现对原型属性和方法的继承,用借用构造函数技术来实现实例属性的继承。组合模式的缺点:重复调用两次父类构造函数。其原型中会存在两份相同的属性/方法。

原型式继承

利用一个空对象作为中介,将某个对象直接赋值给空对象构造函数的原型。ES5 中定义的Object.create() 方法就是原型式继承的实现。缺点与原型链方式相同(原型链继承多个实例的引用类型属性指向相同,存在篡改的可能。无法传参)。

function object(obj){ 
    function F(){} 
    F.prototype = obj; 
    return new F(); }

寄生式继承

在原型式继承的基础上,增强对象,返回构造函数。缺点同原型式继承(原型链继承多个实例的引用类型属性指向相同,存在篡改的可能。无法传参)

function createAnother(original){
  let clone = Object.create(obj); 
  clone.say = () =>{ 
  console.log('新增添的可调用函数')
}

寄生式组合继承

结合借用构造函数传递参数和寄生模式实现继承。只调用了一次SuperType 构造函数,并且因此避免了在SubType.prototype 上创建不必要的、多余的属性。于此同时,原型链还能保持不变;因此,还能够正常使用instanceof 和isPrototypeOf()

function inheritPrototype(subType, superType){ 
    var prototype = Object.create(superType.prototype); // 创建对象,创建父类原型的一个副本 
    prototype.constructor = subType; // 增强对象,弥补因重写原型而失去的默认的constructor 属性 
    subType.prototype = prototype; // 指定对象,将新创建的对象赋值给子类的原型 
}

类继承extends

extends关键字主要用于类声明或者类表达式中,以创建一个类,该类是另一个类的子类。其中constructor表示构造函数,一个类中只能有一个构造函数,如果没有显式指定构造方法,则会添加默认的 constructor方法。

function _inherits(subType, superType) {
  
    // 创建对象,创建父类原型的一个副本
    // 增强对象,弥补因重写原型而失去的默认的constructor 属性
    // 指定对象,将新创建的对象赋值给子类的原型
    subType.prototype = Object.create(superType && superType.prototype, {
        constructor: {
            value: subType,
            enumerable: false,
            writable: true,
            configurable: true
        }
    });
    
    if (superType) {
        Object.setPrototypeOf 
            ? Object.setPrototypeOf(subType, superType) 
            : subType.__proto__ = superType;
    }
}

juejin.cn/post/717208… juejin.cn/post/684490…

ES5继承和ES6继承的区别

  • ES5的继承实质上是先创建子类的实例对象,然后再将父类的方法添加到this上(Parent.call(this)).
  • ES6的继承有所不同,实质上是先创建父类的实例对象this,然后再用子类的构造函数修改this。因为子类没有自己的this对象,所以必须先调用父类的super()方法,否则新建实例报错。

Event.target和Event.currentTarget

Event.currentTarget总是指向事件绑定的元素,Event.target则是事件触发的元素,可以用来做事件委派

setTimeout和setInterval区别

setTimeout加入事件队列的时间是执行耗时 + 间隔耗时setInterval任务间的间隔是 Math.max(执行耗时, 间隔耗时),加入事件队列的时间点是固定的,当队列中存在重复的定时任务会进行丢弃。二者都返回函数的专属id,可以通过clearInterval(id)或者clearTimeout(id)来清除定时器。

前端请求数据量大的长列表,如何解决性能问题?

  1. 仅仅渲染视口内的数据:将无需展示的数据不渲染到页面上,只有当滚动到数据可见区域时才进行渲染和显示。
  2. 固定列表项高度:通过设置列表项高度,可以避免在滚动时浏览器重新计算列表项位置和高度的操作,从而提高滚动性能。
  3. 虚拟滚动容器:在虚拟滚动容器中,只需要在滚动时改变容器内部渲染的数据,并保持容器高度不变即可,这样可以避免不必要的 DOM 操作,提高渲染性能。
  4. 使用 window.requestAnimationFrame() 方法:由于 JavaScript 是单线程语言,可能存在渲染和计算同时进行的情况,导致页面卡顿。使用 window.requestAnimationFrame() 方法可以让浏览器在下一次重绘之前执行指定的函数,从而避免卡顿问题。

window.requestAnimationFrame(callback)  告诉浏览器——你希望执行一个动画,并且要求浏览器在下次重绘之前调用指定的回调函数更新动画。该方法需要传入一个回调函数作为参数,该回调函数会在浏览器下一次重绘之前执行。

常用的鼠标事件

mouseenter 鼠标经过盒子触发。mouseenter只会经过自身盒子触发。而mouseover鼠标经过自身盒子会触发,经过子盒子还会触发。

mouseenter 对应的是mouseleave,与mouseover对应的是mouseout

mousemove鼠标在盒子内移动。

mouseup/down鼠标按下和松开。click鼠标点击。