2022年前端面试必须学会的知识点-JS篇(持续更新中...)


一、js基本数据类型和引用数据类型

    基本数据类型number string boolean undefined null symbol bigint 栈内存:值不变,值比较

    引用数据类型Object对象(普通object function Array Date RegExp Math) 堆内存:引用地址的比较

es6新增的Set,Map的区别

  • Set 和 Map 主要的应用场景在于 数据重组 和 数据储存。
  • Set 是一种叫做集合的数据结构,Map 是一种叫做字典的数据结构。

集合 与 字典 的区别:

  • 共同点:集合、字典 可以储存不重复的值
  • 不同点:集合 是以 [value, value]的形式储存元素,字典 是以 [key, value] 的形式储存

注意: 数据类型和数据结构是不一样的

前端常见的数据结构

一.队列 二.栈 三.链表

四.集合

ES6已经为我们提供了Set的api,但是在某些时候(浏览器不支持的情况下),我们还是需要自己来实现Set的

class Set{
    constructor(){
        this.items = {};
    }
    has(value){ // 判断
        return this.items.hasOwnProperty(value);
    }
    add(value){ // 像集合中添加
        if(!this.has(value)){
            this.items[value] = value;
        }
    }
    remove(value){ // 删除集合中的某一项
        if(this.has(value)){
            delete this.items[value];
        }
    }
}

五.hash表 六.树 七.图

集合(Set)

ES6 新增的一种新的数据结构,类似于数组,成员唯一(内部元素没有重复的值)。且使用键对数据排序即顺序存储。

Set 本身是一种构造函数,用来生成 Set 数据结构。

Set 对象允许你储存任何类型的唯一值,无论是原始值或者是对象引用。

// 去重数组的重复对象
let arr = [1, 2, 3, 2, 1, 1]
[... new Set(arr)]	// [1, 2, 3]
注意:向 Set 加入值的时候,不会发生类型转换,所以5和"5"是两个不同的值。Set 内部判断两个值是否不同,使用的算法叫做“Same-value-zero equality”,它类似于精确相等运算符(===),主要的区别是NaN等于自身,而精确相等运算符认为NaN不等于自身。
操作方法:

add(value):新增,相当于 array里的push。
delete(value):存在即删除集合中value。
has(value):判断集合中是否存在 value。
clear():清空集合。
便利方法:遍历方法(遍历顺序为插入顺序)

keys():返回一个包含集合中所有键的迭代器。
values():返回一个包含集合中所有值得迭代器。
entries():返回一个包含Set对象中所有元素得键值对迭代器。
forEach(callbackFn, thisArg):用于对集合成员执行callbackFn操作,如果提供了 thisArg 参数,回调中的this会是这个参数,没有返回值。
set放NaN会有几个?NaN全等于NaN吗?
let set = new Set();
let a = NaN;
let b = NaN;
set.add(a);
set.add(b);
set // Set {NaN}

let set1 = new Set()
set1.add(5)
set1.add('5')
console.log([...set1])	// [5, "5"]

console.log(NaN == NaN)//false

字典(Map):

是一组键值对的结构,具有极快的查找速度。

操作方法:

set(key, value):向字典中添加新元素。
get(key):通过键查找特定的数值并返回。
has(key):判断字典中是否存在键key。
delete(key):通过键 key 从字典中移除对应的数据。
clear():将这个字典中的所有元素删除。
遍历方法:

Keys():将字典中包含的所有键名以迭代器形式返回。
values():将字典中包含的所有数值以迭代器形式返回。
entries():返回所有成员的迭代器。
forEach():遍历字典的所有成员。

二、typeof

    返回值number string boolean undefined Object function symbol

    function a(){}

    console.log(typeof a )

    console.log(typeof b )

    console.log(typeof undefined )//undefined

    console.log(typeof typeof xxx )//string

    console.log(typeof NaN )

    缺点?typeof无法区分数组和普通对象

    三、typeof如何区分数组和普通对象?

    1.Array.isArray()

    2.a instanceof b 判断a的原型链上是否有b的原型

    实现{} instanceof Object

    function instance_of(a,b) {

      let atype = Object.getPrototypeOf(a);

      while (true) {

        if(atype == null) return false

        if(atype == b.prototype){

          return true

        }

        atype = Object.getPrototypeOf(atype);

      }

    }

    console.log(instance_of({},Object))

    console.log(instance_of({},Array))

    3.Object.prototype.toString

    console.log(Object.prototype.toString.call({}) == '[object Object]')

    console.log(Object.prototype.toString.call([]) == '[object Array]')

    console.log(Object.prototype.toString.call(new Date()) == '[object Date]')

为什么这样就能区分呢?于是我去看了一下toString方法的用法:toString方法返回反映这个对象的字符串。

那为什么不直接用obj.toString()呢?

console.log("jerry".toString());//jerry
console.log((1).toString());//1
console.log([1,2].toString());//1,2
console.log(new Date().toString());//Wed Dec 21 2016 20:35:48 GMT+0800 (中国标准时间)
console.log(function(){}.toString());//function (){}
console.log(null.toString());//error
console.log(undefined.toString());//error

这是因为toString为Object的原型方法,而Array 、Function等类型作为Object的实例,都重写了toString方法。不同的对象类型调用toString方法时,根据原型链的知识,调用的是对应的重写之后的toString方法(Function类型返回内容为函数体的字符串,Array类型返回元素组成的字符串.....),而不会去调用Object上原型toString方法(返回对象的具体类型),所以采用obj.toString()不能得到其对象类型,只能将obj转换为字符串类型;因此,在想要得到对象的具体类型时,应该调用Object上原型toString方法。

四、原型链

原型链:实例对象和原型对象之间的链接关系

为啥要用原型链?为了实现继承,简化代码,只要是链条上的内容,都可以访问到

原型链特点?就近原则;每个函数都存在prototype,并且默认原型都是Object的实例;每个实例对象都有_proto_,指向父函数的prototype

所以JavaScript 有隐式原型(__proto__)与显式原型(prototype)

JavaScript中任意对象都有一个内置属性[[prototype]],在ES5以前没有标准的方法访问这个内置属性,可是大多数浏览器都支持经过__proto__来访问。ES5中有了对于这个内置属性标准的Get方法Object.getPrototypeOf()

Object.prototype 这个对象是个例外,它的__proto__值为null

隐式原型与显式原型的关系

隐式原型指向建立这个对象的函数(constructor)的prototype浏览器

1.__proto__constructor

每一个对象数据类型(普通的对象、实例、prototype......)也天生自带一个属性__proto__,属性值是当前实例所属类的原型(prototype)。原型对象中有一个属性constructor, 它指向函数对象。 

function Person() {}
    var person = new Person()
    console.log(person.__proto__ === Person.prototype)//true
    console.log(Person.prototype.constructor===Person)//true
    //顺便学习一个ES5的方法,可以获得对象的原型
    console.log(Object.getPrototypeOf(person) === Person.prototype) // true

 2.何为原型链

在JavaScript中万物都是对象,对象和对象之间也有关系,并不是孤立存在的。对象之间的继承关系,在JavaScript中是通过prototype对象指向父类对象,直到指向Object对象为止,这样就形成了一个原型指向的链条,专业术语称之为原型链。

当我们访问对象的一个属性或方法时,它会先在对象自身中寻找,如果有则直接使用,如果没有则会去原型对象中寻找,如果找到则直接使用。如果没有则去原型的原型中寻找,直到找到Object对象的原型,Object对象的原型没有原型,如果在Object原型中依然没有找到,则返回undefined。

Object是JS中所有对象数据类型的基类(最顶层的类)在Object.prototype上没有__proto__这个属性。

 

console.log(Object.prototype.__proto__ === null) // true

3.js的new操作符到底做了什么?

1、创建了一个空的js对象(即{})

2、将空对象的原型prototype指向构造函数的原型

3、将空对象作为构造函数的上下文(改变this指向)

4、对构造函数有返回值的判断

怎么实现?

/*
  create函数要接受不定量的参数,第一个参数是构造函数(也就是new操作符的目标函数),其余参数被构造函数使用。
  new Create() 是一种js语法糖。我们可以用函数调用的方式模拟实现
*/
function create(Con,...args){
    //1、创建一个空的对象
    let obj = {}; // let obj = Object.create({});
    //2、将空对象的原型prototype指向构造函数的原型
    Object.setPrototypeOf(obj,Con.prototype); // obj.__proto__ = Con.prototype
    //3、改变构造函数的上下文(this),并将剩余的参数传入
    let result = Con.apply(obj,args);
    //4、在构造函数有返回值的情况进行判断
    return result instanceof Object?result:obj;
}

4.call() apply() bind()?

作用都是用来改变fn内部的this指向

    fn.call(obj,1,2)立即执行

    fn.bind(obj,1,2)等待执行

    fn.apply(obj,[1,2])立即执行

五、i++与++i

    i++:先输出后计算     ++i:先计算后输出(推荐)

    let a = 1;

    alert(a++);

    let b = 1;

    alert(++b);

六、闭包是什么?

闭包实际上是一种函数,就是有权访问某个函数里私有变量的函数。

闭包产生的时机:内层的作用域访问它外层函数作用域里的参数/变量/函数时,闭包就产生了。

闭包的不足:由于闭包会引用它外层函数作用域里的变量函数,因此,会比其他非闭包形式的函数占用更多内存,当外层函数执行完毕退回函数调用栈(call stack)的时候,外层函数作用域里变量因为被引用着,可能并不会被引擎的垃圾回收器回收,因而会引起内存泄漏。过度使用闭包会导致内存占用过多,甚至内存泄漏。

为什么要用到闭包

外部函数调用之后,其变量对象应该被销毁,但是闭包的存在使我们任然可以访问外部函数的变量对象,让这些变量始终保持在内存之中。

    function our(){

      let a = '1'

      return function(){

        return a

      }

    }

    var b = our()

    console.log(b())

造成啥后果?内存泄漏

怎么解决?b = null,释放对闭包的引用

闭包应用场景?

闭包应用场景1–封闭作用域

JavaScript的GC机制
在JavaScript中,如果一个对象不再被引用,那么这个对象就会被GC回收,否则这个对象一直会保存在内存中。
封闭作用域又称为封闭空间,小闭包,匿名函数自调
技术最大的目的是:全局变量私有化

优点:1、不污染全局空间;2、内部所有的临时变量执行完毕都会释放不占内存;3、可以保存全局数据;4、更新复杂变量

闭包应用场景2–作用域链
嵌套之间的函数会形成作用域链,每次对变量的访问实际上都是对整条作用域链的遍历查找,先查找最近的作用域,最后再查找全局作用域,如果在某个作用域找到了对量就会结束本次查找过程。
思考?对于作用域全局查找快-?还是局部作用域查找快?
局部作用域查找远远大于全局作用域查找的速度,所以高级程序设计一般尽量避免全局查找。
每次访问都是对作用域链的一次遍历查找,其中全局作用域是最耗时的。
解决方案:当前目标使用完以后,在退出作用域之前储存这个目标,就可以在下次取到上一次的目标。

闭包应用场景3–保存作用域

保存作用域是一种更高级的闭包技术,如果函数嵌套函数,那么内部的那个函数将形成作用域闭包。好处就是让指令能够绑定一些全局数据去运行。
<button id="btn">点我</button>
(function(document){
    var btn=document.getElementById("btn");
})(document)//传入了参数,第一次查找时是在全局查找,后面的直接使用,也就是保存了全局的数据,局部直接使用。

优点:1、全局数据隐藏化;2、可以让某个指令运行时绑定一些隐藏的全局数据在身上。
一句话:将数据绑定在指令上运行,让指令不再依赖全局数据。

闭包应用场景4–高级排他

闭包应用场景5–参数传递

闭包应用场景6–函数节流

闭包应用场景7–延长局部变量的生命

所有如以下写法的都是闭包

function a(){
    let b
    return function(){
        bxxx
    }
}

    七、toString和valueOf

    共同点:为了解决js值运算和显示问题

    区别

    toString:返回当前对象的字符串形式

    valueOf:返回该对象的原始值

    console.log(Object.prototype.toString({a:'1'}))

    console.log(Object.prototype.valueOf({a:'1'}))

    调用优先级:隐式转换时会自动调用,强制转换为字符串时用toString,数值时用valueOf,当使用运算符时,valueOf高于toString

    valueOf的应用

    let a = {

      value: 0,

      valueOf: function(){

        this.value ++

        return this.value

      }

    }

    console.log(a==1 && a==2)//true

    八、hasOwnProperty和isPrototypeOf

    obj.hasOwnProperty():判断某个对象是否含有指定属性,无法检测原型链

    Object.prototype.isPrototypeOf({}):判断Object的原型是否在{}的原型链上

    九、setTiemeout与事件队列

for (var i = 0; i <= 100; i++) {
      if (i === 1) {
        setTimeout(function () {
          console.log("timeout");
        }, 0);
      }
      console.log(i);
    }

当真正运行的时候你会发现,这个"timeout"文字会在整个for执行完之后再执行,而不是想当然的在某一步运行完之后执行。

    JS的异步机制由事件循环和任务队列构成.JS本身是单线程语言,所谓异步依赖于浏览器或者操作系统等完成. JavaScript 主线程拥有一个执行栈以及一个任务队列,主线程会依次执行代码,当遇到函数时,会先将函数入栈,函数运行完毕后再将该函数出栈,直到所有代码执行完毕。

    遇到异步操作(例如:setTimeout, AJAX)时,异步操作会由浏览器(OS)执行,浏览器会在这些任务完成后,将事先定义的回调函数推入主线程的任务队列(task queue)中,当主线程的执行栈清空之后会读取task queue中的回调函数,当task queue被读取完毕之后,主线程接着执行,从而进入一个无限的循环,这就是事件循环.

    JavaScript是单线程执行的,无法同时执行多段代码。当某一段代码正在执行的时候,所有后续的任务都必须等待,形成一个队列。一旦当前任务执行完毕,再从队列中取出下一个任务,这也常被称为 “阻塞式执行”。所以一次鼠标点击,或是计时器到达时间点,或是Ajax请求完成触发了回调函数,这些事件处理程序或回调函数都不会立即运行,而是立即排队,一旦线程有空闲就执行。假如当前 JavaScript线程正在执行一段很耗时的代码,此时发生了一次鼠标点击,那么事件处理程序就被阻塞,用户也无法立即看到反馈,事件处理程序会被放入任务队列,直到前面的代码结束以后才会开始执行。如果代码中设定了一个 setTimeout,那么浏览器便会在合适的时间,将代码插入任务队列,如果这个时间设为 0,就代表立即插入队列,但不是立即执行,仍然要等待前面代码执行完毕。所以 setTimeout 并不能保证执行的时间,是否及时执行取决于 JavaScript 线程是拥挤还是空闲。

事件循环

例子如下:

console.log('script start');
setTimeout(function() {
console.log('setTimeout');
}, 0);
Promise.resolve().then(function() {
console.log('promise1');
}).then(function() {
console.log('promise2');
});
console.log('script end');
//执行结果:script start=>script end=>promise1 =>promise2=>setTimeout

在JavaScript中,我们把任务分为同步任务和异步任务 

  • 同步和异步任务分别进入不同的执行”场所”,同步的进入主线程,异步的进入Event Table并注册函数

  • 当指定的事情完成时,Event Table会将这个函数移入Event Queue

  • 主线程内的任务执行完毕为空,会去Event Queue读取对应的函数,进入主线程执行。

  • 上述过程会不断重复,也就是常说的Event Loop(事件循环)

我们发现,同样是异步任务,也分先后,promise要优于setTimeout,这就要引出下个知识点

宏任务和微任务

其实异步任务是包含宏任务和微任务,他们会加在同一个队列里面,只是执行顺序不一样而已。

宏任务:整体代码script、setTimeout、setInterval、setImmediate,postMessage、

微任务:原生Promise中then方法、process.nextTick、MutationObserver

对于宏任务和微任务我们只要记住以上几种就好。

执行顺序:同步任务>微观任务>宏观任务

关于setTimeout要补充的是,即便主线程为空,setTimeout(fn,0),0毫秒实际上也是达不到的。根据HTML的标准,最低是4毫秒。关于setInterval( fn , ms )来说并不是每隔ms就会执行一遍,而是经过ms后回调函数加入事件队列,所以setInterval未必准确。

十,页面回流与重绘

1.什么是回流?

当render tree中的一部分(或全部),因为元素的规模尺寸、布局、隐藏等改变 而需要重新构建,这就是回流(reflow)

每个页面至少回流一次,即页面首次加载

2.什么是重绘?

当render tree中的一些元素需要更新属性,而这些属性只是影响元素的外 观、风格,而不影响布局(例如:background-color),则称为重绘(repaints)

特点:回流必将引起重绘,重绘不一定引起回流 回流比重绘的代价更高

当页面布局和几何属性改变时就需要回流

(1)添加或者删除可见的DOM元素; (2)元素位置改变; (3)元素尺寸改变——边距、填充、边框、宽度和高度 (4)内容改变——比如文本改变或者图片大小改变而引起的计算值宽度和高度改变; (5)页面渲染初始化; (6)浏览器窗口尺寸改变——resize事件发生时;

重绘何时发生?

元素的属性或者样式发生变化。

到此,我们就知道如何减少回流和重绘了。

 十一,ES6 ,ES7新特性?

ES6(2015)

1、Let和Const和var
let 声明的变量只在所在块级作用域中可用
const 如果声明的常量是基本类型的数据,不可重新赋值;如果声明的是引用数据类型,改其中的某个属性确实可以被修改掉,如何理解?

因为对象是引用类型的,P中保存的仅是对象的指针,这就意味着,const仅保证指针不发生改变,修改对象的属性不会改变对象的指针,所以是被允许的。也就是说const定义的引用类型只要指针不发生改变,其他的不论如何改变都是允许的。

我们试着修改一下指针,让P指向一个新对象,即使对象的内容没发生改变,指针改变也是不允许的。

2、Class(类)

//定义类
class Point {
  constructor(x, y) {
    this.x = x;
    this.y = y;
  }
  //一个类必须有constructor方法
  toString() {
    return '(' + this.x + ', ' + this.y + ')';
  }
}

ES6的类,完全可以看作构造函数的另一种写法。

Class之间可以通过extends关键字实现继承,这比ES5的通过修改原型链实现继承,要清晰和方便很多。

class ColorPoint extends Point {}//相当于复制
class ColorPoint extends Point {
  constructor(x, y, color) {
    super(x, y); // 调用父类的constructor(x, y)
    this.color = color;
  }
  toString() {
    return this.color + ' ' + super.toString(); // 调用父类的toString()
  }
}

 3、箭头函数
箭头函数表达式的语法比函数表达式更简洁,并且没有自己的this,arguments,super或 new.target。这些函数表达式更适用于那些本来需要匿名函数的地方,并且它们不能用作构造函数。

4、函数参数默认值

5、模板字符串const info = `My name is ${name}, I am ${age}`

6、解构赋值

7、模块化(Module)
在ES6之前,JS并没有模块化的概念,有的也只是社区定制的类似CommonJS和AMD之类的规则。例如基于CommonJS的NodeJS:

/ circle.js
// 输出
const { PI } = Math
export const area = (r) => PI * r ** 2
export const circumference = (r) => 2 * PI * r
// index.js
// 输入
import {
    area
} = './circle.js'

8.Promise

romise 是ES6提供的一种异步解决方案,比回调函数更加清晰明了。

Promise 有三种状态,分别是:1.等待中(pending)2.完成了 (resolved)3.拒绝了(rejected)

这个承诺一旦从等待状态变成为其他状态就永远不能更改状态了,也就是说一旦状态变为 resolved 后,就不能再次改变

9、for …of
for…of语句在可迭代对象(包括 Array,Map,Set,String,TypedArray,arguments 对象等等)上创建一个迭代循环,调用自定义迭代钩子,并为每个不同属性的值执行语句。

10、Symbol

symbol 是一种基本数据类型,Symbol()函数会返回symbol类型的值,该类型具有静态属性和静态方法。它的静态属性会暴露几个内建的成员对象;它的静态方法会暴露全局的symbol注册,且类似于内建对象类,但作为构造函数来说它并不完整,因为它不支持语法:“new Symbol()”。

11、Proxy/Reflect

Proxy 对象用于定义基本操作的自定义行为(如属性查找,赋值,枚举,函数调用等)。

Reflect 是一个内置的对象,它提供拦截 JavaScript 操作的方法。这些方法与 Proxy 的方法相同。Reflect不是一个函数对象,因此它是不可构造的。

ES7(ES2016)

1、Array.prototype.includes()
includes() 方法用来判断一个数组是否包含一个指定的值,根据情况,如果包含则返回 true,否则返回false。

2、幂运算符**

3、模板字符串(Template string)

ES8(ES2017)

1、async/await
虽然Promise可以解决回调地狱的问题,但是链式调用太多,则会变成另一种形式的回调地狱 —— 面条地狱,所以在ES8里则出现了Promise的语法糖async/await,专门解决这个问题。

2、Object.values()    Object.entries()

3、padStart()

padStart() 方法用另一个字符串填充当前字符串(重复,如果需要的话),以便产生的字符串达到给定的长度。填充从当前字符串的开始(左侧)应用的。

const str1 = '5'
console.log(str1.padStart(2, '0')) // "05"
const fullNumber = '2034399002125581'
const last4Digits = fullNumber.slice(-4)
const maskedNumber = last4Digits.padStart(fullNumber.length, '*') 
console.log(maskedNumber) // "************5581"

4、padEnd()

padEnd() 方法会用一个字符串填充当前字符串(如果需要的话则重复填充),返回填充后达到指定长度的字符串。从当前字符串的末尾(右侧)开始填充。 

ES9(ES2018) 

1、for await…of
for await...of 语句会在异步或者同步可迭代对象上创建一个迭代循环,包括 String,Array,Array-like 对象(比如arguments 或者NodeList),TypedArray,Map, Set和自定义的异步或者同步可迭代对象。其会调用自定义迭代钩子,并为每个不同属性的值执行语句。

配合迭代异步生成器,例子如下: 

async function* asyncGenerator() {
      var i = 0
      while (i < 3) {
            yield i++
      }
}
(async function() {
      for await (num of asyncGenerator()) {
            console.log(num)
      }
})()
// 0
// 1
// 2

2、对象扩展操作符

ES6中添加了数组的扩展操作符,让我们在操作数组时更加简便,美中不足的是并不支持对象扩展操作符,但是在ES9开始,这一功能也得到了支持,例如:

var obj1 = { foo: 'bar', x: 42 };
var obj2 = { foo: 'baz', y: 13 };

var clonedObj = { ...obj1 };
// 克隆后的对象: { foo: "bar", x: 42 }

var mergedObj = { ...obj1, ...obj2 };
// 合并后的对象: { foo: "baz", x: 42, y: 13 }

上面便是一个简便的浅拷贝。这里有一点小提示,就是Object.assign() 函数会触发 setters,而展开语法则不会。所以不能替换也不能模拟Object.assign() 。

如果存在相同的属性名,只有最后一个会生效。

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值