[TOC]

第 1 题:写 React / Vue 项目时为什么要在列表组件中写 key,其作用是什么?

key 是给每一个 vnode 的唯一 id,可以依靠 key,更准确,更快的拿到 oldVnode 中对应的 vnode 节点


第 2 题:[‘1’, ‘2’, ‘3’].map(parseInt) what & why ?


第一眼看到这个题目的时候,脑海跳出的答案是 [1, 2, 3],但是真正的答案是[1, NaN, NaN]。

首先让我们回顾一下,map 函数的第一个参数 callback。这个 callback 一共可以接收三个参数,其中第一个参数代表当前被处理的元素,而第二个参数代表该元素的索引。

arr.map(callback: (value: T, index: number, array: T[]) => U, thisArg?:any);

而 parseInt 则是用来解析字符串的,使字符串成为指定基数的整数。接收两个参数,第一个表示被处理的值(字符串),第二个表示为解析时的基数。 parseInt(string, radix)

了解这两个函数后,我们可以模拟一下运行情况

parseInt(‘1’, 0) //radix 为 0 时,且 string 参数不以“0x”和“0”开头时,按照 10 为基数处理。这个时候返回 1parseInt(‘2’, 1) //基数为 1(1 进制)


表示的数中,最大值小于 2,所以无法解析,返回 NaNparseInt(‘3’, 2) //基数为 2(2 进制)表示的数中,最大值小于 3,所以无法解析,返回 NaN


第 3 题:什么是防抖和节流?有什么区别?如何实现?


防抖——触发高频事件后 n 秒后函数只会执行一次,如果 n 秒内高频事件再次被触发,则重新计算时间;

function debounce(fn) {

let timeout = null

// 创建一个标记用来存放定时器的返回值

return function() {

clearTimeout(timeout)

// 每当用户输入的时候把前一个 setTimeout clear 掉 timeout = setTimeout(() => {

// 然后又创建一个新的 setTimeout, 这样就能保证输入字符后的 interval 间隔内如果还有字符输入的话,就不会执行 fn 函数 fn.apply(this, arguments)
}, 500)
}}function sayHi() {

console.log(‘防抖成功’)}var inp =

document.getElementById(‘inp’)inp.addEventListener(‘input’,

debounce(sayHi)) // 防抖

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

function throttle(fn) {

let canRun = true // 通过闭包保存一个标记 return function() {
if (!canRun) return

// 在函数开头判断标记是否为 true,不为 true 则 return canRun = false // 立即设置为 false setTimeout(() => {

// 将外部传入的函数的执行放在 setTimeout 中 fn.apply(this, arguments)

// 最后在 setTimeout 执行完毕后再把标记设置为 true(关键) 表示可以执行下一次循环了。当定时器没有执行的时候标记永远是 false,在开头被 return 掉

canRun = true

}, 500)

}}function sayHi(e) {

console.log(e.target.innerWidth,

e.target.innerHeight)}window.addEventListener(‘resize’,

throttle(sayHi))


第 4 题:介绍下 Set、Map、WeakSet 和 WeakMap 的区别?

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

WeakSet——成员都是对象;成员都是弱引用,可以被垃圾回收机制回收,可以

用来保存 DOM 节点,不容易造成内存泄漏;

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

WeakMap——只接受对象最为键名(null 除外),不接受其他类型的值作为键

名;键名是弱引用,键值可以是任意的,键名所指向的对象可以被垃圾回收,此时键名是无效的;不能遍历,方法有 get、set、has、delete。


第 5 题:介绍下深度优先遍历和广度优先遍历,如何实现?

深度优先遍历——是指从某个顶点出发,首先访问这个顶点,然后找出刚访问这个结点的第一个未被访问的邻结点,然后再以此邻结点为顶点,继续找它的下一个顶点进行访问。重复此步骤,直至所有结点都被访问完为止。

广度优先遍历——是从某个顶点出发,首先访问这个顶点,然后找出刚访问这个结点所有未被访问的邻结点,访问完后再访问这些结点中第一个邻结点的所有结点,重复此方法,直到所有结点都被访问完为止。

//1.深度优先遍历的递归写法 function deepTraversal(node) { let nodes = []
if (node != null) {

nodes.push[node]

let childrens = node.children

for (let i = 0;

i < childrens.length; i++) deepTraversal(childrens[i])

} return nodes}

//2.深度优先遍历的非递归写法 function deepTraversal(node) { let nodes = []

if (node != null) {

let stack = []

//同来存放将来要访问的节点

stack.push(node)

while (stack.length != 0) {

let item = stack.pop()

//正在访问的节点

nodes.push(item)
let childrens = item.children
for (
let i = childrens.length - 1;
i >= 0;
i—

//将现在访问点的节点的子节点存入 stack,供将来访问 )
stack.push(childrens[i])
}
}
return nodes}

//3.广度优先遍历的递归写法 function wideTraversal(node) { let nodes = [],

i = 0

if (node != null) {

nodes.push(node)

wideTraversal(node.nextElementSibling)

node = nodes[i++]
wideTraversal(node.firstElementChild)
}
return nodes}//4.广度优先遍历的非递归写法 function wideTraversal(node) {
let nodes = [],
i = 0 while (node != null) {
nodes.push(node)
node = nodes[i++]
let childrens = node.children
for (let i = 0;
i < childrens.length;
i++) {
nodes.push(childrens[i])
}
}
return nodes
}

第 6 题:请分别用深度优先思想和广度优先思想实现一个拷贝函数?

深复制 深度优先遍历

let DFSdeepClone = (obj, visitedArr = []) => {

let _obj = {}

if (isTypeOf(obj, ‘array’) || isTypeOf(obj, ‘object’)) { let index = visitedArr.indexOf(obj)

_obj = isTypeOf(obj, ‘array’) ? [] : {}

if (~index) { // 判断环状数据

_obj = visitedArr[index]

} else { visitedArr.push(obj) for (let item in obj) {

_obj[item] = DFSdeepClone(obj[item], visitedArr)

}

}

} else if (isTypeOf(obj, ‘function’)) {


_obj = eval(‘(‘ + obj.toString() + ‘)’);

} else {

_obj = obj

}
return _obj

}

广度优先遍历

let BFSdeepClone = (obj) => {

let origin = [obj],

copyObj = {},

copy = [copyObj]

// 去除环状数据 let visitedQueue = [],

visitedCopyQueue = [] while (origin.length > 0) {

let items = origin.shift(), _obj = copy.shift() visitedQueue.push(items)

if (isTypeOf(items, ‘object’) || isTypeOf(items, ‘array’)) { for (let item in items) {

let val = items[item]

if (isTypeOf(val, ‘object’)) {

let index = visitedQueue.indexOf(val) if (!~index) {

_obj[item] = {}

//下次 while 循环使用给空对象提供数据 origin.push(val)

// 推入引用对象 copy.push(_obj[item])

} else {

_obj[item] = visitedCopyQueue[index]

visitedQueue.push(_obj)

}

} else if (isTypeOf(val, ‘array’)) {

// 数组类型在这里创建了一个空数组

_obj[item] = [] origin.push(val) copy.push(_obj[item])

} else if (isTypeOf(val, ‘function’)) {

_obj[item] = eval(‘(‘ + val.toString() + ‘)’);

} else {

_obj[item] = val

}

}


// 将已经处理过的对象数据推入数组 给环状数据使用 visitedCopyQueue.push(_obj)

} else if (isTypeOf(items, ‘function’)) {

copyObj = eval(‘(‘ + items.toString() + ‘)’);

} else {

copyObj = obj

}

}

return copyObj

}

第 7 题:ES5/ES6 的继承除了写法以外还有什么区别?


1. ES5 的继承实质上是先创建子类的实例对象,然后再将父类的方法添加

到 this 上(Parent.apply(this)).

2. ES6 的继承机制完全不同,实质上是先创建父类的实例对象 this(所以必须先调用父类的 super()方法),然后再用子类的构造函数修改 this。

3. ES5 的继承时通过原型或构造函数机制来实现。
4. ES6 通过 class 关键字定义类,里面有构造方法,类之间通过 extends 关

键字实现继承。

5. 子类必须在 constructor 方法中调用 super 方法,否则新建实例报错。因

为子类没有自己的 this 对象,而是继承了父类的 this 对象,然后对其进行加工。

如果不调用 super 方法,子类得不到 this 对象。

6. 注意 super 关键字指代父类的实例,即父类的 this 对象。

7. 注意:在子类构造函数中,调用 super 后,才可使用 this 关键字,否则报错。



function 声明会提升,但不会初始化赋值。Foo 进入暂时性死区,类似于 let、 const 声明变量。
const bar = new Bar();
字节面试题-第一期 - 图1

// it’s ok

function Bar() {
字节面试题-第一期 - 图2

this.bar = 42;

}const foo = new Foo();
字节面试题-第一期 - 图3

// ReferenceError: Foo is not definedclass Foo { constructor() {
字节面试题-第一期 - 图4

this.foo = 42;
字节面试题-第一期 - 图5

}

}

class 声明内部会启用严格模式。

// 引用一个未声明的变量 function Bar() { baz = 42;
字节面试题-第一期 - 图6

// it’s ok}const bar = new Bar();


class Foo {
字节面试题-第一期 - 图7

constructor() {

fol = 42;
字节面试题-第一期 - 图8

// ReferenceError: fol is not defined
字节面试题-第一期 - 图9

}

}const foo = new Foo();

class 的所有方法(包括静态方法和实例方法)都是不可枚举的。

// 引用一个未声明的变量 function Bar() {
字节面试题-第一期 - 图10

this.bar = 42;}Bar.answer = function()
字节面试题-第一期 - 图11

{ return 42;

};

Bar.prototype.print = function() {
字节面试题-第一期 - 图12

console.log(this.bar);

};

const barKeys = Object.keys(Bar);
字节面试题-第一期 - 图13

// [‘answer’]const barProtoKeys = Object.keys(Bar.prototype); // [‘print’]class Foo {
字节面试题-第一期 - 图14

constructor() {
字节面试题-第一期 - 图15

this.foo = 42;
字节面试题-第一期 - 图16

}
字节面试题-第一期 - 图17

static answer() {
字节面试题-第一期 - 图18

return 42;
字节面试题-第一期 - 图19

}
字节面试题-第一期 - 图20

print() {
字节面试题-第一期 - 图21

console.log(this.foo);
字节面试题-第一期 - 图22

}}const fooKeys = Object.keys(Foo);
字节面试题-第一期 - 图23

// []const fooProtoKeys = Object.keys(Foo.prototype); // []
字节面试题-第一期 - 图24

class 的所有方法(包括静态方法和实例方法)都没有原型对象 prototype,所以也没有[[construct]],不能使用 new 来调用。

function Bar() {
字节面试题-第一期 - 图25

this.bar = 42;

}Bar.prototype.print = function() {
字节面试题-第一期 - 图26

console.log(this.bar);

};

const bar = new Bar();

const barPrint = new bar.print();
字节面试题-第一期 - 图27

// it’s okclass Foo {
字节面试题-第一期 - 图28

constructor() {

this.foo = 42;
字节面试题-第一期 - 图29

} print() {
字节面试题-第一期 - 图30

console.log(this.foo);
字节面试题-第一期 - 图31

}}const foo = new Foo();


const fooPrint = new foo.print();
字节面试题-第一期 - 图32

// TypeError: foo.print is not a constructor 必须使用 new 调用 class。

function Bar() {
字节面试题-第一期 - 图33

this.bar = 42;

}const bar = Bar();
字节面试题-第一期 - 图34

// it’s okclass Foo {
字节面试题-第一期 - 图35

constructor() {

this.foo = 42;
字节面试题-第一期 - 图36

}}const foo = Foo();
字节面试题-第一期 - 图37

// TypeError: Class constructor Foo cannot be invoked without ‘new’ class 内部无法重写类名。

function Bar() {
字节面试题-第一期 - 图38

Bar = ‘Baz’;

// it’s ok this.bar = 42;

}const bar = new Bar();

// Bar: ‘Baz’

// bar: Bar {bar: 42}
字节面试题-第一期 - 图39

class Foo {
字节面试题-第一期 - 图40

constructor() {

this.foo = 42;
字节面试题-第一期 - 图41

Foo = ‘Fol’;
字节面试题-第一期 - 图42

// TypeError: Assignment to constant variable }}const foo = new Foo();
字节面试题-第一期 - 图43

Foo = ‘Fol’;
字节面试题-第一期 - 图44

// it’s ok

第 8 题:setTimeout、Promise、Async/Await 的区别

https://blog.csdn.net/yun_hou/article/details/88697954

第 9 题:Async/Await 如何通过同步的方式实现异步

async 起什么作用——输出的是一个 Promise 对象

第 10 题:异步笔试题请写出下面代码的运行结果

async function async1() {
字节面试题-第一期 - 图45

console.log(‘async1 start’)
字节面试题-第一期 - 图46

await async2()
字节面试题-第一期 - 图47

console.log(‘async1 end’)}async function async2()
字节面试题-第一期 - 图48

{
字节面试题-第一期 - 图49

console.log(‘async2’)}console.log(‘script start’)setTimeout(function() {
字节面试题-第一期 - 图50

console.log(‘setTimeout’)},

0) async1()new Promise(function(resolve)

字节面试题-第一期 - 图51
1) {
字节面试题-第一期 - 图52

console.log(‘promise1’)
字节面试题-第一期 - 图53

resolve()}).then(function()
字节面试题-第一期 - 图54

{
字节面试题-第一期 - 图55

console.log(‘promise2’)})console.log(‘script end’)

//输出

//script start

//async1 start

//async2

//promise1

//script end

//async1 end

//promise2

//setTimeout

第 11 题:算法手写题

已知如下数组,编写一个程序将数组扁平化去并除其中重复部分数据,最终得到一个升序且不重复的数组

var arr = [ [1, 2, 2], [3, 4, 5, 5], [6, 7, 8, 9, [11, 12, [12, 13, [14] ] ] ], 10]; 答:使用 Set 方法去重,flat(Infinity)扁平化
字节面试题-第一期 - 图56

Array.from(new Set(arr.flat(Infinity))).sort((a,b)=>{ return a-b})//[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14]

第 12 题:JS 异步解决方案的发展历程以及优缺点。

1、回调函数(callback)

优点:解决了同步的问题(只要有一个任务耗时很长,后面的任务都必须排队等着,会拖延整个程序的执行。)

缺点:回调地狱,不能用 try catch 捕获错误,不能 return

2、Promise

优点:解决了回调地狱的问题

缺点:无法取消 Promise ,错误需要通过回调函数来捕获

3、Generator

特点:可以控制函数的执行,可以配合 co 函数库使用

4、Async/await

优点:代码清晰,不用像 Promise 写一大堆 then 链,处理了回调地狱的问题缺点:await 将异步代码改造成同步代码,如果多个异步操作没有依赖性而使用 await 会导致性能上的降低。

第 13 题:Promise 构造函数是同步执行还是异步执行,那么 then 方法呢?


const promise = new Promise((resolve, reject) => { console.log(1)
字节面试题-第一期 - 图57

resolve()

字节面试题-第一期 - 图58
console.log(2)})promise.then(() => {
字节面试题-第一期 - 图59

console.log(3)})console.log(4)

执行结果是:1243,promise 构造函数是同步执行的,then 方法是异步执行的

第 14 题:情人节福利题,如何实现一个 new 第 15 题:简单讲解一下 http2 的多路复用

HTTP2 采用二进制格式传输,取代了 HTTP1.x 的文本格式,二进制格式解析更高效。

多路复用代替了 HTTP1.x 的序列和阻塞机制,所有的相同域名请求都通过同一个 TCP 连接并发完成。

在 HTTP1.x 中,并发多个请求需要多个 TCP 连接,浏览器为了控制资源会有 6-8 个 TCP 连接都限制。

HTTP2 中

同域名下所有通信都在单个连接上完成,消除了因多个 TCP 连接而带来的延时和内存消耗。

单个连接上可以并行交错的请求和响应,之间互不干扰


第 16 题:谈谈你对 TCP 三次握手和四次挥手的理

字节面试题-第一期 - 图60

字节面试题-第一期 - 图61





第 17 题:A、B 机器正常连接后,B 机器突然重启,问 A 此时处于 TCP 什么状态

如果 A 与 B 建立了正常连接后,从未相互发过数据,这个时候 B 突然机器重启,问 A 此时处于 TCP 什么状态?如何消除服务器程序中的这个状态?(超
纲题,了解即可)

因为 B 会在重启之后进入 tcp 状态机的 listen 状态,只要当 a 重新发送一个数据包(无论是 syn 包或者是应用数据),b 端应该会主动发送一个带 rst 位的重置包来进行连接重置,所以 a 应该在 syn_sent 状态

第 18 题:React 中 setState 什么时候是同步的,什么时候是异步的?

1、由 React 控制的事件处理程序,以及生命周期函数调用 setState 不会同步更

新 state 。

2、React 控制之外的事件中调用 setState 是同步更新的。比如原生 js 绑定的事件,setTimeout/setInterval 等。

第 19 题:React setState 笔试题,下面的代码输出什么?

class Example extends React.Component { constructor() {
字节面试题-第一期 - 图62

super()
字节面试题-第一期 - 图63

this.state = {
字节面试题-第一期 - 图64

val: 0
字节面试题-第一期 - 图65

}
字节面试题-第一期 - 图66

}

componentDidMount() {
字节面试题-第一期 - 图67

this.setState({ val: this.state.val + 1 })

console.log(this.state.val)
字节面试题-第一期 - 图68

// 第 1 次 log
字节面试题-第一期 - 图69

this.setState({ val: this.state.val + 1 })
字节面试题-第一期 - 图70

console.log(this.state.val)

// 第 2 次 log
字节面试题-第一期 - 图71

setTimeout(() => {
字节面试题-第一期 - 图72

this.setState({ val: this.state.val + 1 })
字节面试题-第一期 - 图73

console.log(this.state.val)
字节面试题-第一期 - 图74

// 第 3 次 log
字节面试题-第一期 - 图75

this.setState({ val: this.state.val + 1 })
字节面试题-第一期 - 图76

console.log(this.state.val)
字节面试题-第一期 - 图77

// 第 4 次 log

字节面试题-第一期 - 图78
}, 0)
字节面试题-第一期 - 图79

}
字节面试题-第一期 - 图80

render() {
字节面试题-第一期 - 图81

return null
字节面试题-第一期 - 图82

}

}

答:

0,0,1,2

第 20 题:介绍下 npm 模块安装机制,为什么输入 npm install 就可以自动安装对应的模块?1. npm 模块安装机制:


发出 npm install 命令 1 查询 node_modules 目录之中是否已经存在指定模



若存在,不再重新安装

若不存在

npm 向 registry 查询模块压缩包的网址

下载压缩包,存放在根目录下的.npm 目录里

解压压缩包到当前项目的 node_modules 目录


第 21 题:有以下 3 个判断数组的方法,请分别介绍它们之间的区别和优劣

Object.prototype.toString.call() 、 instanceof 以及 Array.isArray()


Object.prototype.toString.call()

每一个继承 Object 的对象都有 toString 方法,如果 toString 方法没有重写的话,会返回 [Object type],其中 type 为对象的类型。但当除了 Object 类型的

对象外,其他类型直接使用 toString 方法时,会直接返回都是内容的字符串,所以我们需要使用 call 或者 apply 方法来改变 toString 方法的执行上下文。

const an = [‘Hello’,’An’];an.toString();
字节面试题-第一期 - 图83

// “Hello,An”Object.prototype.toString.call(an); // “[object Array]”
字节面试题-第一期 - 图84

这种方法对于所有基本的数据类型都能进行判断,即使是 null 和 undefined 。

Object.prototype.toString.call(‘An’)
字节面试题-第一期 - 图85

// “[object String]”Object.prototype.toString.call(1)
字节面试题-第一期 - 图86

// “[object Number]”Object.prototype.toString.call(Symbol(1))
字节面试题-第一期 - 图87

// “[object Symbol]”Object.prototype.toString.call(null)

字节面试题-第一期 - 图88
// “[object Null]”Object.prototype.toString.call(undefined)
字节面试题-第一期 - 图89

// “[object Undefined]”Object.prototype.toString.call(function(){}) // “[object Function]”Object.prototype.toString.call({name: ‘An’}) // “[object Object]”
字节面试题-第一期 - 图90

Object.prototype.toString.call() 常用于判断浏览器内置对象时。


instanceof

instanceof 的内部机制是通过判断对象的原型链中是不是能找到类型的prototype。

使用 instanceof 判断一个对象是否为数组,instanceof 会判断这个对象的原型链上是否会找到对应的 Array 的原型,找到返回 true,否则返回 false。
[] instanceof Array; // true

但 instanceof 只能用来判断对象类型,原始类型不可以。并且所有对象类型 instanceof Object 都是 true。
[] instanceof Object; // true




Array.isArray()

功能:用来判断对象是否为数组

instanceof 与 isArray

当检测 Array 实例时,Array.isArray 优于 instanceof ,因为 Array.isArray 可以检测出 iframes

var iframe = document.createElement(‘iframe’); document.body.appendChild(iframe);xArray = window.frames[window.frames.length-1].Array; var arr = new xArray(1,2,3);
字节面试题-第一期 - 图91字节面试题-第一期 - 图92

// [1,2,3]// Correctly checking for ArrayArray.isArray(arr); // trueObject.prototype.toString.call(arr);
字节面试题-第一期 - 图93

// true
字节面试题-第一期 - 图94

// Considered harmful, because doesn’t work though iframesarr instanceof Array;
字节面试题-第一期 - 图95

// false

Array.isArray() 与 Object.prototype.toString.call()

Array.isArray()是 ES5 新增的方法,当不存在 Array.isArray() ,可以用 Object.prototype.toString.call() 实现。
if (!Array.isArray) { Array.isArray = function(arg) {

return Object.prototype.toString.call(arg) === ‘[object Array]’; };
字节面试题-第一期 - 图96

}

字节面试题-第一期 - 图97







第 22 题:介绍下重绘和回流(Repaint & Reflow),以及如何进行优化

1. 浏览器渲染机制

浏览器采用流式布局模型(Flow Based Layout)

浏览器会把 HTML 解析成 DOM,把 CSS 解析成 CSSOM,DOM 和 CSSOM 合并就产生了渲染树(Render Tree)。
有了 RenderTree,我们就知道了所有节点的样式,然后计算他们在页面上的大小和位置,最后把节点绘制到页面上。

由于浏览器使用流式布局,对 Render Tree 的计算通常只需要遍历一次就可以完成,但 table 及其内部元素除外,他们可能需要多次计算,通常要花 3 倍于同等元素的时间,这也是为什么要避免使用 table 布局的原因之一。

2. 重绘

由于节点的几何属性发生改变或者由于样式发生改变而不会影响布局的,称为重绘,例如 outline, visibility, color、background-color 等,重绘的代价是高昂的,因为浏览器必须验证 DOM 树上其他节点元素的可见性。

3. 回流

回流是布局或者几何属性需要改变就称为回流。回流是影响浏览器性能的关键

因素,因为其变化涉及到部分页面(或是整个页面)的布局更新。一个元素的回流可能会导致了其所有子元素以及 DOM 中紧随其后的节点、祖先节点元素的随后的回流。



我的组件


字节面试题-第一期 - 图98

错误:错误的描述…


字节面试题-第一期 - 图99

错误纠正

字节面试题-第一期 - 图100


    字节面试题-第一期 - 图101

  1. 第一步

  2. 字节面试题-第一期 - 图102

  3. 第二步

  4. 字节面试题-第一期 - 图103



在上面的 HTML 片段中,对该段落(

标签)回流将会引发强烈的回流,因为它是一个子节点。这也导致了祖先的回流(div.error 和 body – 视浏览器而定)。

此外,

    也会有简单的回流,因为其在 DOM 中在回流元素之后。大部分的回流将导致页面的重新渲染。

    回流必定会发生重绘,重绘不一定会引发回流。

    4. 浏览器优化

    现代浏览器大多都是通过队列机制来批量更新布局,浏览器会把修改操作放在队列中,至少一个浏览器刷新(即 16.6ms)才会清空队列,但当你获取布局信息的时候,队列中可能有会影响这些属性或方法返回值的操作,即使没有,浏览器也会强制清空队列,触发回流与重绘来确保返回正确的值。

    主要包括以下属性或方法:

    1、offsetTop、offsetLeft、offsetWidth、offsetHeight

    2、scrollTop、scrollLeft、scrollWidth、scrollHeight

    3、clientTop、clientLeft、clientWidth、clientHeight

    4、width、height

    5、getComputedStyle()

    6、getBoundingClientRect()

    所以,我们应该避免频繁的使用上述的属性,他们都会强制渲染刷新队列。

    5. 减少重绘与回流

    CSS

    1、使用 transform 替代 top

    2、使用 visibility 替换 display: none ,因为前者只会引起重绘,后者会引发回流(改变了布局

    3、避免使用 table 布局,可能很小的一个小改动会造成整个 table 的重新布局。

    4、尽可能在 DOM 树的最末端改变 class,回流是不可避免的,但可以减少其影响。尽可能在 DOM 树的最末端改变 class,可以限制了回流的范围,使其影响尽可能少的节点。

    5、避免设置多层内联样式,CSS 选择符从右往左匹配查找,避免节点层级过多。



    对于第一种设置样式的方式来说,浏览器只需要找到页面中所有的 span 标签然后设置颜色,但是对于第二种设置样式的方式来说,浏览器首先需要找到所有的 span 标签,然后找到 span 标签上的 a 标签,最后再去找到 div 标签,然后给符合这种条件的 span 标签设置颜色,这样的递归过程就很复杂。所以我们应该尽可能的避免写过于具体的 CSS 选择器,然后对于 HTML 来说也尽
    量少的添加无意义标签,保证层级扁平。

    将动画效果应用到 position 属性为 absolute 或 fixed 的元素上,避免影响其他元

    素的布局,这样只是一个重绘,而不是回流,同时,控制动画速度可以选择requestAnimationFrame,详见探讨 requestAnimationFrame。避免使用 CSS 表达式,可能会引发回流。


    将频繁重绘或者回流的节点设置为图层,图层能够阻止该节点的渲染行为影响别的节点,例如 will-change、video、iframe 等标签,浏览器会自动将该节点变
    为图层。

    CSS3 硬件加速(GPU 加速),使用 css3 硬件加速,可以让 transform、opacity、

    filters 这些动画不会引起回流重绘 。但是对于动画的其它属性,比如 background-color 这些,还是会引起回流重绘的,不过它还是可以提升这些动画
    的性能。

    JavaScript

    1、避免频繁操作样式,最好一次性重写 style 属性,或者将样式列表定义为 class 并一次性更改 class 属性。

    2、避免频繁操作 DOM,创建一个 documentFragment,在它上面应用所有 DOM 操作,最后再把它添加到文档中。

    3、避免频繁读取会引发回流/重绘的属性,如果确实需要多次使用,就用一个变量缓存起来。

    4、对具有复杂动画的元素使用绝对定位,使它脱离文档流,否则会引起父元素及后续元素频繁回流。
    字节面试题-第一期 - 图105






    第 23 题:介绍下观察者模式和订阅-发布模式的区别,各自适用于什么场景

    观察者模式中主体和观察者是互相感知的,发布-订阅模式是借助第三方来实现调度的,发布者和订阅者之间互不感知

    字节面试题-第一期 - 图106

    字节面试题-第一期 - 图107





    第 24 题:聊聊 Redux 和 Vuex 的设计思想

    不管是 Vue,还是 React,都需要管理状态(state),比如组件之间都有共享状态的需要。什么是共享状态?比如一个组件需要使用另一个组件的状态,或者一个组件需要改变另一个组件的状态,都是共享状态。

    父子组件之间,兄弟组件之间共享状态,往往需要写很多没有必要的代码,比如把状态提升到父组件里,或者给兄弟组件写一个父组件,听听就觉得挺啰嗦。如果不对状态进行有效的管理,状态在什么时候,由于什么原因,如何变化就会不受控制,就很难跟踪和测试了。如果没有经历过这方面的困扰,可以简单理解为会搞得很乱就对了。

    在软件开发里,有些通用的思想,比如隔离变化,约定优于配置等,隔离变化就是说做好抽象,把一些容易变化的地方找到共性,隔离出来,不要去影响其他的代码。约定优于配置就是很多东西我们不一定要写一大堆的配置,比如我们几个人约定,view 文件夹里只能放视图,不能放过滤器,过滤器必须放到 filter 文件夹里,那这就是一种约定,约定好之后,我们就不用写一大堆配置文件了,我们要找所有的视图,直接从 view 文件夹里找就行。

    根据这些思想,对于状态管理的解决思路就是:把组件之间需要共享的状态抽取出来,遵循特定的约定,统一来管理,让状态的变化可以预测。根据这个思路,产生了很多的模式和库,我们来挨个聊聊。
    字节面试题-第一期 - 图108






    第 25 题:说说浏览器和 Node 事件循环的区别

    其中一个主要的区别在于浏览器的 event loop 和 nodejs 的 event loop 在处理异步事件的顺序是不同的,nodejs 中有 micro event;其中 Promise 属于 micro event

    该异步事件的处理顺序就和浏览器不同.nodejs V11.0 以上 这两者之间的顺序就相同了.

    function test () {
    字节面试题-第一期 - 图109

    console.log(‘start’)

    setTimeout(() => {
    字节面试题-第一期 - 图110

    console.log(‘children2’)
    字节面试题-第一期 - 图111

    Promise.resolve().then(() =>

    {console.log(‘children2-1’)})
    字节面试题-第一期 - 图112

    }, 0)
    字节面试题-第一期 - 图113

    setTimeout(() => {
    字节面试题-第一期 - 图114

    console.log(‘children3’)
    字节面试题-第一期 - 图115

    Promise.resolve().then(() =>

    {console.log(‘children3-1’)})

    字节面试题-第一期 - 图116
    }, 0)
    字节面试题-第一期 - 图117

    Promise.resolve().then(() =>

    {console.log(‘children1’)})
    字节面试题-第一期 - 图118

    console.log(‘end’)

    }test()// 以上代码在 node11 以下版本的执行结果(先执行所有的宏任务,再执行微任务)// start// end// children1// children2// children3// children2-1// children3-1// 以上代码在 node11 及浏览器的执行结果(顺序执行宏任务和微任务)// start// end// children1// children2// children2-1// children3// children3-1
    字节面试题-第一期 - 图119字节面试题-第一期 - 图120字节面试题-第一期 - 图121






    第 26 题:介绍模块化发展历程

    可从 IIFE、AMD、CMD、CommonJS、UMD、webpack(require.ensure)、ES Module、
    字节面试题-第一期 - 图122






    第 80 题:介绍下 Promise.all 使用、原理实现及错误处理

    const p = Promise.all([p1, p2, p3]);

    Promise.all 方法接受一个数组作为参数,p1、p2、p3 都是 Promise 实例,如果不是,就会先调用下面讲到的 Promise.resolve 方法,将参数转为 Promise 实例,再进一步处理。(Promise.all 方法的参数可以不是数组,但必须具有 Iterator 接口,且返回的每个成员都是 Promise 实例。)

    第 81 题:打印出 1 - 10000 之间的所有对称数

    例如:121、1331 等

    […Array(10000).keys()].filter((x) => { return x.toString().length > 1 && x ===
    字节面试题-第一期 - 图123

    Number(x.toString().split(‘’).reverse().join(‘’))
    字节面试题-第一期 - 图124

    })
    字节面试题-第一期 - 图125






    第 82 题:周一算法题之「移动零」


    给定一个数组 nums,编写一个函数将所有 0 移动到数组的末尾,同时保持非零元素的相对顺序。

    示例:

    输入: [0,1,0,3,12] 输出: [1,3,12,0,0]

    复制代码说明: 必须在原数组上操作,不能拷贝额外的数组。 尽量减少操作次



    答:

    function zeroMove(array) {
    字节面试题-第一期 - 图126

    let len = array.length;
    字节面试题-第一期 - 图127

    let j = 0;
    字节面试题-第一期 - 图128

    for(let
    字节面试题-第一期 - 图129

    i=0;i字节面试题-第一期 - 图130

    if(array[i]===0){
    字节面试题-第一期 - 图131

    array.push(0);
    字节面试题-第一期 - 图132

    array.splice(i,1);
    字节面试题-第一期 - 图133

    i —;

    j ++;
    字节面试题-第一期 - 图134

    }
    字节面试题-第一期 - 图135

    }
    字节面试题-第一期 - 图136

    return array;
    字节面试题-第一期 - 图137

    }






    第 83 题:var、let 和 const 区别的实现原理是什么

    三者的区别:

    var 和 let 用以声明变量,const 用于声明只读的常量;

    var 和 let 用以声明变量,const 用于声明只读的常量;

    var 声明的变量,不存在块级作用域,在全局范围内都有效,let 和 const

    声明的,只在它所在的代码块内有效;

    let 和 const 不存在像 var 那样的 “变量提升” 现象,所以 var 定义变

    量可以先使用,后声明,而 let 和 const 只可先声明,后使用;

    let 声明的变量存在暂时性死区,即只要块级作用域中存在 let,那么它

    所声明的变量就绑定了这个区域,不再受外部的影响。

    let 不允许在相同作用域内,重复声明同一个变量;

    const 在声明时必须初始化赋值,一旦声明,其声明的值就不允许改变,更不允许重复声明;如 const 声明了一个复合类型的常量,其存储的是一个引用地址,不允许改变的是这个地址,而对象本身是可变的。

    变量与内存之间的关系,主要由三个部分组成:

    变量名

    内存地址

    内存空间

    JS 引擎在读取变量时,先找到变量绑定的内存地址,然后找到地址所指向的内存空间,最后读取其中的内容。当变量改变时,JS 引擎不会用新值覆盖之前旧值的内存空间(虽然从写代码的角度来看,确实像是被覆盖掉了),而是重新分配一个新的内存空间来存储新值,并将新的内存地址与变量进行绑定,JS 引擎会在合适的时机进行 GC,回收旧的内存空间。

    const 定义变量(常量)后,变量名与内存地址之间建立了一种不可变的绑定关系,阻隔变量地址被改变,当 const 定义的变量进行重新赋值时,根据前面的论述,JS 引擎会尝试重新分配新的内存空间,所以会被拒绝,便会抛出异常。






    第 84 题:请实现一个 add 函数,满足以下功能。

    add(1);

    // 1add(1)(2);

    // 3add(1)(2)(3);

    // 6add(1)(2, 3);

    // 6add(1, 2)(3);

    // 6add(1, 2, 3);

    // 6

    答:

    实现 1:
    function currying(fn, length) {



    args.length >= length // 注释 3

    ? fn.apply(this, args) // 注释 4

    : currying(fn.bind(this, …args), length - args.length) // 注释

    5 }}

    实现 2:

    const currying = fn =>

    judge = (…args) =>

    args.length >= fn.length

    ? fn(…args)

    : (…arg) => judge(…args, …arg)

    其中注释部分

    注释 1:第一次调用获取函数 fn 参数的长度,后续调用获取 fn 剩余参数的

    长度

    注释 2:currying 包裹之后返回一个新函数,接收参数为 …args

    注释 3:新函数接收的参数长度是否大于等于 fn 剩余参数需要接收的长度

    注释 4:满足要求,执行 fn 函数,传入新函数的参数

    注释 5:不满足要求,递归 currying 函数,新的 fn 为 bind 返回的新函数(bind绑定了 …args 参数,未执行),新的 length 为 fn 剩余参数的长度






    第 85 题:react-router 里的 标签和 标签有什么区别

    如何禁掉 标签默认事件,禁掉之后如何实现跳转。

    答:

    Link 点击事件 handleClick 部分源码:

    if (_this.props.onClick) _this.props.onClick(event); if (

    !event.defaultPrevented && // onClick prevented default event.button === 0 && // ignore everything but left

    clicks !_this.props.target && // let browser handle “target=_blank” etc. !isModifiedEvent(event) // ignore clicks with modifier keys

    ) {

    event.preventDefault();

    var history = _this.context.router.history; var _this$props = _this.props,

    replace = _this$props.replace,

    to = _this$props.to;

    if (replace) {

    history.replace(to);

    } else {

    history.push(to);

    }

    }

    Link 做了 3 件事情:

    有 onclick 那就执行 onclick

    click 的时候阻止 a 标签默认事件(这样子点击
    123

    不会跳转和刷新页面)

    再取得跳转 href(即是 to),用 history(前端路由两种方式之一,history

    & hash)跳转,此时只是链接变了,并没有刷新页面







    第 86 题:周一算法题之「两数之和」


    给定一个整数数组和一个目标值,找出数组中和为目标值的两个数。

    你可以假设每个输入只对应一种答案,且同样的元素不能被重复利用。


    示例:

    给定 nums = [2, 7, 11, 15], target = 9 因为 nums[0] + nums[1] = 2 + 7 = 9 所以返

    回 [0, 1]

    答:

    function anwser (arr, target) {

    let map = {} for (let i = 0; i < arr.length; i++) { map[arr[i]] = i }

    for (let i = 0; i < arr.length; i++) { var d = target - arr[i]

    if (map[d]) { return [i, map[d]]

    }

    }

    return new Error(‘404 not found’)}






    第 87 题:在输入框中如何判断输入的是一个正确的网址。

    function isUrl(url) {

    const a = document.createElement(“a”); a.href = url;

    return (

    [

    /^(http|https):$/.test(a.protocol),

    a.host,

    a.pathname !== url,

    a.pathname !== /${url}

    ].find(x => !x) === undefined

    );

    }






    第 88 题:实现 convert 方法,把原始 list 转换成树形结构,要求尽可能降低时间复杂度

    以下数据结构中,id 代表部门编号,name 是部门名称,parentId 是父部门编号,为 0 代表一级部门,现在要求实现一个 convert 方法,把原始 list 转换成树形结构,parentId 为多少就挂载在该 id 的属性 children 数组下,结构如
    下:


    // 原始 list 如下 let list =[

    {id:1,name:’部门 A’,parentId:0},

    {id:2,name:’部门 B’,parentId:0},

    {id:3,name:’部门 C’,parentId:1},

    {id:4,name:’部门 D’,parentId:1},

    {id:5,name:’部门 E’,parentId:2},

    {id:6,name:’部门 F’,parentId:3},

    {id:7,name:’部门 G’,parentId:2},

    {id:8,name:’部门 H’,parentId:4}];

    const result = convert(list, …);// 转换后的结果如下 let result =

    [

    {

    id: 1,

    name: ‘部门 A’,

    parentId: 0,

    children: [

    {

    id: 3,

    name: ‘部门 C’,

    parentId: 1,

    children: [

    {

    id: 6,

    name: ‘部门 F’,

    parentId: 3

    }, {

    id: 16,

    name: ‘部门 L’,

    parentId: 3

    }

    ]

    },

    {

    id: 4,

    name: ‘部门 D’,

    parentId: 1,

    children: [

    {

    id: 8,

    name: ‘部门 H’,

    parentId: 4

    }

    ]

    }

    ]

    },

    ···];

    答:

    function convert(list) {

    const res = []

    const map = list.reduce((res, v) => (res[v.id] = v, res), {}) for (const item of list) {

    if (item.parentId === 0) {

    res.push(item)

    Continue

    }

    if (item.parentId in map) {

    const parent = map[item.parentId] parent.children = parent.children || [] parent.children.push(item)

    }

    }

    return res}






    第 89 题:设计并实现 Promise.race()

    Promise._race = promises => new Promise((resolve, reject) => { promises.forEach(promise => {

    promise.then(resolve, reject) })})Promise.myrace = function(iterator) { return new Promise ((resolve,reject) => {

    try {

    let it = iteratorSymbol.iterator;

    while(true) {

    let res = it.next();

    console.log(res);

    if(res.done) break;

    if(res.value instanceof Promise){

    res.value.then(resolve,reject);

    } else{

    resolve(res.value)

    }

    }

    } catch (error) {

    reject(error)

    }


    })

    }






    第 90 题:实现模糊搜索结果的关键词高亮显示

    <!DOCTYPE html>














      第 91 题:介绍下 HTTPS 中间人攻击

      https 协议由 http + ssl 协议构成,具体的链接过程可参考 SSL 或 TLS 握手的概述

      中间人攻击过程如下:

      1. 服务器向客户端发送公钥。

      2. 攻击者截获公钥,保留在自己手上。

      3. 然后攻击者自己生成一个【伪造的】公钥,发给客户端。

      4. 客户端收到伪造的公钥后,生成加密 hash 值发给服务器。

      5. 攻击者获得加密 hash 值,用自己的私钥解密获得真秘钥。

      6. 同时生成假的加密 hash 值,发给服务器。

      7. 服务器用私钥解密获得假秘钥。

      8. 服务器用加秘钥加密传输信息

      防范方法:

      服务端在发送浏览器的公钥中加入 CA 证书,浏览器可以验证 CA 证书的有效性






      第 92 题:已知数据格式,实现一个函数 fn 找出链条中所有的父级 idconst value = ‘112’

      const fn = (value) => {…}fn(value) // 输出 [1, 11, 112] 答:const data = [

      {

      id: “1”,

      name: “test1”,

      children: [

      {

      id: “11”,

      name: “test11”,

      children: [

      {

      id: “111”,

      name: “test111”

      },

      {

      id: “112”,

      name: “test112”

      }

      ]

      },

      {

      id: “12”,

      name: “test12”,

      children: [

      {

      id: “121”,

      name: “test121”

      },

      {

      id: “122”,

      name:

      “test122”

      }

      ]

      }

      ]

      }


      ];

      const find = value => {

      let result = [];

      let findArr = data;

      let skey = “”;

      for (let i = 0, l = value.length; i < l; i++) { skey += value[i];

      let item = findArr.find(item => {

      return item.id == skey;

      });

      if (!item) {

      return [];

      }

      result.push(item.id);

      if (item.children) {

      findArr = item.children;

      } else {

      if (i < l - 1) return [];

      return result;

      }

      }

      };

      //调用看结果

      function testFun() {

      console.log(“1,11,111:”, find(“111”)); console.log(“1,11,112:”, find(“112”)); console.log(“1,12,121:”, find(“121”));

      console.log(“1,12,122:”, find(“122”)); console.log(“[]:”, find(“113”)); console.log(“[]:”, find(“1114”));

      }






      第 93 题:给定两个大小为 m 和 n 的有序数组 nums1 和nums2。请找出这两个有序数组的中位数。要求算法的时间复杂度为 O(log(m+n))。


      示例 1:

      nums1 = [1, 3] nums2 = [2]

      中位数是 2.0

      示例 2:

      nums1 = [1, 2] nums2 = [3, 4]

      中位数是(2 + 3) / 2 = 2.5

      答:

      const findMedianSortedArrays = function( nums1: number[],

      nums2: number[]

      ) {

      const lenN1 = nums1.length;

      const lenN2 = nums2.length;

      const median = Math.ceil((lenN1 + lenN2 + 1) / 2); const isOddLen = (lenN1 + lenN2) % 2 === 0; const result = new Array(median); let i = 0; // pointer for nums1

      let j = 0; // pointer for nums2

      for (let k = 0; k < median; k++) {

      if (i < lenN1 && j < lenN2) {

      // tslint:disable-next-line:prefer-conditional-expression if (nums1[i] < nums2[j]) {

      result[i + j] = nums1[i++];

      } else {

      result[i + j] = nums2[j++];

      }

      } else if (i < lenN1) {

      result[i + j] = nums1[i++];

      } else if (j < lenN2) {

      result[i + j] = nums2[j++];

      }

      }

      if (isOddLen) {

      return (result[median - 1] + result[median - 2]) / 2; } else {

      return result[median - 1];

      }

      };






      第 94 题:vue 在 v-for 时给每项元素绑定事件需要用事件代理吗?为什么?


      在 vue 中 vue 做了处理

      如果我们自己在非 vue 中需要对很多元素添加事件的时候,可以通过将事件添加到它们的父节点而将事件委托给父节点来触发处理函数






      第 95 题:模拟实现一个深拷贝,并考虑对象相互引用以及 Symbol 拷贝的情况

      一个不考虑其他数据类型的公共方法,基本满足大部分场景

      function deepCopy(target, cache = new Set()) {

      if (typeof target !== ‘object’ || cache.has(target)) { return target

      }

      if (Array.isArray(target)) {

      target.map(t => {

      cache.add(t)

      return t

      })

      } else {

      return

      […Object.keys(target), …Object.getOwnPropertySymbols(target)].red uce((res, key) => {

      cache.add(target[key])

      res[key] = deepCopy(target[key], cache)

      return res

      }, target.constructor !== Object ? Object.create(target.constructor.prototype) : {})

      }

      }

      主要问题是

      symbol 作为 key,不会被遍历到,所以 stringify 和 parse 是不行的

      有环引用,stringify 和 parse 也会报错

      我们另外用 getOwnPropertySymbols 可以获取 symbol key 可以解决问题 1,用集合记忆曾经遍历过的对象可以解决问题 2。当然,还有很多数据类型要独立去拷贝。比如拷贝一个 RegExp,lodash 是最全的数据类型拷贝了,有空可以研究一下

      另外,如果不考虑用 symbol 做 key,还有两种黑科技深拷贝,可以解决环引用的问题,比 stringify 和 parse 优雅强一些

      function deepCopyByHistory(target) {

      const prev = history.state

      history.replaceState(target, document.title)

      const res = history.state

      history.replaceState(prev, document.title)

      return res

      }

      async function deepCopyByMessageChannel(target) { return new Promise(resolve => {

      const channel = new MessageChannel()

      channel.port2.onmessage = ev => resolve(ev.data)

      channel.port1.postMessage(target)

      }).then(data => data)}

      无论哪种方法,它们都有一个共性:失去了继承关系,所以剩下的需要我们手动补上去了,故有 Object.create(target.constructor.prototype)的操作






      第 96 题:介绍下前端加密的常见场景和方法

      首先,加密的目的,简而言之就是将明文转换为密文、甚至转换为其他的东西,用来隐藏明文内容本身,防止其他人直接获取到敏感明文信息、或者提高其他人获取到明文信息的难度。通常我们提到加密会想到密码加密、HTTPS 等关键词,这里从场景和方法分别提一些我的个人见解。

      场景-密码传输

      前端密码传输过程中如果不加密,在日志中就可以拿到用户的明文密码,对用户安全不太负责。这种加密其实相对比较简单,可以使用 PlanA-前端加密、后端解密后计算密码字符串的 MD5/MD6 存入数据库;也可以 PlanB-直接前端使

      用一种稳定算法加密成唯一值、后端直接将加密结果进行 MD5/MD6,全程密码明文不出现在程序中。

      PlanA 使用 Base64 / Unicode+1 等方式加密成非明文,后端解开之后再存它的 MD5/MD6 。

      PlanB 直接使用 MD5/MD6 之类的方式取 Hash ,让后端存 Hash 的 Hash 。

      场景-数据包加密

      应该大家有遇到过:打开一个正经网站,网站底下蹦出个不正经广告——比如

      X 通的流量浮层,X 信的插入式广告……(我没有针对谁)但是这几年,我们会发现这种广告逐渐变少了,其原因就是大家都开始采用 HTTPS 了。被人插入这种广告的方法其实很好理解:你的网页数据包被抓取->在数据包到达你手机之前被篡改->你得到了带网页广告的数据包->渲染到你手机屏幕。而 HTTPS 进行了包加密,就解决了这个问题。严格来说我认为从手段上来看,它不算是一

      种前端加密场景;但是从解决问题的角度来看,这确实是前端需要知道的事情。 Plan 全面采用 HTTPS
      场景-展示成果加密


      经常有人开发网页爬虫爬取大家辛辛苦苦一点一点发布的数据成果,有些会影响你的竞争力,有些会降低你的知名度,甚至有些出于恶意爬取你的公开数据后进行全量公开……比如有些食谱网站被爬掉所有食谱,站点被克隆;有些求职

      网站被爬掉所有职位,被拿去卖信息;甚至有些小说漫画网站赖以生存的内容也很容易被爬取。

      Plan 将文本内容进行展示层加密,利用字体的引用特点,把拿给爬虫的数据变成“乱码”。举个栗子:正常来讲,当我们拥有一串数字“12345”并将其放在网站页面上的时候,其实网站页面上显示的并不是简单的数字,而是数字对应的字体的“12345”。这时我们打乱一下字体中图形和字码的对应关系,比如我们搞成这样:

      图形:12345 字码:23154

      这时,如果你想让用户看到“12345”,你在页面中渲染的数字就应该是“23154”。

      这种手段也可以算作一种加密。






      第 97 题:React 和 Vue 的 diff 时间复杂度从 O(n^3) 优化到 O(n) ,那么 O(n^3) 和 O(n) 是如何计算出来的?


      三种优化来降低复杂度:

      1. 如果父节点不同,放弃对子节点的比较,直接删除旧节点然后添加新的节点重新渲染;

      2. 如果子节点有变化,Virtual DOM 不会计算变化的是什么,而是重新渲染,

      3. 通过唯一的 key 策略







      第 98 题:写出如下代码的打印结果

      function changeObjProperty(o) {

      o.siteUrl = “http://www.baidu.com

      o = new Object()

      o.siteUrl = “http://www.google.com

      }

      let webSite = new Object();

      changeObjProperty(webSite);

      console.log(webSite.siteUrl);


      答:

      webSite 属于复合数据类型,函数参数中以地址传递,修改值会影响到原始值,但如果将其完全替换成另一个值,则原来的值不会受到影响






      第 99 题:编程算法题

      用 JavaScript 写一个函数,输入 int 型,返回整数逆序后的字符串。如:输入整型 1234,返回字符串“4321”。要求必须使用递归函数调用,不能用全局变量,输入函数必须只有一个参数传入,必须返回字符串。

      function fun(num) {

      let num1 = num / 10;

      let num2 = num % 10;

      if (num1 < 1) {

      return num;

      } else {

      num1 = Math.floor(num1);

      return ${num2}${fun(num1)};

      }

      }

      var a = fun(12345);

      console.log(a);

      console.log(typeof a);






      第 100 题:请写出如下代码的打印结果

      function Foo() {

      Foo.a = function() {

      console.log(1);

      };

      this.a = function() {

      console.log(2);

      };

      }

      Foo.prototype.a = function() {
      console.log(3);
      };

      Foo.a = function() {
      console.log(4);
      };

      Foo.a();
      let obj = new Foo();
      obj.a();
      Foo.a();

      答:

      4 2 1

      注:资料来源:https://github.com/Advanced-Frontend/Daily-Interview-Question