2021最新前端面试精选题(JavaScript)

1、JS的数据类型

(1)基本数据类型:在存储变量时存储的是值本身,包括String、Number、Boolean、undefined、null、Symbol(es6新增)、BigInt(es10新增)
(2)引用数据类型:在存储变量是存储的仅仅是地址,如Object、Array、Date

两者的区别:
(1)基本数据类型存放在栈中,由操作系统自动分配释放存放函数的参数值、局部变量的值等;
引用数据类型栈中存放的是地址,真正的对象实例存放在堆空间中。
(2)基本数据类型占据空间小,大小固定,属于被频繁使用的数据;
引用数据类型占据空间大,大小不固定。当解释器寻找引用值时,会首先检索其在栈中的地址,取得地址后从堆中获得实体。

2、JS数据类型的转换

(1)转换为布尔值(调用Boolean()方法)
(2)转换为数字( Number()、parseInt()和parseFloat() )
(3)转换为字符串( .toString()、String() )注:null、undefined没有.toString()

3、JS中数据类型的判断

(1)typeof

console.log(typeof 2);                // number
console.log(typeof true);             // boolean
console.log(typeof 'str');            // string
console.log(typeof []);               // object
console.log(typeof function(){});     // function
console.log(typeof {});               // object
console.log(typeof undefined);        // undefined
console.log(typeof null);             // object

由此可见,typeof并不能完全准确的判断变量到底是什么类型,null和[]会显示object

(2)instanceof

console.log(2 instanceof Number);                  // false
console.log(true instanceof Boolean);              // false
console.log('str' instanceof String);              // false
console.log([] instanceof Array);                  // true
console.log(function(){} instanceof Function);     // true
console.log({} instanceof Object);                 // true

可以看出,instanceof可以精准判断引用数据类型(Array、Function、Object),而基本数据类型不能被instanceof精准判断。
我们来看一下instanceof在MDN中的解释:instanceof运算符用来测试一个对象在其原型链中是否存在一个构造函数的prototype属性。其意思就是判断对象是否是某一数据类型(如Array)的实例,请重点关注一下是判断一个对象是否是数据类型的实例,在这里字面量值,2、true、'str’不是实例,所以判断为false。

(3)constuctor

console.log((2).constructor === Number);                  // true
console.log((true).constructor === Boolean);              // true
console.log(('str').constructor === String);              // true
console.log(([]).constructor === Array);                  // true
console.log((function(){}).constructor === Function);     // true
console.log(({}).constructor === Object);                 // true

这里有个坑,如果我创建一个对象,更改它的原型,constuctor就会变得不可靠了

function fn() {};
Fn.prototype = new Array();
var f = new Fn();
console.log(f.constructor === Fn);     // false
console.log(f.constructor === Array)   // true

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

var a = Object.prototype.toString;
console.log(a.call(2));                // Number
console.log(a.call(true));             // Boolean
console.log(a.call('str'));            // String
console.log(a.call([]));               // Array
console.log(a.call({}));               // Object
console.log(a.call(function(){}));     // Function
console.log(a.call(undefined));        // Undefined
console.log(a.call(null));             // Null

4、JavaScript的作用域和作用域链

作用域:作用域是定义变量的区域,它有一套访问变量的规则,这套规则来管理浏览器引擎如何在当前作用域以及嵌套的作用域中根据变量(标识符)进行变量查找.。
作用域链:作用域链的作用是保证对执行环境有权访问的所有变量和函数的有序访问,通过作用域链,我们可以访问到外层环境的变量和函数。当我们查找一个变量时,如果当前执行环境中没有找到,我们可以沿着作用域链向后查找。

5、JavaScript数组原生方法

(1)push():向数组末尾添加一个或多个元素,并返回新的长度
(2)shift():把数组的首位元素删除
(3)unshift():向数组的首部添加一个或多个元素,并返回新的长度
(4)pop():删除数组的最后一个元素
(5)splice():用于插入、删除或替换数组的元素
(6)concat():拼接两个或多个数组
(7)join():把数组中的所有元素放入一个字符串,元素是通过指定的分隔符进行分隔的
(8)toString():把数组转换为字符串,并返回结果
(9)reverse():用于颠倒数组中元素的顺序
(10)slice():从已有的数组中返回选定的元素
(11)sort():对数组的元素进行排序(从小到大)
(12)indexOf():返回数组中的索引
(13)lastIndexOf():返回获取项在数组中出现的最后一次索引
(14)forEach():循环遍历数组
(15)map():循环遍历数组

6、JavaScript对象原生方法

(1)charAt():返回在指定位置的字符
(2)charCodeAt():返回在指定的位置的字符的Unicode编码
(3)concat():连接字符串
(4)indexOf():检索字符串
(5)match():找到一个或多个正则表达式的匹配
(6)replace():替换与正则表达式匹配的字符串
(7)search():检索与正则表达式相匹配的值
(8)slice():提取字符串的片段,并在新的字符串中返回被提取的部分
(9)split():把字符串分割为数组
(10)toLocaleLowerCase():把字符串转换为小写
(11)toLocaleUpperCase():把字符串转换为大写
(12)toLowerCase():把字符串转换为小写
(13)toUpperCase():把字符串转换为大写
(14)substr():从起始索引号提取字符串中指定数目的字符
(15)substring():提取字符串中两个指定的索引号之间的字符

7、ECMAScript2015(ES6)有哪些新特性?

(1)块作用域
(2)类
(3)箭头函数
(4)模板字符串
(5)加强的对象字面
(6)对象结构
(7)Promise
(8)模块
(9)Symbol
(10)代理proxy
(11)函数默认参数
(12)rest和展开

8、var,let和const的区别是什么?

(1)var有变量提升,无论声明在何处,都会被视为声明在函数的最顶部,let、const声明不会提升
(2)var是函数作用域,let、const是块级作用域
(3)var可以重复声明,let和const不可重复定义,否则会报错
(4)const声明的变量通常被认为是常量,let声明变量。const声明的变量不可修改,如果const变量是一个对象的话,对象所包含的值是可以被修改的,抽象一点说,就是对象所指向的地址没有变就行了。

9、什么是箭头函数?

// ES5 version
function getName(name) {
  return 'Hello' + name
}
// ES6version 箭头函数
const getName = (name) => `hello ${name}`

箭头函数表达式的语法比函数表达式更简洁,并且没有自己的this,arguments,super,没有prototype属性,所以不能使用new关键字。箭头函数的this指向的是自己的父级。

10、手动实现Array.prototype.map方法

function map(arr, mapCallback) {
  if (!Array.isArray(arr) || !arr.length || typeof mapCallback !== 'function') {
    return [];
  } else {
    let result = []
    for (let i = 0, len = arr.length; i < len; i++) {
      result.push(mapCallback(arr[i],i,arr))
    }
    return result 
  }
}

11、手动实现一个Array.prototype.filter方法

function map(arr, filterCallback) {
  if (!Array.isArray(arr) || !arr.length || typeof filterCallback !== 'function') {
    return [];
  } else {
    let result = []
    for (let i = 0, len = arr.length; i < len; i++) {
      if (filterCallback(arr[i],i,arr)) {
        result.push(arr[i])
      }
    }
    return result 
  }
}

12、手动实现一个Array.prototype.reduce方法

function map(arr, reduceCallback, initiaValue) {
  if (!Array.isArray(arr) || !arr.length || typeof reduceCallback!== 'function') {
    return [];
  } else {
    let hasInitiaValue = initiaValue !== undefined
    let value = hasInitiaValue ? initiaValue : arr[0]
    for (let i = hasInitiaValue ? 1 : 0, len = arr.length; i < len; i++) {
      value = reduceCallback(value, arr[i], i, arr)
    }
    return value
  }
}

13、手写call、apply及bind函数

call函数的实现步骤:
1、判断调用对象是否为函数,即使我们是定义在函数的原型上的,但是可能出现使用call等方式调用的情况
2、判断传入上下文对象是否存在,如果不存在的话,则设置为window。
3、处理传入的参数,截取第一个参数后的所有参数
4、将函数作为上下文对象的一个属性
5、使用上下文对象来调用这个方法,并保存返回结果
6、删除刚才新增的属性
7、返回结果

// call函数实现
Function.prototype.myCall = function(context) {
  // 判断调用对象
  if (typeof this !== "function") {
    console.error("type error");
  }

  // 获取参数
  let args = [...arguments].slice(1),
    result = null;

  // 判断 context 是否传入,如果未传入则设置为 window
  context = context || window;

  // 将调用函数设为对象的方法
  context.fn = this;

  // 调用函数
  result = context.fn(...args);

  // 将属性删除
  delete context.fn;

  return result;
};

apply函数的实现步骤:
(1)判断调用对象是否为函数,即使我们是定义在函数的原型上的,但是可能出现使用 call 等方式调用的情况。
(2)判断传入上下文对象是否存在,如果不存在,则设置为 window 。
(3)将函数作为上下文对象的一个属性。
(4)判断参数值是否传入
(5)使用上下文对象来调用这个方法,并保存返回结果。
(6)删除刚才新增的属性
(7)返回结果

// apply 函数实现

Function.prototype.myApply = function(context) {
  // 判断调用对象是否为函数
  if (typeof this !== "function") {
    throw new TypeError("Error");
  }

  let result = null;

  // 判断 context 是否存在,如果未传入则为 window
  context = context || window;

  // 将函数设为对象的方法
  context.fn = this;

  // 调用方法
  if (arguments[1]) {
    result = context.fn(...arguments[1]);
  } else {
    result = context.fn();
  }

  // 将属性删除
  delete context.fn;

  return result;
};

bind函数的实现步骤:
(1)判断调用对象是否为函数,即使我们是定义在函数的原型上的,但是可能出现使用 call 等方式调用的情况。
(2)保存当前函数的引用,获取其余传入参数值。
(3)创建一个函数返回
(4)函数内部使用 apply 来绑定函数调用,需要判断函数作为构造函数的情况,这个时候需要传入当前函数的 this 给 apply 调用,其余情况都传入指定的上下文对象。

// bind 函数实现
Function.prototype.myBind = function(context) {
  // 判断调用对象是否为函数
  if (typeof this !== "function") {
    throw new TypeError("Error");
  }

  // 获取参数
  var args = [...arguments].slice(1),
    fn = this;

  return function Fn() {
    // 根据调用方式,传入不同绑定值
    return fn.apply(
      this instanceof Fn ? this : context,
      args.concat(...arguments)
    );
  };
};

14、函数柯里化的实现

// 函数柯里化指的是一种将使用多个参数的一个函数转换成一系列使用一个参数的函数的技术。

function curry(fn, args) {
  // 获取函数需要的参数长度
  let length = fn.length;

  args = args || [];

  return function() {
    let subArgs = args.slice(0);

    // 拼接得到现有的所有参数
    for (let i = 0; i < arguments.length; i++) {
      subArgs.push(arguments[i]);
    }

    // 判断参数的长度是否已经满足函数所需参数的长度
    if (subArgs.length >= length) {
      // 如果满足,执行函数
      return fn.apply(this, subArgs);
    } else {
      // 如果不满足,递归返回科里化的函数,等待参数的传入
      return curry.call(this, fn, subArgs);
    }
  };
}

// es6 实现
function curry(fn, ...args) {
  return fn.length <= args.length ? fn(...args) : curry.bind(null, fn, ...args);
}

15、[‘1’,‘2’,‘3’].map(parseInt)输出什么?

map遍历数组
parseInt用来解析字符串,parseInt(string,Index)
[‘1’,‘2’,‘3’].map(parseInt)相当于parseInt(1,0),parseInt(2,1),parseInt(3,2)
parseInt(1,0)等同于1的十进制,也就是1
parseInt(2,1)等同于一进制,2大于1,所以输出NaN
parseInt(3,2)等同于二进制,3大于2,所以输出NaN

故[‘1’,‘2’,‘3’].map(parseInt)最终输出为[1,NaN,NaN]

16、什么是防抖和节流?有什么区别,如何实现?

防抖:如果n秒内高频事件再次触发,则重新计算时间,比如说用户一直点击某个按钮,实际上是用户最后一次点击按钮n秒后执行

function debounce(fn) {
   let timeout = null
   // 创建一个标记用来存放定时器的返回值
   return funtion () {
      clearTimeout(timeout)
      // 每当用户输入的时候把前一个setTimeout clear掉
      timeout = setTimeout(() => {
         // 然后创建一个新的setTimeout,这样就能保证输入字符后的interval间隔内如果还有字符输入的话,就不会执行fn函数
         fn.apply(this, arguments)
      },500)
   }
}
function sayHi() {
  console.log('防抖成功')
  document.getElementById('inp').addEventListener('input',debounce(sayHi))
}

节流:高频事件触发,但在n秒内只会执行一次,所以节流函数会稀释函数的执行频率

function throttle(fn) {
  // 通过闭包保存一个标记
  let canRun = true
  return function() {
    // 在函数开头判断标记是否为true,不为true,则return
    if (!canRun) return
    // 立即设置为false
    canRun = false
    setTimeout(() => {
      // 将外部传入的函数的执行放在 setTimeout 中
      fn.apply(this,arguments)
      // 最后在 setTimeout执行完毕后再把标记设置为true,表示可以执行下一次循环,当定时器都没有执行的时候标记永远是false,在开头被return掉
      canRun = true
    },500)
  }
}
function sayHi() {
  console.log(e.target.innerWidth,e.target.innerHeight)
  window.addEventListener('resize',throttle(sayHi))
}

17、介绍一下Set、Map、weakSet、weakMap的区别?

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

WeakSet:成员都是对象;成员都是弱引用,可以被垃圾回收机制回收,可以用来保存DOM节点,不容易造成内存泄漏。

Map:本质上是键值对的集合,类似集合;可以遍历,方法很多,可以跟各种数据格式转换

WeakMap:只接收对象值为键名,不接收其他类型的值作为键名;键名是弱引用,键值可以是任意的,键名所指向的对象可以被垃圾回收机制回收,此时键名是无效的,不能遍历。

标题18、Ajax是什么?如何创建一个Ajax?

Ajax是一种异步通信的方法,通过直接由JS脚本向服务器发起http通信,然后根据服务器返回的数据,更新网页的相应部分,而不是刷新整个页面的一种方法

手写Ajax:
原生:

// 1、创建一个Ajax对象
var xhr = window.XMLHttpRequest? new XMLHttpRequest() : new ActiveXObject('Microsoft.XMLHTTP'); //兼容IE6及以下版本
// 2、配置Ajax请求地址
xhr.open('get','index.xml',true);
// 3、发送请求
xhr.send(null)
// 4、监听请求、接受响应
xhr.onreadystatechange = function() {
  if (xhr.readyState == 4 && xhr.status == 200 || xhr.status == 304) {
      console.log(xhr.responsetXML)
  }
}

jQuery:

$.ajax({
  type: 'post',
  url: '',
  async: true,
  data: data,
  dataType:'jsonp',
  success:function(msg){},
  err:function(error){},
})

19、promise 封装实现

function getJSON(url){
  // 创建一个promise对象
  let promise = new Promise(function(resolve,reject) {
    let xhr = new XMLHttpRequest();
    // 新建一个http请求
    xhr.open('get',url,true);
    // 设置状态的监听函数
    xhr.omreadystatechange = function() {
      if (this.readyState !== 4) return;
      // 当成功请求或者失败时,改变promise的状态
      if (this.status === 200) {
        resolve(this.response);
      } else {
        reject(new Error(this.statusText));
      }
    };
    // 设置响应的数据类型
    xhr.responseType = 'json';
    // 设置请求头信息
    xhr.setRequestHeader('Accept','application/json');
    //发送http请求
    xhr.send(null);
  });
  return promise
}

7、下面的代码会在console输出神马?为什么?

(function(){
  var a = b = 3;
})();
console.log('a defined?' + (typeof a !== 'undefined'));
console.log('b defined?' + (typeof b !== 'undefined'));

这跟变量作用域有关,输出可以换成下面:

console.log(a) // undefined
console.log(b) // 3

为什么会是这样的结果呢?我们来拆解一下自执行函数中的变量赋值:

var a = b = 3
// 可拆解成
b = 3
var a = b
// 所以b成了全局变量,而a是自执行函数的一个局部变量

所以,输出的结果是:

a defined?false
b defined?true

20、写一个按照下面方式调用都能正常工作的sum方法

console.log(sum(2,3)) // 5
console.log(sum(2)(3)) // 5

function sum() {
  var fir = arguments[0]
  if (arguments.length === 2) {
    return arguments[0] + arguments[1]
  } else {
    return function(sec) {
      return fir + sec
    }
  }
}

21、下面代码会输出什么?为什么?

var arr1 = 'john'.split('');
var arr2 = arr1.reverse();
var arr3 = 'jones'.split('');
arr2.push(arr3);
console.log('arr1.length=' + arr1.length + 'last=' + arr1.slice(-1));
console.log('arr2.length=' + arr2.length + 'last=' + arr2.slice(-1));

输出结果:

arr1.length=5 last=j,o,n,e,s
arr2.length=5 last=j,o,n,e,s

这里主要是reverse()会改变数组本身,并返回原数组的引用

22、下面这段代码会输出什么?为什么?

console.log(1 + '2' + '2');
console.log(1 + +'2' + '2');
console.log(1 + -'1' + '2');
console.log(+'1' + '1' + '2');
console.log('A' - 'B' + '2');
console.log('A' - 'B' + 2);

// '122',这个想必大家都没疑问,Number + String,以拼接的方式返回字符串
// '32', 这是 因为数字字符串之前存在数字中的正负号(+/-)时,会被转换成数字
// '02',原因同第二个
// '112',原因同第二个
// 'NaN2', 原因就是字符串A - 字符串B,得到的是NaN,再拼接2字符串
// NaN, 原因就是字符串A - 字符串B,得到的是NaN,NaN + 2 得到的还是NaN

23、解释下列代码的输出

console.log(0 || 1);          // 1
console.log(1 || 2);          // 1
console.log(0 && 1);          // 0
console.log(1 && 2);          // 2
console.log(1 || 2 || 0);     // 1
console.log(0 || 2 || 1);     // 2
console.log(0 || 0 || false); // false
console.log(1 && 2 || 0);     // 2
console.log(0 || 2 && 1);     // 1
console.log(0 && 2 || 1);     // 1

为什么?

// 其实这题主要考的是 Boolean 取值问题
0 的boolean值是false
||符号取值是取最前面的一个真值
&&符号如果都是false,返回最后的一个false值,如果是真值,返回最后一个真值

补充:

// 在javaScript,常见的false值:
0,'0', +0,-0,false, '', null, undefined, null, NaN
// 要注意空数组和空对象
console.log([] == false) // true
console.log({} == false) // false
console.log(Boolean([])) // true
console.log(Boolean({})) // true
// 所以在if中,[]{}都表现为true;

24、10的阶乘的写法

(function f(n) {
  return ((n > 1) ? n * f(n-1) : n)
})(10)

25、解释下面代码的输出

var hero = {
  _name: 'John Doe',
  getSecretIdentity: function() {
    return this._name
  }
}

var stoleSecretIdentity = hero.getSecretIdentity

console.log(stoleSecretIdentity ())  // undefined
console.log(hero.getSecretIdentity()) // John Doe

// 重点在于赋值,var stoleSecretIdentity = hero.getSecretIdentity相当于
var stoleSecretIdentity = function () {
  return this._name
}
所以第一个输出是undefined
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值