面试题&面经问题

面试题(主要是面经问题)

跨域问题

当协议、子域名、主域名、端口号中任意一个不相同时,都算作不同域。不同域之间相互请求资源,就算作“跨域”。
第一:如果是协议和端口造成的跨域问题“前台”是无能为力的。

第二:在跨域问题上,仅仅是通过“URL 的首部”来识别而不会根据域名对应的 IP 地址是否相同来判断。“URL 的首部”可以理解为“协议, 域名和端口必须匹配”。
跨域并不是请求发不出去,请求能发出去,服务端能收到请求并正常返回结果,只是结果被浏览器拦截了。

解决办法

  1. JSONP 方式
    利用 <script> 标签没有跨域限制的漏洞,网页可以得到从其他来源动态产生的 JSON 数据。JSONP 请求一定需要对方的服务器做支持才可以。 JSONP 优点是简单兼容性好,可用于解决主流浏览器的跨域数据访问的问题。缺点是仅支持 get 方法具有局限性,不安全可能会遭受 XSS 攻击。
  2. cors 方式
    CORS 需要浏览器和后端同时支持。IE 8 和 9 需要通过 XDomainRequest 来实现。浏览器会自动进行 CORS 通信,实现 CORS 通信的关键是后端。只要后端实现了 CORS,就实现了跨域。服务端设置 Access-Control-Allow-Origin 就可以开启 CORS。 该属性表示哪些域名可以访问资源,如果设置通配符则表示所有网站都可以访问资源。虽然设置 CORS 和前端没什么关系,但是通过这种方式解决跨域问题的话,会在发送请求时出现两种情况,分别为简单请求和复杂请求。
  3. postmessage
    postMessage()方法允许来自不同源的脚本采用异步方式进行有限的通信,可以实现跨文本档、多窗口、跨域消息传递。
  4. websocket
  5. Node 中间件两次代理
  6. Nginx 反向代理

思否资料
掘金资料

HTML 相关

http1.0 http1.1 http2 区别

HTTP1.0、HTTP1.1 和 HTTP2.0 的区别

怎样把网站升级到 http/2

http1.1

  1. 缓存处理,在原先的If-Modified-Since, Expires之上添加了更多的缓存控制策略例如:Entity tag If-Unmodified-Since If-Match If-None-Match
  2. 带宽优化及网络连接的使用,加入了 range 头域,允许请求部分资源例如 206 状态吗
  3. 错误通知的管理。新增了 24 个错误状态响应码。
  4. host 头处理。
  5. 长连接。默认开启:Connection:keep-alive。在同一个 tcp 连接上可以传送多个 http 请求和响应

http2.0

  1. 新的二进制格式。
  2. 多路复用。一个连接可以传输多个资源,雪碧图、减少请求数等就没有必要了。突破了 chrome 同时并发 6 个请求的限制。
  3. header 压缩
  4. 服务端推送(server push)可以提前让服务端推送资源而不是浏览器主动请求

强缓存和协商缓存

github 缓存一系列文章
网络协议系列 — 强缓存与协商缓存机制
浏览器缓存看这一篇就够了
说说缓存

CORS 跨域相关

CORS 简单请求和预检请求

JS 相关

JS 基础

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

  2. == 和 === 的问题
    == 在做比较的时候进行了隐式类型转换
    参考资料

  3. new 一个对象时发生了什么
    new Fun()会进行几个步骤:
    1、创建一个空对象,并且 this 引用该对象,同时还继承了该函数的原型。
    2、this.name 这种属性和方法被加入到 this 引用的对象中。
    3、该函数有 return,如果有,但是返回值是基本类型(Number,String,Boolean,undefined,null)时,会忽略掉此 return 并返回 this(this 引用的对象),如果返回值是引用类型(对象),则返回该对象,this 会被忽略
    4、该函数没有 return,则默认返回 this 引用的对象
    这里我们把 A 函数的返回值设置为一个 Object 类型,则这个时候执行 new A 返回的就是 A 函数的返回值{}.如果我们把 A 的返回值设置为 return [];那么我们在 new A 的时候也相应的得到一个空数组.
    new 操作符具体干了什么呢?

    1. 创建一个空对象,并且 this 变量引用该对象,同时还继承了该函数的原型。
    2. 属性和方法被加入到 this 引用的对象中。
    3. 新创建的对象由 this 所引用,并且最后隐式的返回 this 。
      参考资料
  4. js 原型和原型链
    在 javascript 中,函数可以有属性。 每个函数都有一个特殊的属性叫作原型(prototype)

  5. Promise 相关题目
    Promise 首先解决回调嵌套地狱问题,
    ES6 系列之 Promise
    题目资料

  6. 50 道 JS 基础面试题
    链接

  7. 谈谈你对 ECMAScript6 的理解
    资料

  8. 回流和重绘
    当渲染树中的一部分(或全部)因为元素的规模尺寸,布局,隐藏等改变而需要重新构建。这就称为回流(reflow)。每个页面至少需要一次回流,就是在页面第一次加载的时候。在回流的时候,浏览器会使渲染树中受到影响的部分失效,并重新构造这部分渲染树。完成回流后,浏览器会重新绘制受影响的部分到屏幕中,该过程成为重绘
    回流和重绘介绍(掘金)

  9. js 数组有哪些原生方法,列举一下
    pop、push、shift、unshift、splice、reverse、sort、concat、join、slice、toString、indexOf、lastIndexOf、reduce、reduceRight
    forEach、map、filter、every、some

  10. 什么是 Cookie 隔离?(或者:请求资源的时候不要带 cookie 怎么做)
    通过使用多个非主要域名来请求静态文件,如果静态文件都放在主域名下,那静态文件请求的时候带有的 cookie 的数据提交给 server 是非常浪费的,还不如隔离开。因为 cookie 有域的限制,因此不能跨域提交请求,故使用非主要域名的时候,请求头中就不会带有 cookie 数据,这样可以降低请求头的大小,降低请求时间,从而达到降低整体请求延时的目的。同时这种方式不会将 cookie 传入 server,也减少了 server 对 cookie 的处理分析环节,提高了 server 的 http 请求的解析速度。

  11. 渐进增强与优雅降级
    渐进增强:针对低版本浏览器进行构建页面,保证最基本的功能,然后再针对高级浏览器进行效果、交互等改进,达到更好的用户体验。
    优雅降级:一开始就构建完整的功能,然后再针对低版本浏览器进行兼容。

  12. Web Worker 和 Web Socket
    web socket:在一个单独的持久连接上提供全双工、双向的通信。使用自定义的协议(ws://、wss://),同源策略对 web socket 不适用。
    web worker:运行在后台的 JavaScript,不影响页面的性能。
    创建 worker:var worker = new Worker(url);
    向 worker 发送数据:worker.postMessage(data);
    接收 worker 返回的数据:worker.onmessage
    终止一个 worker 的执行:worker.terminate();
    资料(掘金)

  13. JS 里面什么是闭包
    闭包是在一个函数里声明了另外一个函数,并且这个函数访问了父函数作用域里的变量。

  14. 箭头函数与 this
    call 和 apply 都是调用函数的方法 call 需要列举每个参数,apply 可以传入参数数组
    call 在调用的时候可以指定 this
    window.setTimeout()和 window.setInterval()中 this 的指向是 window 对象

    箭头函数的写法

    1. 规则一:箭头函数只能用赋值式写法,不能用声明式写法
    2. 规则二:如果参数只有一个,可以不加括号,如果没有参数或者参数多于一个就需要加括号
    3. 规则三:如果函数体只有一句话,可以不加花括号
    4. 规则四:如果函数体没有括号,可以不写 return,箭头函数会帮你 return

    箭头函数的特性

    1. 箭头函数的特性一:默认绑定外层 this
    2. 箭头函数的特性二:不能用 call 方法修改里面的 this

    参考资料

  15. JavaScript 中 apply、call、bind 的区别与用法
    apply() 方法调用一个函数, 其具有一个指定的 this 值,以及作为一个数组(或类似数组的对象)提供的参数。 语法 func.apply(thisArg, [argsArray])
    call() 方法调用一个函数, 其具有一个指定的 this 值和分别地提供的参数(参数的列表)。 语法 fun.call(thisArg, arg1, arg2, …)
    bind()方法创建一个新的函数, 当被调用时,将其 this 关键字设置为提供的值,在调用新函数时,在任何提供之前提供一个给定的参数序列。 语法 fun.bind(thisArg, 队列 or 数组)()

Event Loop 必考

当我们调用一个方法的时候,js 会生成一个与这个方法对应的执行环境(context),又叫执行上下文。这个执行环境中存在着这个方法的私有作用域,上层作用域的引用,方法的参数,这个作用域中的定义的变量以及这个作用域的 this 对象。而当一系列方法被依次调用的时候,因为 js 是单线程的,同一时间只能执行一个方法,于是这些方法被排队在一个单独的地方,这个地方被称为执行栈。

当一个脚本第一次执行的时候,js 引擎会解析这段代码,并将其中的同步代码按照执行顺序加入执行栈中,然后从头开始执行。如果当前执行的是一个方法,那么 js 会向执行栈中添加这个方法的执行环境,然后进入这个执行环境继续执行其中的代码。当这个执行环境中的代码 执行完毕并返回结果后,js 会退出这个执行环境并把这个执行环境销毁,回到上一个方法的执行环境。这个过程反复进行,直到执行栈中的代码全部执行完毕。

js 引擎遇到一个异步事件后并不会一直等待其返回结果,而是会将这个事件挂起,继续执行执行栈中的其他任务。当一个异步事件返回结果后,js 会将这个事件加入与当前执行栈不同的另一个队列,我们称之为事件队列。被放入事件队列不会立刻执行其回调,而是等待当前执行栈中的所有任务都执行完毕, 主线程处于闲置状态时,主线程会去查找事件队列是否有任务。如果有,那么主线程会从中取出排在第一位的事件,并把这个事件对应的回调放入执行栈中,然后执行其中的同步代码…,如此反复,这样就形成了一个无限的循环。这就是这个过程被称为“事件循环(Event Loop)”的原因

简单点说就是执行栈和事件队列,栈执行完了就从事件队列里取出一个事件成为新的栈继续执行,有异步任务就继续添加到事件队列里面。

以上的事件循环过程是一个宏观的表述,实际上因为异步任务之间并不相同,因此他们的执行优先级也有区别。不同的异步任务被分为两类:微任务(micro task)和宏任务(macro task)。
宏任务队列:setInterval() setTimeout()
微任务队列:new Promise() new MutaionObserver()

前面我们介绍过,在一个事件循环中,异步事件返回结果后会被放到一个任务队列中。然而,根据这个异步事件的类型,这个事件实际上会被对应的宏任务队列或者微任务队列中去。并且在当前执行栈为空的时候,主线程会 查看微任务队列是否有事件存在。如果不存在,那么再去宏任务队列中取出一个事件并把对应的回到加入当前执行栈;如果存在,则会依次执行队列中事件对应的回调,直到微任务队列为空,然后去宏任务队列中取出最前面的一个事件,把对应的回调加入当前执行栈…如此反复,进入循环。

我们只需记住当当前执行栈执行完毕时会立刻先处理所有微任务队列中的事件,然后再去宏任务队列中取出一个事件。同一次事件循环中,微任务永远在宏任务之前执行。

node 事件循环与浏览器有何不同

在 node 中,事件循环表现出的状态与浏览器中大致相同。不同的是 node 中有一套自己的模型。node 中事件循环的实现是依靠的 libuv 引擎。我们知道 node 选择 chrome v8 引擎作为 js 解释器,v8 引擎将 js 代码分析后去调用对应的 node api,而这些 api 最后则由 libuv 引擎驱动,执行对应的任务,并把不同的事件放在不同的队列中等待主线程执行。 因此实际上 node 中的事件循环存在于 libuv 引擎中。

详解 JS 的 Event Loop

JS 创建对象的几种方式

工厂模式

1
2
3
4
5
6
7
8
9
10
11
12
function createPerson(name, age, job) {
let o = new Object()
o.name = name
o.age = age
o.job = job
o.sayName = function () {
console.log(this.name)
}
return o
}
let person1 = createPerson('Nicholas', 29, 'Software Engineer')
let person2 = createPerson('Greg', 27, 'Doctor')

这种工厂模式虽然可以解决创建多个类似对象的问题,但没有解决对象标识问题(即新创建的对象是什么类型) 。

构造函数模式

1
2
3
4
5
6
7
8
9
10
11
function Person(name, age, job) {
this.name = name
this.age = age
this.job = job
this.sayName = function () {
console.log(this.name)
}
}
let person1 = new Person('Nicholas', 29, 'Software Engineer')
let person2 = new Person('Greg', 27, 'Doctor')
person1.sayName() // Nicholas person2.sayName(); // Greg

跟工厂模式区别:1.没有显式地创建对象 2.属性和方法直接赋值给了 this 3.没有 return
new 的作用 看上下文

原型模式

1
2
3
4
5
6
7
8
9
10
11
12
function Person() {}
Person.prototype.name = 'Nicholas'
Person.prototype.age = 29
Person.prototype.job = 'Software Engineer'
Person.prototype.sayName = function () {
console.log(this.name)
}
let person1 = new Person()
person1.sayName() // "Nicholas"
let person2 = new Person()
person2.sayName() // "Nicholas"
console.log(person1.sayName == person2.sayName) // true

JS 的继承(8 种)

JS 的 8 种继承方式

原型链继承

构造函数、原型和实例之间的关系:每个构造函数都有一个原型对象,原型对象都包含一个指向构造函数的指针,而实例都包含一个原型对象的指针。
继承的本质就是复制,即重写原型对象,代之以一个新类型的实例。
原型链方案存在的缺点:多个实例对引用类型的操作会被篡改。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
function SuperType() {
this.property = true
}

SuperType.prototype.getSuperValue = function () {
return this.property
}

function SubType() {
this.subproperty = false
}

// 这里是关键,创建SuperType的实例,并将该实例赋值给SubType.prototype
SubType.prototype = new SuperType()

SubType.prototype.getSubValue = function () {
return this.subproperty
}

var instance = new SubType()
console.log(instance.getSuperValue()) // true

借用构造函数继承

使用父类的构造函数来增强子类实例,等同于复制父类的实例给子类(不使用原型)。
核心代码是 SuperType.call(this),创建子类实例时调用 SuperType 构造函数,于是 SubType 的每个实例都会将 SuperType 中的属性复制一份。
缺点:1.只能继承父类的实例属性和方法,不能继承原型属性/方法 2.无法实现复用,每个子类都有父类实例函数的副本,影响性能

1
2
3
4
5
6
7
8
9
10
11
12
13
function SuperType() {
this.color = ['red', 'green', 'blue']
}
function SubType() {
//继承自SuperType
SuperType.call(this)
}
var instance1 = new SubType()
instance1.color.push('black')
alert(instance1.color) //"red,green,blue,black"

var instance2 = new SubType()
alert(instance2.color) //"red,green,blue"

组合继承

组合上述两种方法就是组合继承。用原型链实现对原型属性和方法的继承,用借用构造函数技术来实现实例属性的继承。
实例对象 instance1 上的两个属性就屏蔽了其原型对象 SubType.prototype 的两个同名属性。所以,组合模式的缺点就是在使用子类创建实例对象时,其原型中会存在两份相同的属性/方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
function SuperType(name) {
this.name = name
this.colors = ['red', 'blue', 'green']
}
SuperType.prototype.sayName = function () {
alert(this.name)
}

function SubType(name, age) {
// 继承属性
// 第二次调用SuperType()
SuperType.call(this, name)
this.age = age
}

// 继承方法
// 构建原型链
// 第一次调用SuperType()
SubType.prototype = new SuperType()
// 重写SubType.prototype的constructor属性,指向自己的构造函数SubType
SubType.prototype.constructor = SubType
SubType.prototype.sayAge = function () {
alert(this.age)
}

var instance1 = new SubType('Nicholas', 29)
instance1.colors.push('black')
alert(instance1.colors) //"red,blue,green,black"
instance1.sayName() //"Nicholas";
instance1.sayAge() //29

var instance2 = new SubType('Greg', 27)
alert(instance2.colors) //"red,blue,green"
instance2.sayName() //"Greg";
instance2.sayAge() //27

原型式继承

利用一个空对象作为中介,将某个对象直接赋值给空对象构造函数的原型。
object()对传入其中的对象执行了一次浅复制,将构造函数 F 的原型直接指向传入的对象。
ES5 中存在 Object.create()的方法,能够代替上面的 object 方法。
缺点:1.原型链继承多个实例的引用类型属性指向相同,存在篡改的可能。 2.无法传递参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
function object(obj) {
function F() {}
F.prototype = obj
return new F()
}
var person = {
name: 'Nicholas',
friends: ['Shelby', 'Court', 'Van'],
}

var anotherPerson = object(person)
anotherPerson.name = 'Greg'
anotherPerson.friends.push('Rob')

var yetAnotherPerson = object(person)
yetAnotherPerson.name = 'Linda'
yetAnotherPerson.friends.push('Barbie')

alert(person.friends) //"Shelby,Court,Van,Rob,Barbie"

寄生式继承

核心:在原型式继承的基础上,增强对象,返回构造函数
函数的主要作用是为构造函数新增属性和方法,以增强函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function createAnother(original) {
var clone = object(original) // 通过调用 object() 函数创建一个新对象
clone.sayHi = function () {
// 以某种方式来增强对象
alert('hi')
}
return clone // 返回这个对象
}
var person = {
name: 'Nicholas',
friends: ['Shelby', 'Court', 'Van'],
}
var anotherPerson = createAnother(person)
anotherPerson.sayHi() //"hi"

寄生组合式继承

结合借用构造函数传递参数和寄生模式实现继承。
这个例子的高效率体现在它只调用了一次 SuperType 构造函数,并且因此避免了在 SubType.prototype 上创建不必要的、多余的属性。于此同时,原型链还能保持不变;因此,还能够正常使用 instanceof 和 isPrototypeOf()。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
function inheritPrototype(subType, superType) {
var prototype = Object.create(superType.prototype) // 创建对象,创建父类原型的一个副本
prototype.constructor = subType // 增强对象,弥补因重写原型而失去的默认的constructor 属性
subType.prototype = prototype // 指定对象,将新创建的对象赋值给子类的原型
}

// 父类初始化实例属性和原型属性
function SuperType(name) {
this.name = name
this.colors = ['red', 'blue', 'green']
}
SuperType.prototype.sayName = function () {
alert(this.name)
}

// 借用构造函数传递增强子类实例属性(支持传参和避免篡改)
function SubType(name, age) {
SuperType.call(this, name)
this.age = age
}

// 将父类原型指向子类
inheritPrototype(SubType, SuperType)

// 新增子类原型属性
SubType.prototype.sayAge = function () {
alert(this.age)
}

var instance1 = new SubType('xyc', 23)
var instance2 = new SubType('lxy', 23)

instance1.colors.push('2') // ["red", "blue", "green", "2"]
instance1.colors.push('3') // ["red", "blue", "green", "3"]

混入方式继承多个对象

Object.assign 会把 OtherSuperClass 原型上的函数拷贝到 MyClass 原型上,使 MyClass 的所有实例都可用 OtherSuperClass 的方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function MyClass() {
SuperClass.call(this)
OtherSuperClass.call(this)
}

// 继承一个类
MyClass.prototype = Object.create(SuperClass.prototype)
// 混合其它
Object.assign(MyClass.prototype, OtherSuperClass.prototype)
// 重新指定constructor
MyClass.prototype.constructor = MyClass

MyClass.prototype.myMethod = function () {
// do something
}

ES6 类继承 extends

extends 关键字主要用于类声明或者类表达式中,以创建一个类,该类是另一个类的子类。其中 constructor 表示构造函数,一个类中只能有一个构造函数,有多个会报出 SyntaxError 错误,如果没有显式指定构造方法,则会添加默认的 constructor 方法,使用例子如下。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
class Rectangle {
// constructor
constructor(height, width) {
this.height = height;
this.width = width;
}

// Getter
get area() {
return this.calcArea()
}

// Method
calcArea() {
return this.height * this.width;
}
}

const rectangle = new Rectangle(10, 20);
console.log(rectangle.area);
// 输出 200

-----------------------------------------------------------------
// 继承
class Square extends Rectangle {

constructor(length) {
super(length, length);

// 如果子类中存在构造函数,则需要在使用“this”之前首先调用 super()。
this.name = 'Square';
}

get area() {
return this.height * this.width;
}
}

const square = new Square(10);
console.log(square.area);
// 输出 100

JS 常用设计模式

  • 装饰器模式
  • 迭代器模式
  • 发布订阅模式
  • 工厂模式
  • 单例模式
  • 代理模式
  • 外观模式

javascript 设计模式及应用场景

CSS 相关

CSS 基础

  1. css 哪些属性可以继承
    字体系列属性、文本系列属性、元素可见性:visibility、表格布局属性:caption-side、列表属性:list-style-type、设置嵌套引用的引号类型:quotes、光标属性:cursor
    参考资料

  2. 50 道 css 题
    资料

  3. 清除浮动

    1
    2
    3
    <div class="father clearfix">
    <div class="b">我是儿子</div>
    </div>
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    .father {
    background: black;
    }

    .b {
    width: 120px;
    height: 120px;
    float: left;
    background: darkred;
    }
    .clearfix:after {
    content: '.';
    clear: both;
    display: block;
    height: 0;
    overflow: hidden;
    visibility: hidden;
    }
    .clearfix {
    /* 触发 hasLayout */
    zoom: 1;
    }

    清除浮动有好几种方法,不过现在最流行的就是 after 伪元素清除浮动(不适用于 IE)。第二种是空标签清除浮动,子元素最后添加一个空标签,然后加上clear:both(w3c 推荐,浮动闭合标签,但是会额外增加无用元素),第三种是在父元素上面添加overflow:auto;属性(具有不确定性的影响,建议多个浏览器调试)。
    简书资料
    CSDN 资料

  4. BFC 原理及用法
    BFC(Block Formatting Context)直译为“块级格式化上下文”。
    是 W3C CSS 2.1 规范中的一个概念,它决定了元素如何对其内容进行定位,以及与其他元素的关系和相互作用 当涉及到可视化布局的时候,Block Formatting Context 提供了一个环境,HTML 元素在这个环境中按照一定规则进行布局。一个环境中的元素不会影响到其它环境中的布局
    怎样才能形成 BFC

    1. float 的值不能为 none
    2. overflow 的值不能为 visible
    3. display 的值为 table-cell, table-caption, inline-block 中的任何一个
    4. position 的值不为 relative 和 static

      BFC 全称为块级格式化上下文 (Block Formatting Context) 。BFC 是 W3C CSS 2.1 规范中的一个概念,它决定了元素如何对其内容进行定位以及与其他元素的关系和相互作用,当涉及到可视化布局的时候,Block Formatting Context 提供了一个环境,HTML 元素在这个环境中按照一定规则进行布局。一个环境中的元素不会影响到其它环境中的布局。比如浮动元素会形成 BFC,浮动元素内部子元素的主要受该浮动元素影响,两个浮动元素之间是互不影响的。这里有点类似一个 BFC 就是一个独立的行政单位的意思。可以说 BFC 就是一个作用范围,把它理解成是一个独立的容器,并且这个容器里 box 的布局与这个容器外的 box 毫不相干。

    触发 BFC 的条件:

    • 根元素或其它包含它的元素
    • 浮动元素 (元素的 float 不是 none)
    • 绝对定位元素 (元素具有 position 为 absolute 或 fixed)
    • 内联块 (元素具有 display: inline-block)
    • 表格单元格 (元素具有 display: table-cell,HTML 表格单元格默认属性)
    • 表格标题 (元素具有 display: table-caption, HTML 表格标题默认属性)
       具有 overflow 且值不是 visible 的块元素
    
    • 弹性盒(flex 或 inline-flex)
       display: flow-root
       column-span: all
    
  5. css 选择器及优先级
    !important,内联样式(1000),ID 选择器(0100),类选择器(class)/属性选择器([属性])/伪类选择器(0010)(a:hover,a:link, a:active, focus,a:visited, before, after, ::selection),
    元素选择器/伪元素选择器(0001)(:first-letter ,:fist-line, :before),
    关系选择器/通配符选择器(0000)

BFC 及其应用

  • BFC 具体说明,详见《面试基础知识》。需要使用 BFC 解决的问题:

    • 垂直外边距折叠问题 。兄弟之间上下 margin 取最大值而不是相加值。父子之间,如果没有设置 border 或者文字,则

      当两个元素是父子关系时,子元素的外边距(父元素的外边距)会传递给对方

      1. 加文字解决
      2. 设置父或者子的 border
      3. 子使用 padding 而不是 margin
      4. 使用 clearfix 类,伪元素
        1
        2
        3
        4
        5
        6
        7
        8
        9
        .clearfix::before {
        /* 由于必须使用margin,所以解决思路是破坏两个margin相邻的状态 */
        content: '';
        /* 使用table的好处:
        - 既能将虚拟元素变成块元素
        - 同时在内容为空时,不占据空间(block会占据)
        */
        display: table;
        }
        垂直外边距折叠问题
    • 去除浮动,其中的伪元素 clear:both 和 添加 overflow:auto(bfc)就是用来去除浮动的。因为BFC在计算高度的时候,内部浮动元素的高度也要计算在内。也就是说,即使BFC区域内只有一个浮动元素,BFC的高度也不会发生塌缩,高度是大于等于浮动元素的高度的。
      去除浮动的四种方式

回流和重绘

Vue 相关

具体的 Vue 相关面试题详见《Vue 面试题相关》

Vue 基础

  1. vue 的生命周期(8 个)
    beforeCreate、created、beforeMount、mounted、beforeUpdate、updated、beforeDestroy、destroyed
    参考资料

  2. Vue 常问的面试题
    参考资料
    参考资料 CSDN

  3. 自定义组件 v-model 的用法
    emm 之前用过 .sync 不过在自定义组件上用v-model还真没用过。
    这个用法在 vue 2.2 版本添加 具体使用方法如下
    子组件:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    <template>
    <div>
    {{ msg }}
    </div>
    </template>

    <script>
    export default {
    model: {
    prop: 'msg',//这个字段,是指父组件设置 v-model 时,将变量值传给子组件的 msg
    event: 'ok'//这个字段,是指父组件监听 parent-event 事件
    },
    props: {
    msg: String//此处必须定义和model的prop相同的props,因为v-model会传值给子组件
    },
    watch: {
    msg(newVal, oldVal) {
    if (newVal.length > 3) {
    this.$emit('ok', '输入字符已经超过3个') //通过 emit 触发ok,将字符串传递给父组件的v-model绑定的变量
    }
    }
    }
    }
    </script>

    <style lang="scss" scoped>
    </style>

    父组件:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    <template>
    <div class="home">
    <Vmd v-model="testMsg"></Vmd>
    <input
    type="text"
    v-model="testMsg"
    >
    </div>
    </template>

    <script>
    import Vmd from "@/components/Vmodel.vue";
    export default {
    name: "home",
    components: {
    Vmd
    },
    data() {
    return {
    testMsg: 'a'
    }
    }
    };
    </script>

    还有.sync用法,也挺简单,具体见资料
    掘金资料

  4. vue-router 的几个钩子函数的使用
    通过直接更改 Url 触发哪个函数钩子。

  5. v-for 和 v-if 的优先级及为什么不能在一起使用
    因为 v-for 优先级比 v-if 高,所以如果一起用的话 每次动一下数据 都会渲染整个列表,设置的 v-if 都走一下 相当于多操作了 DOM,建议你先把数据处理好(内存中 开销比 DOM 小)然后再渲染
    Vue 风格指南

  6. vue 路由 history 模式刷新页面出现 404 问题
    vue hash 模式下,URL 中存在’#’,用’history’模式就能解决这个问题。但是 history 模式会出现刷新页面后,页面出现 404。解决的办法是用 nginx 配置一下

    1
    2
    3
    4
    5
    6
    7
    8
    location /{
    root /data/nginx/html;
    index index.html index.htm;
    if (!-e $request_filename) {
    rewrite ^/(.*) /index.html last;
    break;
    }
    }

    第二种方法更改 vue 打包配置文件
    掘金
    思否
    文档

  7. vuex 相当于数据库和状态管理中心
    注意问题:模块内的 getter, mutation,action 是注册在全局空间的,state 只注册在局部命名空间的;
    解决 vuex 数据刷新消失问题
    stack overflow cookie 存储方式
    记得还有一个

  8. v-if 触发 destoryed 钩子 v-show 不会触发
    组件销毁不会清除定时器,setTimeout 放到了异步任务队列,哪怕组件清楚了也会执行回调。

  9. vue 兄弟组件通信的三种方式
    一、子传父 父传子
    二、Vuex
    三、创建一个事件总线 新建一个 vue 实例当做事件总线 然后使用这个实例的$on和$emit 来进行传递信息

  10. 组件里面 data 必须是一个函数
    Object 是引用数据类型, 每个组件的 data 都是内存的同一个地址,一个数据改变了其他也改变了。当一个组件被定义,data 必须声明为返回一个初始数据对象的函数,因为组件可能被用来创建多个实例。如果 data 仍然是一个纯粹的对象,则所有的实例将共享引用同一个数据对象!通过提供 data 函数,每次创建一个新实例后,我们能够调用 data 函数,从而返回初始数据的一个全新副本数据对象。

  11. vue 中$set 的使用场景
    一、通过数组的下标去修改数组的值,数据已经被修改了,但是不触发 updated 函数,视图不更新,
    二、vue 中检测不到对象属性的添加和删除
    vue 在创建实例的时候把 data 深度遍历所有属性,并使用 Object.defineProperty 把这些属性全部转为 getter/setter。让 Vue 追踪依赖,在属性被访问和修改时通知变化。所以属性必须在 data 对象上存在才能让 Vue 转换它,这样才能让它是响应的。
    当你在对象上新加了一个属性 newProperty,当前新加的这个属性并没有加入 vue 检测数据更新的机制(因为是在初始化之后添加的),vue.$set 是能让 vue 知道你添加了属性, 它会给你做处理

  12. 计算属性 vs 方法
    计算属性是基于它们的响应式依赖进行缓存的。只在相关响应式依赖发生改变时它们才会重新求值。这就意味着只要 message 还没有发生改变,多次访问 reversedMessage 计算属性会立即返回之前的计算结果,而不必再次执行函数。如果不希望有缓存 那么使用方法来代替。

React 相关

具体的 React 相关面试题详见《React 面试题相关》

React 基础

  1. RN 生命周期
    getDefaultProps、getInitialState、componentWillMount、render、componentDidMount、shouldComponentUpdate、componentWillUpdate、componentDidUpdate、componentWillReceiveProps、componentWillUnmount
    参考资料
  2. React 有关的面试题
    掘金资料
  3. setState 之后发生了什么

Nodejs 相关

浏览器相关

Http 协议

资料

  1. Http 的请求报文和响应报文分为几个部分
    一个 HTTP 请求报文由请求行(request line)、请求头部(header)、空行和请求数据 4 个部分组成
    HTTP 响应也由三个部分组成,分别是:状态行、消息报头、响应正文。
  2. 常见的 HTTP 状态码
    1xx(临时响应)表示临时响应并需要请求者继续执行操作的状态代码。代码 说明
    2xx (成功)表示成功处理了请求的状态代码。代码 说明
    3xx (重定向) 表示要完成请求,需要进一步操作。 通常,这些状态代码用来重定向。代码 说明
    4xx(客户端请求错误) 这些状态代码表示请求可能出错,妨碍了服务器的处理。代码 说明
    5xx(服务器错误)这些状态代码表示服务器在尝试处理请求时发生内部错误。 这些错误可能是服务器本身的错误,而不是请求出错。代码 说明
    博客园简洁
    常见的 14 种状态码
  3. Get 和 Post 的区别
    GET 在浏览器回退时是无害的,而 POST 会再次提交请求。
    GET 产生的 URL 地址可以被 Bookmark,而 POST 不可以。
    GET 请求会被浏览器主动 cache,而 POST 不会,除非手动设置。
    GET 请求只能进行 url 编码,而 POST 支持多种编码方式。
    GET 请求参数会被完整保留在浏览器历史记录里,而 POST 中的参数不会被保留。
    GET 请求在 URL 中传送的参数是有长度限制的,而 POST 么有。
    对参数的数据类型,GET 只接受 ASCII 字符,而 POST 没有限制。
    GET 比 POST 更不安全,因为参数直接暴露在 URL 上,所以不能用来传递敏感信息。
    GET 参数通过 URL 传递,POST 放在 Request body 中。
  4. RESTful Api 四种操作对应说明
    POST Create 新增一个没有 id 的资源
    GET Read 取得一个资源
    PUT Update 更新一个资源。或新增一个含 id 资源(如果 id 不存在)
    DELETE Delete 删除一个资源

浏览器基础兼容

  1. 浏览器兼容性问题 掘金总结

    1. 安装 Normalize.css 来磨平各浏览器之间的样式差异问题

    2. 使用 html5shiv.js 解决 IE9 以下对于新增 H5 标签不支持的问题

      1
      2
      3
      <!--[if lt IE 9]>
      <script type="text/javascript" src="https://cdn.bootcss.com/html5shiv/3.7.3/html5shiv.min.js"></script>
      <![endif]-->
    3. 使用 respond.js 解决 ie9 以下浏览器不支持 CSS3 Media Query 的问题。

      1
      <script src='https://cdn.bootcss.com/respond.js/1.4.2/respond.min.js' />
    4. 浏览器 CSS 兼容前缀

      1
      2
      3
      4
      5
      6
      7
      8
      9
      -o-transform: rotate(7deg); // Opera

      -ms-transform: rotate(7deg); // IE

      -moz-transform: rotate(7deg); // Firefox

      -webkit-transform: rotate(7deg); // Chrome

      transform:rotate(7deg); // 统一标识语句
  2. BFC 解决边距重叠问题
    当相邻元素都设置了 margin 边距时,margin 将取最大值,舍弃小值。为了不让边距重叠,可以给子元素加一个父元素,并设置该父元素为 BFC:overflow: hidden;

1
2
3
4
5
6
7
8
9
<div class="box" id="box">
<p>Lorem ipsum dolor sit.</p>

<div style="overflow: hidden;">
<p>Lorem ipsum dolor sit.</p>
</div>

<p>Lorem ipsum dolor sit.</p>
</div>

掘金总结

性能相关

前端性能优化指标

掘金 性能优化指标及方案

文档加载相关

Time to First Byte(TTFB):浏览器从请求页面开始到接收第一字节的时间,这个时间段内包括 DNS 查找、TCP 连接和 SSL 连接。
DomContentLoaded(DCL):DomContentLoaded 事件触发的时间。当 HTML 文档被完全加载和解析完成之后,DOMContentLoaded事件被触发,而无需等待样式表、图像和子框架加载完成。
Load(L):onLoad 事件触发的时间。页面所有资源都加载完毕后(比如图片,CSS),onLoad 事件才被触发。

内容呈现相关

First Paint(FP): 从开始加载到浏览器首次绘制像素到屏幕上的时间,也就是页面在屏幕上首次发生视觉变化的时间。但此变化可能是简单的背景色更新或不引人注意的内容,它并不表示页面内容完整性,可能会报告没有任何可见的内容被绘制的时间。(First Paint 不包括默认的背景绘制,但包括非默认的背景绘制。)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
function getFirstPaint() {
let firstPaints = {}
if (typeof performance.getEntriesByType === 'function') {
let performanceEntries = performance.getEntriesByType('paint') || []
performanceEntries.forEach((entry) => {
if (entry.name === 'first-paint') {
firstPaints.firstPaint = entry.startTime
} else if (entry.name === 'first-contentful-paint') {
firstPaints.firstContentfulPaint = entry.startTime
}
})
} else {
if (chrome && chrome.loadTimes) {
let loadTimes = window.chrome.loadTimes()
let { firstPaintTime, startLoadTime } = loadTimes
firstPaints.firstPaint = (firstPaintTime - startLoadTime) * 1000
} else if (
performance.timing &&
typeof performance.timing.msFirstPaint === 'number'
) {
let { msFirstPaint, navigationStart } = performance.timing
firstPaints.firstPaint = msFirstPaint - navigationStart
}
}
return firstPaints
}

First Contentful Paint(FCP): 浏览器首次绘制来自 DOM 的内容的时间,内容必须是文本、图片(包含背景图)、非白色的 canvas 或 SVG,也包括带有正在加载中的 Web 字体的文本。这是用户第一次开始看到页面内容,但仅仅有内容,并不意味着它是有用的内容(例如 Header、导航栏等),也不意味着有用户要消费的内容。

First Meaningful Paint(FMP) :页面的主要内容绘制到屏幕上的时间,这是一个更好的衡量用户感知加载体验的指标,但仍然不理想。主要内容的定义因页面而异,例如对于博客文章,它的主要内容是标题和摘要,对于搜索页面,它的主要内容是搜索结果,对于电商的页面,图片则是主要内容。

Largest Contentful Paint(LCP): 可视区域中最大的内容元素呈现到屏幕上的时间,用以估算页面的主要内容对用户可见时间。

Speed Index(SI):这是一个表示页面可视区域中内容的填充速度的指标,可以通过计算页面可见区域内容显示的平均时间来衡量。

First Screen Paint(FSP):页面从开始加载到首屏内容全部绘制完成的时间,用户可以看到首屏的全部内容。如果说 LCP 是用户看到有效内容的最近似的时间,那么在 FSP 这个时间点用户已经看到了可视区域内完整的内容,可以说是衡量用户视觉体验最合适的指标。

xingnengzhibiao.png
xingnengzhibiao.png

交互响应性相关

Time to Interactive(TTI):表示网页第一次 完全达到可交互状态 的时间点,浏览器已经可以持续性的响应用户的输入。完全达到可交互状态的时间点是在最后一个长任务(Long Task)完成的时间, 并且在随后的 5 秒内网络和主线程是空闲的。

First CPU Idle(FCI):页面第一次可以响应用户输入的时间。

First Input Delay(FID):从用户第一次与页面交互(例如单击链接、点击按钮等)到浏览器实际能够响应该交互的时间。
输入延迟是因为浏览器的主线程正忙于做其他事情,所以不能响应用户。发生这种情况的一个常见原因是浏览器正忙于解析和执行应用程序加载的大型 JavaScript 文件。
第一次输入延迟通常发生在第一次内容绘制(FCP)和可持续交互时间(TTI)之间,因为页面已经呈现了一些内容,但还不能可靠地交互。

Frames Per Second(FPS):帧率是视频设备产生图像(或帧)的速率,用每秒可以重新绘制的帧数(Frames Per Second,FPS)表示。

首屏加载优化方案

  • Vue-Router 路由懒加载(利用 Webpack 的代码切割)
  • 使用 CDN 加速,将通用的库从 vendor 进行抽离
  • Nginx 的 gzip 压缩
  • Vue 异步组件
  • 服务端渲染 SSR
  • 如果使用了一些 UI 库,采用按需加载
  • Webpack 开启 gzip 压缩
  • 如果首屏为登录页,可以做成多入口
  • Service Worker 缓存文件处理
  • 使用 link 标签的 rel 属性设置 prefetch(这段资源将会在未来某个导航或者功能要用到,但是本资源的下载顺序权重比较低,prefetch 通常用于加速下一次导航)、preload(preload 将会把资源得下载顺序权重提高,使得关键数据提前下载好,优化页面打开速度)

前端处理 10w 数据以上的去重

  1. 首先在不考虑 IE 兼容的情况下推荐使用 Set 去重,可以看到每个去重方法具体效率都跟数据量、重复量相关。当然有的也跟数据类型相关,比如使用 sort 排序去重
  2. 如果是有序的数值,sort 排序去重无疑是最快的,兼容性也很好。如果能确定需要
  3. 去重的数据类型是简单字符串,推荐使用对象 key 唯一去重,经测试,在数据是短字符串的情况下,对象 key 唯一的去重方式在效率上还能提升 5 倍以上。如果数据类型比较复杂,而又不需要兼容 IE10 以下
  4. 推荐使用 Map 数据唯一去重。以上所有测试结果都能看到,重复量越大,处理速度越快,反而重复得越少,需要处理的时间就越长。选择去重方案也需要考虑当前项目中是否有使用 babel 工具,即使用了兼容性不好的 Set、Map 方法,babel 也会打包为 ES5 语法。然而并不知道具体会打包成怎样去重,所以上诉方法都请视项目情况使用。
    10 万条数据去重

前端如何处理 10w 条+的数据渲染

建议也别分页懒加载了,直接就是虚拟列表+宏任务切片
10 万条数据渲染
区域数据去重 + JavaScript 一次性展示几万条数据实例代码

网站性能优化

  1. 减少 http 请求(虽然说 http2.0 几乎不限制一次 6 个的请求,但是为了兼容最好还是做一下)
  2. CSS Sprites 精灵图
  3. 减少图片使用 用图标或者 svg 代替
  4. js、 css 代码源码压缩混淆
  5. 网页 Gzip 压缩
  6. CDN 托管
  7. 配置 ETag 缓存、Expires 与 Cache-control
  8. 减少 DNS 查询
  9. 减少 DOM 数量
  10. 减少回流重绘
  11. 合并操作 DOM

图片处理

  1. WebP 格式,谷歌(google)开发的一种旨在加快图片加载速度的图片格式。图片压缩体积大约只有 JPEG 的 2/3,并能节省大量的服务器带宽资源和数据空间。Facebook Ebay 等知名网站已经开始测试并使用 WebP 格式
  2. Apng: 全称是“Animated Portable Network Graphics”, 是 PNG 的位图动画扩展,可以实现 png 格式的动态图片效果。04 年诞生,但一直得不到各大浏览器厂商的支持,直到日前得到 iOS safari 8 的支持,有望代替 GIF 成为下一代动态图标准

Webpack 问题

webpack 流程、原理、loaders、plugins 等等
揭秘 webpack 插件工作流程和原理

Vite 问题

Vite 是一个开发构建工具

Vite 工程化实战

备战 2021:vite 工程化实践,建议收藏

Rollup

Rollup 是前端模块化的一个打包工具。Vite 使用 rollup 构建生产环境打包。

babel 问题

一文读懂 babel 编译流程,再也不怕面试官刁难了
babel 的使用方式

综合问题

命令式编程和声明式编程的区别

简单点说声明式编程就是告诉我要什么,然后怎么实现我不管,命令式编程就是一步步指导该怎么做。打个比方就是 jq 和 vue、react 的区别,jq 要一步步操作 dom 实现效果,而 vue 只要设置个数据就能自动渲染。
参考资料:
知乎 命令式编程(Imperative) vs 声明式编程( Declarative)
从年会看声明式编程(Declarative Programming)

Vue 和 React 的选择

  • 两个框架性能和生态相差不多
  • 看公司技术栈和工程师熟悉程度
  • react 的 jsx 语法图灵完备, 比 vue 的模板语言描述能力更强,但模板语言也有好处:更容易做编译期和运行时的优化
  • react 的函数式组件和 hooks 配合起来抽象能力极强,组件拆分更小、更抽象
  • react 和 typescript 结合能力更强,大型项目更好
  • react 有大厂背书(Antd)和先发优势
  • react 更改业态生产规则,vue 则偏向于使用规则
  • react 包含很多非侵入式的代码和服务
  • react 没有官方指定的状态管理库,因此社区涌现了一堆状态管理库并几乎每年都要重复造轮子 redux、mobx、context、hooks、Zustand、Recoil、dva 等等

TCP 和 IP

TCP 是一种面向连接的单播协议,在发送数据前,通信双方必须在彼此间建立一条连接。所谓的“连接”,其实是客户端和服务器的内存里保存的一份关于对方的信息,如 ip 地址、端口号等。

TCP 可以看成是一种字节流,它会处理 IP 层或以下的层的丢包、重复以及错误问题。在连接的建立过程中,双方需要交换一些连接的参数。这些参数可以放在 TCP 头部。

TCP 提供了一种可靠、面向连接、字节流、传输层的服务,采用三次握手建立一个连接。采用 4 次挥手来关闭一个连接。

什么是 3 次握手,四次挥手

客户端和服务端通信前要进行连接,“3 次握手”的作用就是双方都能明确自己和对方的收、发能力是正常的。

第一次握手:客户端发送网络包,服务端收到了。这样服务端就能得出结论:客户端的发送能力、服务端的接收能力是正常的。

第二次握手:服务端发包,客户端收到了。这样客户端就能得出结论:服务端的接收、发送能力,客户端的接收、发送能力是正常的。 从客户端的视角来看,我接到了服务端发送过来的响应数据包,说明服务端接收到了我在第一次握手时发送的网络包,并且成功发送了响应数据包,这就说明,服务端的接收、发送能力正常。而另一方面,我收到了服务端的响应数据包,说明我第一次发送的网络包成功到达服务端,这样,我自己的发送和接收能力也是正常的。

第三次握手:客户端发包,服务端收到了。这样服务端就能得出结论:客户端的接收、发送能力,服务端的发送、接收能力是正常的。 第一、二次握手后,服务端并不知道客户端的接收能力以及自己的发送能力是否正常。而在第三次握手时,服务端收到了客户端对第二次握手作的回应。从服务端的角度,我在第二次握手时的响应数据发送出去了,客户端接收到了。所以,我的发送能力是正常的。而客户端的接收能力也是正常的。

三次握手

TCP 连接是双向传输的对等的模式,就是说双方都可以同时向对方发送或接收数据。当有一方要关闭连接时,会发送指令告知对方,我要关闭连接了。这时对方会回一个 ACK,此时一个方向的连接关闭。但是另一个方向仍然可以继续传输数据,等到发送完了所有的数据后,会发送一个 FIN 段来关闭此方向上的连接。接收方发送 ACK 确认关闭连接。注意,接收到 FIN 报文的一方只能回复一个 ACK, 它是无法马上返回对方一个 FIN 报文段的,因为结束数据传输的“指令”是上层应用层给出的,我只是一个“搬运工”,我无法了解“上层的意志”。

四次挥手

为什么建立连接是三次握手,而关闭连接却是四次挥手呢?

这是因为服务端在 LISTEN 状态下,收到建立连接请求的 SYN 报文后,把 ACK 和 SYN 放在一个报文里发送给客户端。而关闭连接时,当收到对方的 FIN 报文时,仅仅表示对方不再发送数据了但是还能接收数据,己方是否现在关闭发送数据通道,需要上层应用来决定,因此,己方 ACK 和 FIN 一般都会分开发送。

知乎 三次握手 四次挥手
详解 TCP 三次握手、四次挥手,附带精美图解和超高频面试题

简述一下 polyfill 和 shim 的异同

在使用 shim 时,不会在意旧环境是否已经存在某 API,它会直接重新改变全局对象,为旧环境提供 API(它是一个垫片,为旧环境提供新功能,从而创建一个新环境)。 而在 polyfill 中,它会判断旧环境是否已经存在 API,不存在时才会添加新 API(它是腻子,抹平不同环境下的 API 差异)。 这二者的目的并不相同。

JS 面试题总结

  1. 给定一个字符串 var str=’abcabcefbbd’。1)求字符 a 出现的次数。2)求出现次数最多的字符是,出现的次数是多少

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    function getStr() {
    let str = 'abcabcefbbd'
    let beatStr = {}
    let moreStr = ''
    let num = 0
    for (let item of str) {
    if (beatStr[item]) {
    beatStr[item]++
    } else {
    beatStr[item] = 1
    }
    if (beatStr[item] > num) {
    num = beatStr[item]
    moreStr = item
    }
    }
    console.log(num, moreStr, beatStr)
    return beatStr
    }
    getStr()

    输出4 'b' {a: 2, b: 4, c: 2, e: 1, f: 1}

  2. 以下代码输出多少

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    var c = 0
    function print() {
    console.log(window.c)
    console.log(c)
    }
    function plus() {
    setTimeout(function () {
    c += 1
    }, 1000)
    }
    plus()
    print()

    输出:window.c = 0 c = 0

  3. js 实现一个千分位分割方法
    js 实现一个千位符分割方法: parseInt('11123123231').toLocaleString() "111,231,232,313,213"
    或者是

    1
    2
    3
    var str = '100000000000',
    reg = /(?=(\B\d{3})+$)/g
    console.log(str.replace(reg, ','))

    参考资料

  4. 实现一个拍平数组,实现以下需求
    var source = ['1',['2',['3',['4']]],'5',['6']]
    ['1','2','3','4','5','6']
    解题思路:递归
    var ary = [1,23,4];console.log(ary instanceof Array)//true; instanceof 判断是否是数组

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    var source = ['1', ['2', ['3', ['4']]], '5', ['6']]
    var sortArr = []

    function sort(arr) {
    for (let i in arr) {
    if (arr[i] instanceof Array) {
    sort(arr[i])
    } else {
    sortArr.push(arr[i])
    }
    }
    }
    sort(source)
    // eslint-disable-next-line no-console
    console.log(sortArr)
    // [ '1', '2', '3', '4', '5', '6' ]
  5. 函数柯里化 任意参数数相加

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    var adder = function () {
    var _args = []
    return function () {
    if (arguments.length === 0) {
    return _args.reduce(function (a, b) {
    return a + b
    })
    }
    ;[].push.apply(_args, [].slice.call(arguments))
    // _args.push(...arguments)
    return arguments.callee
    }
    }
    var sum = adder()
    console.log(sum) // Function

    sum(100, 200)(300) // 调用形式灵活,一次调用可输入一个或者多个参数,并且支持链式调用
    sum(400)
    console.log(sum()) // 1000 (加总计算)

    参考资料:dalao
    参考资料:掘金
    参考资料:arguments.callee

  6. 数组[1,2,3,4,5]如何变成[1,2,3,’a’,’b’,5]
    使用 ES6 的扩展运算符

    1
    2
    3
    let a = [1,2,3,4,5]
    let b = ['a'.'b']
    a.splice(3,1,...b)
  7. 以下代码输出多少

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    const length = 10
    function fn() {
    console.log(this.length)
    }

    const obj = {
    length: 5,
    method: function (fn) {
    fn()
    arguments[0]()
    },
    }

    obj.method(fn, 1)

    输出:10,2

    method 这个函数传入了两个参数,一个参数为 fn(),fn()为普通函数,this 指向函数的调用者,此时指向全局(也可以看这个函数前面没有点),所以运行结果为 10,arguments 是函数的所有参数,是一个类数组的对象,arguments0,可以看成是 arguments.0(),调用这个函数的是 arguments,此时 this 就是指 arguments,this.length 就是 angument.length,就是传入的参数的总个数 2

Author: XavierShi
Link: https://blog.xaviershi.com/2021/10/23/面试题&面经问题/
Copyright Notice: All articles in this blog are licensed under CC BY-NC-SA 4.0 unless stating additionally.