前端面试必备知识点 JS篇

138 阅读4分钟

0c323a292df5e0fedd75f2a0576034a85fdf72cb.jpeg

JS模块

1.原型对象

  • 基本类型 number string boolean object null undefined
  • 引用类型 String Number Boolean Object Function Array Date RegExp Error
 function Mother(name) {
      this.name = name;
 }

const son = new Mother("jack");
console.log(son.__proto__ === Mother.prototype);

// 在执行new过程时 
1.创建一个新的对象 son
2. son.__proto__ 指向 Mother.prototype
3. 绑定this指向 Mother.call(son,'jack')
4.执行函数中的代码
5.默认return this

扩展问题
let F = function () {};

Object.prototype.a = function () {};
Function.prototype.b = function () {};

let f = new F();

f存在哪个函数???

console.log(f.__proto__ === F.prototype);
console.log(F.__proto__ === Function.prototype);
console.log(F.prototype.__proto__ === Object.prototype);

答案:只有a没有b
    

2.原型链

 function SuperMarket() {}
 SuperMarket.prototype.product = "money";
 function Shop() {}
 Shop.prototype = new SuperMarket();

let p = new Shop();
console.log(p.product);

当p在shop的原型链上找不到product时 会一直往上找 这样就形成了一条原型链

3.作用域

  • 全局作用域
  • 函数作用域
  • eval(少用)
function books() {
        const book = "shuben";
        return function () {
          console.log(book);
        };
 }

let b = books();
b();

匿名函数一层一层的往上找 形成一条作用域链

问题:请打印出下列结果
for (var i = 0; i < 5; i++) {
        setTimeout(function () {
          console.log(i++);
        }, 0);
}
console.log(i);


答案:5 5 6 7 8 9

函数先进行for循环 将异步宏任务弄进任务执行队列保存 等外部同步任务执行后拿到的i为5,再分别拿出任务队列里的函数

若要输出5 0 1  2 3 4
      for (var i = 0; i < 5; i++) {
        (function (x) {
          setTimeout(function () {
            console.log(x++);
          }, 0);
        })(i);
      }
console.log(i);

4.this指向

  • 对于直接调用 foo 来说,不管 foo 函数被放在了什么地方,this 一定是 window

  • 对于 obj.foo() 来说,我们只需要记住,谁调用了函数,谁就是 this,所以在这个场景下 foo 函数中的 this 就是 obj 对象

  • 在构造函数模式中,类中(函数体中)出现的this.xxx=xxx中的this是当前类的一个实例

  • call、apply和bind:this 是第一个参数,第二个参数apply是传入一个数组,而其他两个则传入的是一个个参数

  • 箭头函数this指向:箭头函数没有自己的this,看其外层的是否有函数,如果有,外层函数的this就是内部箭头函数的this,如果没有,则this是window。

image.png

那么 call,apply,bind的原理是什么呢? call apply都是立即执行返回的结果 而bind返回的是一个函数

 Function.prototype.myCall = function (context, ...args) {
    const ctx = context || window;
    ctx.fn = this;
    const res = ctx.fn(...args);
    delete ctx.fn;
    return res;
  }
  
 Function.prototype.myApply = function (context, args) {
      const ctx = context || window;
      ctx.fn = this;
      const res = ctx.fn(...args);
      delete ctx.fn;
      return res;
    };
    
  Function.prototype.myBind = function () {
      let args = Array.from(arguments);
      let thisObj = args.shift();
      let thisFunc = this;

      // 因为需要构造函数,所以不能是匿名函数了
      let fBound = function () {
        newArgs = args.concat(Array.from(arguments));
        // 判断是否为构造函数
        thisObj = this instanceof fBound ? this : thisObj;
        return thisFunc.apply(thisObj, newArgs);
      };

      // Object.create拷贝原型对象
      fBound.prototype = Object.create(this.prototype);

      return fBound;
    };

5.数据类型的判断

  • typeof typeof返回一个表示数据类型的字符串,返回结果包括:number、boolean、string、symbol、object、undefined、function等7种数据类型,但不能判断null、array等

当然null 被判断为 object是历史遗留的一个bug问题

typeof Symbol(); // symbol 有效
typeof ''; // string 有效
typeof 1; // number 有效
typeof true; //boolean 有效
typeof undefined; //undefined 有效
typeof new Function(); // function 有效
typeof null; //object 无效
typeof [] ; //object 无效
typeof new Date(); //object 无效
typeof new RegExp(); //object 无效
  • instanceof instanceof 是用来判断A是否为B的实例,表达式为:A instanceof B,如果A是B的实例,则返回true,否则返回false。instanceof 运算符用来测试一个对象在其原型链中是否存在一个构造函数的 prototype 属性,但它不能检测null 和 undefined
[] instanceof Array; //true
{} instanceof Object; //Object;//true
new Date() instanceof Date;//true
new RegExp() instanceof RegExp//true
null instanceof Null//报错
undefined instanceof undefined//报错

原理
function myInstanceOf(a, b) {
      let l = a.__proto__;
      let r = b.prototype;
      while (l !== null) {
        if (l === r) return true;
        l = l.__proto__;
      }
      return false;
}

6.闭包

闭包这个概念也是JavaScript中比较抽象的概念,闭包是就是函数中的函数,里面的函数可以访问外面函数的变量,外面的变量的是这个内部函数的一部分。

闭包的作用:

  • 使用闭包可以访问函数中的变量。
  • 可以使变量长期保存在内存中,生命周期比较长

闭包不能滥用,否则会导致内存泄露,影响网页的性能。闭包使用完了后,要立即释放资源,将引用变量指向null。

闭包主要有两个应用场景:

  • 函数作为参数传递(见作用域部分例子)
  • 函数作为返回值(如下例
function outer() { 
let num = 0 //内部变量 
return function add() { 
//通过return返回add函数,就可以在outer函数外访问了。 
num++ //内部函数有引用,作为add函数的一部分了 
console.log(num) 
}
} 
let func1 = outer() // 
func1() //实际上是调用add函数, 输出1 
func1() //输出2 
let func2 = outer() 
func2() // 输出1 
func2() // 输出2

7.async 和 defer

async 是异步加载。defer是延后加载。 借用网上的一张图

image.png

  • async: 一般用于加载第三方脚本,而且不需要修改dom的情况下使用,不保证加载的顺序,加载后立即执行
  • defer: 一般用于需要修改dom的情况下,会先进行加载,保证在html加载完成后再执行对应的脚步

8. Promise

1). Promise 是 JS 中进行异步编程的新解决方案,是为了解决回调地狱的嵌套写法

备注:旧方案是单纯使用回调函数

2). 具体来说

  • 从语法上来说: Promise 是一个构造函数

  • 从功能上来说: promise 对象用来封装一个异步操作并可以获取其成功/失败的结果值

3).状态变更 从pending ----> resolved。 pending ----> rejected

  • 只有这 2 种, 且一个 promise 对象只能改变一次 无论变为成功还是失败, 都会有一个结果数据,而且无法逆转,成功的结果数据一般称为 value, 失败的结果数据一般称为 reason

image.png

4). promise.all 原理:返回一个新的 promise, 只有所有的 promise 都成功才成功, 只要有一个失败了就直接失败

const myAll = (promise) => {
    return new Promise((resolve, reject) => {
        let count = 0;
        let res = [];
        if (promise.length === 0) {
          resolve(res);
        } else {
          function parsePromise(i, data) {
            res[i] = data;
            if (++count === promise.length) {
              resolve(res);
            }
          }

          for (let i = 0; i < promise.length; i++) {
            Promise.resolve(promise[i]).then(
              (data) => {
                parsePromise(i, data);
              },
              (err) => reject(err)
            );
          }
        }
      });
}

5).promise.race原理:返回一个新的 promise, 第一个完成的 promise 的结果状态就是最终的结果状态

const myRace = (promises) => {
    return new Promise((resolve, reject) => {
        if(promises.length === 0) {
          resolve([])
        }else {
          for(let i = 0; i < promises.length; i++) {
            let current = promises[i];
            if(isPromise(current)) {
              current.then(resolve, reject)
            }else {
              resolve(current)
            }
          }
        }
      })
}

6).async await

  • async await本质上是promise的语法糖,其原理是将generator的函数执行权交出来
// 确保拿到res1 交给getRes2函数
let res1 = await getRes1();
let res2 = await getRes2(res1);

9.防抖与节流

  • 防抖:在事件被触发n秒后再执行回调,如果在这n秒内又被触发,则重新计时。
function debounce(fn, delay, immediate) {
  let ctx, args, result, timeout;
  return function () {
    ctx = this;
    args = arguments;
    if (timeout) clearTimeout(timeout);
    if (immediate) {
      let callNow = !timeout;
      timeout = setTimeout(() => {
        timeout = null;
      }, delay);
      if (callNow) result = fn.apply(ctx, ...args);
    } else {
      timeout = setTimeout(() => {
        fn.apply(ctx, ...args);
      }, delay);
    }
    return result;
  };
}
  • 节流:规定在一个单位时间内,只能触发一次函数。如果这个单位时间内触发多次函数,只有一次生效。
function throttle(fn, delay) {
  let ctx, args, timeout;
  return function () {
    ctx = this;
    args = arguments;
    if (!timeout) {
      timeout = setTimeout(() => {
        fn.apply(ctx, ...args);
        timeout = null;
      }, delay);
    }
  };
}

10.ES模块和Commonjs模块

1). CommonJS 模块输出的是一个值的拷贝,ES6 模块输出的是值的引用。

  • CommonJS 模块输出的是值的拷贝,也就是说,一旦输出一个值,模块内部的变化就影响不到这个值。
  • ES6 模块的运行机制与 CommonJS 不一样。JS 引擎对脚本静态分析的时候,遇到模块加载命令import,就会生成一个只读引用。等到脚本真正执行时,再根据这个只读引用,到被加载的那个模块里面去取值。换句话说,ES6 的import有点像 Unix 系统的“符号连接”,原始值变了,import加载的值也会跟着变。因此,ES6 模块是动态引用,并且不会缓存值,模块里面的变量绑定其所在的模块。

2). CommonJS 模块是运行时加载,ES6 模块是编译时输出接口。

  • 运行时加载: CommonJS 模块就是对象;即在输入时是先加载整个模块,生成一个对象,然后再从这个对象上面读取方法,这种加载称为“运行时加载”。
  • 编译时加载: ES6 模块不是对象,而是通过 export 命令显式指定输出的代码,import时采用静态命令的形式。即在import时可以指定加载某个输出值,而不是加载整个模块,这种加载称为“编译时加载”。

CommonJS 加载的是一个对象(即module.exports属性),该对象只有在脚本运行完才会生成。而 ES6 模块不是对象,它的对外接口只是一种静态定义,在代码静态解析阶段就会生成。

11.单线程 --- 宏任务和为任务

  • 我们都知道,js是单线程执行任务,那么在执行任务时,每个线程都会有它自己的event loop(事件循环),所以都能独立运行。然而所有同源窗口会共享一个event loop以同步通信。event loop会一直运行,来执行进入队列的宏任务。一个event loop有多种的宏任务源,这些宏任务源保证了在本任务源内的顺序。但是浏览器每次都会选择一个源中的一个宏任务去执行。这保证了浏览器给与一些宏任务(如用户输入)以更高的优先级。
  • macrotask(宏任务) 和 microtask(微任务) 表示异步任务的两种分类。在挂起任务时,JS 引擎会将所有任务按照类别分到这两个队列中,首先在 macrotask 的队列(这个队列也被叫做 task queue)中取出第一个任务,执行完毕后取出 microtask 队列中的所有任务顺序执行;之后再取 macrotask 任务,周而复始,直至两个队列的任务都取完。
  • 分类如下

image.png

借用两张图来描述

image.png

image.png image.png

  • 请快速输出下列代码的结果 (美团面试题)
setTimeout(function () {
  console.log("1");
}, 0);
async function async1() {
  console.log("2");
  const data = await async2();
  console.log("3");
  return data;
}
async function async2() {
  return new Promise((resolve) => {
    console.log("4");
    resolve("async2的结果");
  }).then((data) => {
    console.log("5");
    return data;
  });
}
async1().then((data) => {
  console.log("6");
  console.log(data);
});
new Promise(function (resolve) {
  console.log("7");
  //   resolve()
}).then(function () {
  console.log("8");
});

输出结果:247536 async2 的结果 1