字节跳动前端面试总结(持续更新)

一、HTML

二、CSS

三、JS

1、setTimeOut()和setInterval()的区别

都是用来处理延时和定时任务的函数

setTimeOut()只延迟执行一次,且是在指定的毫秒数之后再调用函数。 
setInterval()是一段时间执行一次,可以执行多次,是在每隔指定的毫秒数循环调用函数,直到clearInterval把它清除掉。 

2、原型链继承

原型和原型链(步骤十分详细)

让子类的原型对象(prototype)指向父类的实例,当子类实例找不到对应的属性和方法时,就会往它的原型对象,也就是父类实例上找,从而实现对父类属性和方法的继承。

详细请看JS-继承的六种方式

3、call、apply、bind的区别

  • 函数调用问题:call 和 apply 会调用函数,bind 不会调用函数
  • 参数传递问题:call 和 apply 传递的参数不一样, call 传递参数 aru1, aru2..形式 apply 必须数组形式[arg]

详细请看二、--2、改变函数内部this指向

4、this指向

5、new操作符(4件事)

  • 创建一个空对象obj
  • 设置原型链 即 obj . _proto _ = 构造函数.prototype ;
  • 让构造函数中的this指向obj
  • 返回对象obj

模拟JS实现new操作符

function myNew(constrc, ...args) {
    const obj = {};    // 1. 创建一个空对象
    obj.__proto__ = constrc.prototype;    // 2. 将obj的__proto__属性指向构造函数的原型对象
    constrc.apply(obj,args);    // 3. 将构造函数constrc执行的上下文this指向obj,并执行 
    return obj;    // 4. 返回新创建的对象
}

6、基本类型和引用类型

  基本类型 引用类型
种类 Undefinded、Null、Number、String、Boolean、Symbol Object、Array、RegExp、Date、Function
描述 简单的数据段 为多个值构成的对象
操作 按值访问,可操作保存在变量中的实际的值。在栈内存中发生复制行为时系统会为新的变量提供新值 按引用访问,当复制保存对象的某个变量时,操作的是对象的引用(栈存的地址),但在为对象添加属性时,操作的是实际的对象(堆数据)
存储 数据存放在栈中 数据存放在堆中,栈中存放的是堆中的引用地址

可以用typeof方法打印来判断是属于哪个类型的(数组Array.isArray(arr) )

7、隐式转换

不同类型的变量比较时要先转类型,叫做类型转换,类型转换也叫隐式转换。

(1)常见隐式转换

算数运算转换规则:

① 字符串 + 数字,数字就会转成字符串。

② 数字 - 字符串,字符串转成数字。如果字符串不是纯数字就会转成NaN。字符串 - 数字也一样。两个字符串相减也先转成数字。

③ *,/,>,< 跟 - 的转换也是一样。

(2)关于==

不同数据类型之间的比较转换规则:对象—>字符串—>数字,布尔值—>数字

例如:对象和布尔值进行比较时,对象先转换为字符串,然后再转换为数字,布尔值直接转换为数字

[] == true;  //false  []转换为字符串'',然后转换为数字0,true转换为数字1,所以为false
[1,2,3] == '1,2,3' // true  [1,2,3]转化为'1,2,3',然后和'1,2,3', so结果为true;
[1] == 1;  // true  `对象先转换为字符串再转换为数字,二者再比较 [1] => '1' => 1 所以结果为true
'1' == 1 // true
'1' == true; // true 
true == 1 // true

1、一个有趣的题目:

[] == false;    //true  对象 => 字符串 => 数值0 false转换为数字0,
![] == false;    //true  

第二个前边多了个!,则直接转换为布尔值再取反,转换为布尔值时,空字符串(''),NaN,0,null,undefined这几个外返回的都是true, 所以! []这个[] => true 取反为false,所以![] == false为true。

2、特殊情况

  • null和undefined在进行比较的时候,不会进行类型转换
undefined==null;    //true 
  • 只有undefined、null、NaN、0、' '会被转换为false,其余都为true;
0 == null                 //false; 

0 == undefined            //false;

false == null             //false;

false == undefined        //false;

true == null              //false;

true == undefined         //false;

'' == null                //false;

'' == undefined           //false;
  • NaN和任何值都不相等, 包括NaN本身;

8、为什么 0.1 + 0.2!= 0.3 ?

JS数字运算的精度缺失问题(数字的存储方式)

console.log(0.1+0.2) 
//0.30000000000000004

原理:在计算机中数字无论是定点数还是浮点数都是以多位二进制的方式进行存储的。
在JS中数字采用的IEEE 754的双精度标准进行存储(存储一个数值所使用的二进制位数比较多,精度更准确)

0.1和0.2用二进制无法整除,JS中采用的IEEE 754的双精度标准,在存储空间有限的情况下,当出现这种无法整除的小数的时候就会取一个近似值,在js中如果这个近似值足够近似,那么js就会认为他就是那个值。

console.log(0.1000000000000001) 
// 0.1000000000000001 (中间14个0,会打印除本身)
console.log(0.10000000000000001) 
// 0.1 (中间15个0,js会认为两个值足够近似,所以输出0.1

此时对于JS来说,其不够近似于0.3,于是就出现了0.1 + 0.2 != 0.3 这个现象。 当然,也并非所有的近似值相加都得不到正确的结果。

解决办法:将浮点数转化成整数计算

0.1+0.2 => (0.1*10+0.2*10)/10

9、事件循环机制和macro-micro

(1)事件循环机制(the Event Loop)

  1. 事件开始,进入主线程
  2. 主线程执行同步事件,异步事件移交给异步模块
  3. 当异步进程执行完毕之后,让其回调进入事件队列
  4. 主线程的同步事件执行完毕,就会查询事件队列,如果事件队列中存在事件,事件队列就将事件推到主线程
  5. 主线程执行事件队列推过来的事件,执行完毕后再去事件队列中查询… 这个循环的过程就是事件循环

每次循环代表一个周期,在这个周期内,主线程执行栈正常工作,当主线程任务清空时,会从任务队列中提取到已完成的异步回调函数入栈,然后执行栈又开始工作,进入下一次事件循环。

注意:每次事件循环只会从任务队列中获取一个回调函数,无论回调队列中有多少个函数,都只会有一个推到主线程。其他的函数需要等到下一次事件循环(主线程任务又清空时)

拓展:setTimeout不一定是在规定时间内后立即执行。如上述例子,1000ms只代表多长时间后进入回调队列,但什么时候去执行它,要看主线程的任务什么时候结束。

console.log('script start')
setTimeout(() => {
    console.log('异步操作1')
}, 1000)
setTimeout(() => {
    console.log('异步操作2')
}, 1000)
console.log('script end')
/* 执行结果
 * script start
 * script end
 * 异步操作1
 * 异步操作2
*/

(2)Macro 和 Micro

Macrotask 以及 Microtask 都属于异步任务,它们各自包括如下api:

  • macrotask(宏任务队列,也即任务队列):setTimeoutsetIntervalsetImmediate 、I/OUI rendering
  • microtask(微任务队列):process.nextTickPromisesObject.observeMutationObserver

其中 Macrotask 队列就是任务队列,在事件循环中,每次只会执行一个macrotask,而 Microtasks 则通常安排在当前正在执行的同步任务之后执行,并且需要与当前队列中所有 Microtask 都在同一周期内处理。并且每次主线程任务被清空时,先执行所有microtask,再去执行一个macrotask

(3)总结

  • JavaScript是单线程语言,通过事件循环和任务队列来实现异步操作。
  • 异步操作都会放到任务队列中去执行,执行完毕后,回调函数等待被调度
  • 主线程任务清空时,首先会执行所有的microTask,然后执行一个macroTask
  • setTimeout不会按照给定时间后执行(主线程任务阻塞时,会一直等待)
  • microTask异步也可能造成程序阻塞。(因为每次事件循环会执行所有microTask,死循环)

四、前端性能优化

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

你可能感兴趣的:(前端面试,js)