The Future Depends on You
首页/JavaScript/JavaScript面试题记录/
JavaScript面试题记录
上次更新时间:2021-5-26 文章分类:JavaScript 阅读人数:57

setTimeout、Promise、async/await 三者之间异步解决方案的区别?

在Javascript运行机制中 setTimeout 归属于 宏任务Promise.then 归属于 微任务 ;而 async 函数返回一个 Promise 对象,所以 await 后面的内容也归属于 微任务

示例:

async function async1() {
    console.log("async1 start");
    await  async2();
    console.log("async1 end");
}
async  function async2() {
    console.log( 'async2');
}
console.log("script start");
setTimeout(function () {
    console.log("settimeout");
},0);
async1();
new Promise(function (resolve) {
    console.log("promise1");
    resolve();
}).then(function () {
    console.log("promise2");
});
console.log('script end');

上面的示例包含了 setTimeout、Promise 和 async/await。解析一下执行顺序及打印结果:

  1. 执行同步程序:打印出 script start -> 将 setTimeout 部分放入宏任务队列 -> 执行async1 打印出 async1 start -> 执行 async2 打印出 async2 -> 将await后面的内容放入 微任务队列 -> 执行 Promise 打印出 promise1 -> 将 then 放入微任务队列 -> 打印出 script end

  2. 执行微任务队列:首先执行 async1await 后面的部分,打印出 async1 end -> 然后执行 Promise.then 中的部分 -> 打印出 promise2

  3. 执行宏任务队列:setTimeout 部分,打印出 settimeout

所以:打印顺序为:

script start -> async1 start -> async2 -> promise1 -> script end -> async1 end -> promise2 -> setTimeout

宏任务和微任务

查看另一篇文章 宏任务、微任务与Event-Loop

解释 JavaScript 的单线程模型,以及为什么这样设计?setTimeout 的延时为何做不到精确?

单线程模型指的是,JavaScript只在一个线程上运行。也就是说,JavaScript同时只能执行一个任务,其他任务都必须在后面排队等待。

JavaScript运行时,除了一个运行线程,引擎还提供一个消息队列,里面是各种需要当前程序处理的消息。新的消息进入队列的时候,会自动排在队列的尾端。

所有任务可以分成两种,一种是同步任务,另一种是异步任务。同步任务指的是,在JavaScript执行进程上排队执行的任务,只有前一个任务执行完毕,才能执行后一个任务;异步任务指的是,不进入JavaScript执行进程、而进入“任务队列”(task queue)的任务,只有“任务队列”通知主进程,某个异步任务可以执行了,该任务(采用回调函数的形式)才会进入JavaScript进程执行。

setTimeout 会在指定时间向消息队列添加一条消息。如果消息队列之中,此时没有其他消息,这条消息会立即得到处理;否则,这条消息只能等到其他消息处理完,才会得到处理。因此,setTimeout指定的执行时间,只是一个最早可能发生的时间,并不能保证一定会在那个时间发生。

说说你用过的 ES6 语法的功能点,对 ES2017-9 的新增功能点是否有关注?

ES6语法(一) | ES6语法(二) | ES6语法(三) | ES6语法(四) - 函数 | ES6语法(五) - Object | ES6语法(六) - 模板字符串 | ES6语法(七) - 解构赋值 | ES6语法(八) - Promise | ES6语法(九) - Reflect | ES6语法(十) - Proxy | ES6语法(十一) - Generator | ES6语法(十二) - Iterator | ES6语法(十三) - module | ES7新特性 | ES8新特性 | ES9新特性 | ES10新特性

解释 JavaScript 的闭包?解释 this 指针指向的问题以及常用改变 this 指针指向的函数? apply, bind, call 三者之间的区别?

闭包:JS作用域和闭包

this: this的四种绑定规则及优先级

apply, bind, call:call、apply和bind的用法以及区别

JavaScript 继承的几种方式及优缺点?

查看系列篇文章 JavaScript 中的继承方式及优缺点

JavaScript 是如何操作 Cookie 的?

1.创建Cookies

document.cookie = " userName=yctang; expires=Wed, 15 Jan 2020 12:00:00 UTC; path=/userInfo; domain=yctang.club"

这条记录创建了cookie的内容 userName=yctang,过期时间 expires=Wed, 15 Jan 2020 12:00:00 UTC,Cookie路径 path=/userInfo,Cookie域 domain=yctang.club

2.读取Cookie

var cookie = document.cookie

document.cookie 会在一条字符串中返回所有 cookie

3.更新Cookie

通过创建的方式用新值覆盖 cookie 来更改它的值

document.cookie = "userName=new_value"

4.删除Cookie

给 cookie 设置一个空值,并将其过期日期设置为过去的任意时间来删除 cookie

document.cookie = "userName=; expires=Thu, 01 Jan 1970 00:00:00 UTC;"

剩余参数和 arguments 对象的区别

1.剩余参数只包含那些没有对应形参的实参(可以是参数的 部分),而 arguments 对象包含了传给函数的所有实参(是参数的全部);

2.arguments 对象不是一个真实的数组,而剩余参数是真实的 Array 实例 也就是说,能够在它上面直接使用所有的数组方法;

3.arguments 对象还有一些附加的属性(如 callee 属性);

4.如果想在 arguments 对象上使用数组方法,首先要将它转换为真实的数组。

js数据类型

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

var和let的区别

var 有变量提升,let没有变量提升
var 是全局作用域,了他是块级作用域

?. 和 ??

?. 可选链操作符,允许读取位于连接对象链深处的属性的值,而不必明确验证链中的每个引用是否有效
?? 空值合并操作符,只有当左侧为null和undefined时,才会返回右侧的数

条件判断为false

0、-0、0.0、null、""、false、undefined、NaN

判断对象是否为空

1.使用 JSON 对象转化为 json 字符串,再判断该字符串是否为 '{}'

var data = {a:1};
var ren = (JSON.stringify(data) == "{}");
console.log(ren); // true

2.使用ES6的Object.keys()方法

var data = {};
var arr = Object.keys(data);
console.log(arr.length == 0); // true

3.for in 循环判断

var obj = {};
var fun = function() {
  for(var key in obj) {
    return false;
  }
  return true;
}
console.log(fun()); // true

类型转换

  1. 转为字符串:toString()
  2. 转为数字:Number();parseInt();parseFloat()
  3. 转为布尔值:Boolean()

对象合并

使用 Object.assign

let user = { name: 'yctang',age: 18 };
let page = { pageSize: 10, currentPage: 1 };
let newObj = {};

Object.assign(newObj,user,page);
console.log('newObj', newObj) // {name: "yctang", age: 18, pageSize: 10, currentPage: 1}

defineproperty和proxy区别

数组转字符串

toString

let arr = [1,2,3]
const obj = arr.toString()
console.log(obj) // 1,2,3
console.log(typeof obj) // string

隐式转换

let arr1 = [1,2,3]
let arr2 = [4,5,6]
const obj = arr1 + "," + arr2
console.log(obj) // 1,2,3,4,5,6
console.log(typeof obj) // string

Array.join()

let arr = [1,2,3,4,5,6]
const obj = arr.join('')
console.log(obj) // 123456
console.log(typeof obj) // string

字符串截取

slice

let str = 'yctang'
console.log(str.slice(0,5)) // 'yctan'

substring

let str = 'yctang'
console.log(str.substring(0,5)) // 'yctan'

substr

let str = 'yctang'
console.log(str.substr(0,5)) // 'yctan'

当年所剩时间的倒数计

function counter() {
  var date = new Date();
  // 获取当年 年数
  var year = date.getFullYear();
  var date2 = new Date();
  // 获取下一年整年时间戳
  date2.setFullYear(year+1);
  date2.setMonth(0);
  date2.setDate(0);
  date2.setHours(0);
  date2.setMinutes(0);
  date2.setSeconds(0);
  /*转换成秒*/
  var time = (date2 - date) / 1000;
  var day = Math.floor(time / (24 * 60 * 60))
  var hour = Math.floor(time % (24 * 60 * 60) / (60 * 60))
  var minute = Math.floor(time % (24 * 60 * 60) % (60 * 60) / 60);
  var second = Math.floor(time % (24 * 60 * 60) % (60 * 60) % 60);
  var str = year + "年还剩" + day + "天" + hour + "时" + minute + "分" + second + "秒";
  const time11 = document.getElementById("time")
  time11.innerHTML = str
}
setInterval("counter()", 1000);

深拷贝

function deepClone(obj={}){
  if(typeof obj !="object" || obj == null){
    return obj
  }
  let result
  if(Array.isArray(obj)){
    result = []
  } else {
    result = {}
  }
  for(let key in obj){
    if(obj.hasOwnProperty(key)){
      result[key] = deepClone(obj[key])
    }
  }
  return result
}

节流、防抖

节流

函数节流:当持续触发事件时,保证一定时间段内只调用一次事件处理函数。

function throttle(func, delay) {
  var timer = null;
  return function() {
    var _this= this;
    var args = arguments;
    if (!timer) {
      timer = setTimeout(function() {
        func.apply(_this, args);
        timer = null;
      }, delay);
    }
  }
}

防抖

函数防抖:当持续触发事件时,一定时间段内没有再触发事件,事件处理函数才会执行一次,如果设定的时间到来之前,又一次触发了事件,就重新开始延时。

function debounce(fn, wait) {
  var timeout = null
  return function() {        
    if(timeout !== null) {
      clearTimeout(timeout)
   }
    timeout = setTimeout(fn, wait)
  }
}

什么是闭包并举例

闭包:可以访问其他函数内部变量的函数

举例: 第四题中的节流和防抖就是典型的闭包

给多个元素绑定点击事件

let items = document.querySelectorAll('div')
for (let j = 0; j < items.length; j++) {
  (function(j) {
    items[j].onclick = function() {
      console.log(j);
    };
  })(j);
}

数组有哪些方法

concat:合并两个或多个数组
copyWithin:浅复制数组的一部分到同一数组中的另一个位置,并返回它,不会改变原数组的长度
every:测试一个数组内的所有元素是否都能通过某个指定函数的测试,返回一个布尔值
fill:用一个固定值填充一个数组中从起始索引到终止索引内的全部元素
filter:创建一个新数组, 其包含通过所提供函数实现的测试的所有元素
find:返回数组中满足提供的测试函数的第一个元素的值,否则返回 undefined
findIndex:返回数组中满足提供的测试函数的第一个元素的索引,若没有找到对应元素则返回-1
flat:按照一个可指定的深度递归遍历数组,并将所有元素与遍历到的子数组中的元素合并为一个新数组返回
forEach:对数组的每个元素执行一次给定的函
from:从一个类似数组或可迭代对象创建一个新的,浅拷贝的数组实例
includes:判断一个数组是否包含一个指定的值,根据情况,如果包含则返回 true,否则返回false
indexOf:返回在数组中可以找到一个给定元素的第一个索引,如果不存在,则返回-1
isArray:确定传递的值是否是一个 Array
join:将一个数组(或一个类数组对象)的所有元素连接成一个字符串并返回这个字符串
lastIndexOf:返回指定元素在数组中的最后一个的索引,如果不存在则返回 -1
map:创建一个新数组,其结果是该数组中的每个元素是调用一次提供的函数后的返回值
of:创建一个具有可变数量参数的新数组实例,而不考虑参数的数量或类型
pop:从数组中删除最后一个元素,并返回该元素的值。此方法更改数组的长度
push:将一个或多个元素添加到数组的末尾,并返回该数组的新长度
shift:从数组中删除第一个元素,并返回该元素的值
unshift:将一个或多个元素添加到数组的开头,并返回该数组的新长度
reduce:对数组中的每个元素执行一个由您提供的reducer函数(升序执行),将其结果汇总为单个返回值
reduceRight:接受一个函数作为累加器(accumulator)和数组的每个值(从右到左)将其减少为单个值
reverse:将数组中元素的位置颠倒,并返回该数组
slice:返回一个新的数组对象,这一对象是一个由 begin 和 end 决定的原数组的浅拷贝
some:测试数组中是不是至少有1个元素通过了被提供的函数测试。它返回的是一个Boolean值
sort:对数组的元素进行排序,并返回数组
splice:通过删除或替换现有元素或者原地添加新的元素来修改数组,并以数组形式返回被修改的内容
toString:返回一个字符串,表示指定的数组及其元素

事件循环

宏任务、微任务与Event-Loop

谈谈js面向对象

js继承的几种模式

JavaScript 中的继承方式及优缺点

js数据类型

基本类型:字符串(String)、数字(Number)、布尔(Boolean)、对空(Null)、未定义(Undefined)、Symbol;
引用数据类型:对象(Object)、数组(Array)、函数(Function)