JavaScript 高频面试题2021

349 阅读9分钟

1. js数据类型、typeof 、instanceof

基本类型:string、number、boolean、null、undefined
引用类型:object、function、array

typeof : 主要用来判断数据类型,返回值有string、boolean、number、undefined、object、function
instanceof: 判断该对象是不是是谁的实例,返回值为true或false。

var arr = [11, 22, 33];

console.log(typeof arr); 			//object
console.log(arr instanceof Array);  //true

undefined 和 null 的区别:
       null表示空对象,undefined表示已在作用域中声明但未赋值的变量
       null 和 undefined 的值相等,但类型不等

console.log(null == undefined); // true     值相同
console.log(null === undefined); // false   类型不同

console.log(typeof undefined); // undefined
console.log(typeof null); // object

2. 闭包

词法作用域:
  词法作用域就是定义在词法阶段的作用域。
  换句话说,词法作用域是由你在写代码时将变量和块作用域写在哪里来决定的。

function fn1() {
  var x = 10;

  function fn2() {  // 注意:fn2函数并没有定义局部变量
  //fn1内部的所有局部变量,对fn2都是可见的。但是反过来就不行
  //这就是Javascript语言特有的"链式作用域"结构,子对象会一级一级地向上寻找所有父对象的变量。
  //所以,父对象的所有变量,对子对象都是可见的,反之则不成立。
    console.log(x); 
  }

  fn2(); //fn2函数在fn1函数内部执行
}

f1(); // 10

闭包:是指有权访问另一个函数作用域中的变量的函数

function fn1() {
  var x = 10;

  function fn2() {  //fn2函数,就是闭包
    console.log(x);
  }

  //既然fn2可以读取fn1中的局部变量,那么只要把fn2作为返回值,我们不就可以在fn1外部读取它的内部变量了吗!
  return fn2;
}

var result = fn1();
result(); // 10

闭包用途

  • 能够访问函数定义时所在的词法作用域(阻止其被回收)
  • 私有化变量
  • 模拟块级作用域
  • 创建模块

闭包缺点:会导致函数的变量一直保存在内存中,过多的闭包可能会导致内存泄漏

最后:引用闭包的两个思考题(阮一峰),如果你能理解下面两段代码的运行结果,应该就算理解闭包的运行机制了。

//代码片段一:
var name = "The Window";

  var object = {
    name : "My Object",

    getNameFunc : function(){
      return function(){
        return this.name;
      };

    }

  };

  alert(object.getNameFunc()());    // "The Window"
//代码片段二:
var name = "The Window";

  var object = {
    name : "My Object",

    getNameFunc : function(){
      var that = this;
      return function(){
        return that.name;
      };

    }

  };

  alert(object.getNameFunc()());    // "My Object"

3. 原型、原型链

原型:对象中固有的__proto__属性,该属性指向对象的prototype原型属性。

原型链:当我们访问一个对象的属性时,如果这个对象内部不存在这个属性,那么就会去它的原型对象里找这个属性,而这个原型对象又会有自己的原型,于是就这样一直找下去,也就是原型链的概念。所以原型链为对象成员查找机制提供一个方向

特点:JavaScript对象是通过引用来传递的,我们创建的每个新对象实体中并没有一份属于自己的原型副本。当我们修改原型时,与之相关的对象也会继承这一改变。 在这里插入图片描述

4. this指向

this 表示当前对象的一个引用。

1. 在方法中,this 表示该方法所属的对象
     a、如果该方法属于某个对象,this指向该对象
     b、如果该方法是全局方法,this指向window ,在严格模式下,this 是未定义的(undefined)。
     c、如果是构造函数调用,this指向这个用new新创建的对象。

2. 在事件中,this 表示接收事件的元素

3. apply() 、 call() 和 bind() 调用模式,这三个方法都可以显示的指定调用函数的 this 指向。
     apply()    接收的参数是数组
     call()        接收参数列表
     bind()      方法不会立即执行,而是返回一个改变了上下文 this 后的函数。

5. 作用域、作用域链、变量提升

     作用域:确定当前执行的代码对某些变量的访问权限。(全局作用域、函数作用域、块级作用域)。

     作用域链:就是从当前作用域开始一层一层向上寻找某个变量,直到找到全局作用域还是没找到,就宣布放弃。

     变量提升(预解析):在当前作用域下, JS 代码执行之前,浏览器会把用var 和 function 声明的变量和函数进行提前声明。

6. 继承(ES6 extends 、 组合继承 )

1. extends (ES6)
注意: 1.在 ES6 中类没有变量提升,所以必须先定义类,才能通过类实例化对象。
            2.类里面的共有属性和方法一定要加this使用。

//父类
class Father {
// 类里面的  构造函数
  constructor(firstName, secondName) {
    this.firstName = firstName;
    this.secondName = secondName;
  }  
// 类里面的  普通函数
  say() {
    console.log("父类的方法");
  }
}


//子类
class Son extends Father {
  // super 关键字用于访问和调用对象父类上的函数。可以调用父类的构造函数,也可以调用父类的普通函数
  // 注意: 子类在构造函数中使用super, 必须放到 this 前面  (必须先调用父类的构造方法,在使用子类构造方法)
  constructor(x,y) {
    // 调用父类的构造函数
    super(firstName, secondName);
    
    // 调用普通函数(可以,但无意义)
    //super.say();
  }
}

let s = new Son("张", "无忌");
console.log(s.firstName);   // 张
s.say();					// 父类的方法    

2. 组合继承:构造函数 + 原型对象
核心原理
          使用call(), 将父类型的this 指向 子类型(缺点:不能继承父类型原型上的方法)
          解决方法:让子类的 prototype 原型对象 = new 父类() (父类实例化之后另外开辟内存空间,就不会影响原来父类原型对象)

// 父 构造函数
function Father(firstName, secondName) {
  this.firstName = firstName;
  this.secondName = secondName;
  this.have = function () {
    console.log("父亲有三栋别墅!!!");
  };
}

Father.prototype.rank = function () {
  console.log("最高军衔!");
};

// 子 构造函数
function Son(firstName) {
  Father.call(this, "李");
}

Son.prototype = new Father(); //这一步必须写在,创建子类对象之前

let s = new Son();
s.have();   // 父亲有三栋别墅!!!
s.rank();   // 最高军衔!

7. 普通函数 和 箭头函数的区别

  • 箭头函数是匿名函数,不能作为构造函数,不能使用new
  • 箭头函数不绑定this,会捕获其所在的上下文的this值,作为自己的this值
  • 箭头函数通过 call() 或 apply() 方法调用一个函数时,只传入了一个参数,对 this 并没有影响。
  • 箭头函数没有原型属性

8. 有哪些内置对象

  • 全局变量值: NaN、undefined
  • 全局函数: parseInt() 、parseFloat()
  • 构造函数: Date、Object 等

9. JSON 与 XML

  • JSON 是一种基于文本的轻量级的数据交换格式。它可以被任何的编程语言读取和作为数据格式来传递。
  • 在项目开发中,我们使用 JSON 作为前后端数据交换的方式。
  • 前端通过通过 JSON.stringify() 将符合JSON格式的数据结构 序列化为 JSON字符串,然后将它传递到后端
  • 后端通过JSON.parse()将JSON格式的字符串解析后生成对应的数据结构,以此来实现前后端数据的一个传递。

相比JSON,XML还是有一些不足:
          XML标签冗余高,数据体积大,传输速度慢
          XML解析较难,json解析难度几乎为0

10. ES6新增

  1. const(常量)/let 用来声明变量,不可重复声明,具有块级作用域。不存在变量提升
  2. 变量的解构赋值(包含数组、对象、字符串、数字及布尔值,函数参数),剩余运算符(...rest)
// 数组 解构赋值:
let arr = [11, 22, 33];
let [a, b, c] = arr;
console.log(a); //11
console.log(b); //22
console.log(c); //33

// 对象 解构赋值:
let obj = { name: "Tom", age: 18, address: "UK" };
let { name: x, age, address: z } = obj;
console.log(x); // Tom
console.log(age); // 18
console.log(z); //UK

//拓展运算符(...)用于取出参数对象所有可遍历属性然后拷贝到当前对象。

//基本用法
let person = {name: "Amy", age: 15}; 
let someone = { ...person }; 
someone; //{name: "Amy", age: 15}

//可用于合并两个对象
let age = {age: 15}; 
let name = {name: "Amy"}; 
let person = {...age, ...name}; 
person; //{age: 15, name: "Amy"}
  1. 模板字符串
//  ${ ... } 用来渲染一个变量
//  ` 作为分隔符

let user = 'Barret';
console.log(`Hi ${user}!`); // Hi Barret!
  1. 箭头函数
  2. Set和Map数据结构
         Set 对象允许你存储任何类型的唯一值,无论是原始值或者是对象引用
         Map 对象保存键值对。任何值(对象或者原始值) 都可以作为一个键或一个值
  3. Promise
  4. async函数
  5. Module语法(import/export)

11. 浅拷贝和深拷贝

浅拷贝:

 只是拷贝一层,更深层次对象级别的只拷贝引用(地址),所以被拷贝的对象和拷贝出来的对象都是共用一个地址(会相互影响)
  1. Object.assign(target,source)
  2. es6对象扩展运算符

深拷贝:

 拷贝出来的对象和原对象之间没有任何数据是共享的,所有的东西都是自己独占的一份。
1. jQuery的$.extend():
   我们可以通过$.extend()方法来完成深复制。值得庆幸的是,我们在jQuery中可以通过添加一个参数来实现递归extend
2. 使用JSON对象实现深拷贝
   使用JSON全局对象的parse和stringify方法来实现深复制也算是一个简单讨巧的方法。
function jsonClone(obj) {
  return JSON.parse(JSON.stringify(obj));  
  //JSON.parse() 方法用来解析JSON字符串,构造由字符串描述的JavaScript值或对象。
  // JSON.stringify() 方法将一个 JavaScript 对象或值转换为 JSON 字符串
}
var clone = jsonClone({ a:1 }); 
3. 使用递归自己封装一个函数
 function deepClone(obj) {
  if (!obj || typeof obj !== "object") return;
  let newObj = Array.isArray(obj) ? [] : {};
  for (let key in obj) {
    if (obj.hasOwnProperty(key)) {
      newObj[key] =
        typeof obj[key] === "object" ? deepClone(obj[key]) : obj[key];
    }
  }
  return newObj;
} 

12. 防抖

定义:触发事件后在n秒内函数只能执行一次,如果在n秒内又触发了事件,则会重新计算函数执行时间。

使用场景:搜索框搜索输入。只需用户最后一次输入完,再发送请求 手机号、邮箱验证输入检测 onchange oninput事件 窗口大小Resize。只需窗口调整完成后,计算窗口大小。防止重复渲染。

const debounce = (fn, wait, immediate) => {
  let timer = null;
  return function (...args) {
    if (timer) clearTimeout(timer);
    if (immediate && !timer) {
      fn.call(this, args);
    }
    timer = setTimeout(() => {
      fn.call(this, args);
    }, wait);
  };
};
const betterFn = debounce(() => console.log("fn 防抖执行了"), 1000, true);
document.addEventListener("scroll", betterFn); 

13. 节流

定义:当持续触发事件时,保证隔间时间触发一次事件。

使用场景

  1. 懒加载、滚动加载、加载更多或监听滚动条位置;
  2. 百度搜索框,搜索联想功能;
  3. 防止高频点击提交,防止表单重复提交;
 function throttle(fn,wait){
  let pre = 0;
  return function(...args){
      let now = Date.now();
      if( now - pre >= wait){
          fn.apply(this,args);
          pre = now;
      }
  }
}
function handle(){
  console.log(Math.random());
}
window.addEventListener("mousemove",throttle(handle,1000));