JavaScript面试题

68 阅读1小时+

声明

这些都是来自y03l2iufsbl.feishu.cn/docx/XfL4di…

1,不会冒泡的事件

冒泡:事件会从目标元素开始向上冒泡到它的父元素,并最终到达document元素

不冒泡:事件通常直接在目标元素上触发,并不会向上传播
例如:
1,focus
2,blur
3,load
4,unload
5,stop
6,scroll
7,readystatechange

2,mouseEnter和 mouseOver区别?

mouseenter:当鼠标进入元素自身时触发,只在目标元素上触发,不会因为鼠标移动到其子元素上而再次触发

mouseover:不仅在目标元素上触发,也在其子元素上触发。所以,如果鼠标从一个子元素移动到另一个子元素,这些元素的父元素会触发多个 

3,MessageChannel

MessageChannel: 提供了两个通信端点(port1 和 port2),可以在两个不同的执行环境之间传递消息,并通过事件监听的方式来处理这些消息

场景:
1Web Workers 通信
2,不同浏览上下文(browsing context)之间的通信
3SharedWorker 通信
4,服务端和客户端之间的通信
5,异步任务处理

4,Proxy 能够监听到对象中的对象的引用吗?

可以监听到对象中的对象的引用

5,如何让 var [a, b] = {a: 1, b: 2} 解构赋值成功?

迭代协议
满足迭代协议需要对象身上有一个名为[Symbol.iterator]的方法。再使用for..of或者解构赋值的时候会隐式的调用这个方法,得到一个迭代对象,通过迭代对象的next方法判断当前是否完成迭代和具体迭代的值。

const obj = {
	a: '1',
	b: '2',
	[Symbol.iterator]() {
		let index = 0
		const keys = Object.keys(this)
		return {
			next() {
				if (index < keys.length) {
					return {
						done: false,
						value: obj[keys[index++]]
					}
				}
				return {
					done: true,
					value: undefined
				}
			}
		}
	}
}

const [a, b] = obj

for (let i of obj) {
	console.log(i)
}

6,函数声明与变量声明提升

函数会首先提升,然后才是变量

foo();
var foo;
function foo() {
	console.log(1)
}
foo = function() {
	console.log(2)
}
输出结果:1

分解:
function foo() {
	console.log(1)
}
var foo;

foo();

foo = function() {
	console.log(2)
}

7,下列代码执行结果

foo(typeof a); // typeof a undefined
function foo(p) {
	console.log(this); // 上下文windows
	console.log(p); // typeof a undefined
	console.log(typeof b); // 报错
	let b = 0;
}

8,作用域链

JavaScript 中用于查找变量和函数的一种机制

当代码在一个执行环境中执行时,如果需要访问一个变量或者函数,JavaScript 引擎会首先在当前执行环境的变量对象中查找,如果找不到,它会沿着作用域链向上一级的执行环境中查找,直到找到对应的变量或者函数,或者达到全局执行环境为止。

9,bind、call、apply 有什么区别

作用是改变函数执行时的上下文,改变this指向

apply:接受两个参数,第一个参数是this指向,第二个参数是函数接受的参数,以数组的形式传入,改变this指向后原函数会立即执行,且此方法只是临时改变(第一个参数若为nullundefined时,this指向window)

call:接受两个参数,第一个参数是this指向,第二个参数时参数列表,改变this指向后原函数会立即执行,且此方法只是临时改变(第一个参数若为nullundefined时,this指向window)

bind:接受两个参数,第一个参数是this指向,后面传入的也是一个参数列表(但是这个参数列表可以分多次传入),改变this指向后不会立即执行,而是返回一个永久改变this指向的函数(第一个参数若为nullundefined时,this指向window

10,var,let,const区别

var会挂载window上(全局)
letconst不会

11,common.js和es6中模块的区别

common.js1,使用requiremodule.exports
2,同步加载
3Node.js 环境
4,导出的是值的拷贝: 但对于对象和数组等引用类型,修改引用类型的属性会在所有引用中反映出来。
es6:
1,使用importexport
2,静态分析和异步加载
3,现代前端开发
4,导出的是值的引用: 导出值的引用意味着当导出模块中的值发生变化时,所有引用该值的地方都会反映出这些变化。

12,script标签放在header里和放在body底部区别

会对页面的加载和性能产生不同的影响

<head>
优点:
1,预加载: 浏览器在渲染页面之前,会先下载和解析,这样可以确保脚本在页面加载过程中随时可以被调用
2,全局可用性: 一些脚本,特别是需要在页面一加载就运行的脚本
缺点:
1,阻塞渲染: 浏览器在遇到 <script> 标签时会暂停 HTML 的解析和渲染,直到脚本下载并执行完毕
2,页面白屏时间延长

<body>
优点:
1,非阻塞渲染
2,更好的用户体验
缺点:
1,延迟脚本执行

13,DOM 和 BOM

DOM1,是HTMLXML 文档的标准的对象模型
2DOM 以树状结构组织文档的内容,其中树的根节点是 document 对象,它代表整个文档

BOM:
1BOM 是表示浏览器窗口及其各个组件的对象模型
2BOM 的核心对象是 window 对象,它表示浏览器窗口,并且是 JavaScript 中的全局对象
3,navigator,location,history,screen

14,简单描述从输入网址到页面显示的过程

1DNS解析
首先在本地的域名服务器中查找,没找到去根域名服务器查找,没有再去com顶级域名服务器查找,,如此的类推下去,直到找到IP地址,然后把它记录在本地,供下次使用。大致过程就是.-> .com -> google.com. -> www.google.com

2,发起TCP连接
TCP提供一种可靠的传输,这个过程涉及到三次握手,四次挥手。

3,发送HTTP请求
发送HTTP请求,就是构建HTTP请求报文,并通过TCP协议,发送到服务器指定端口。

4,服务器处理请求并返回HTTP报文

5,浏览器解析渲染页面
解析HTML形成DOM树
解析CSS形成CSSOM 树
合并DOM树和CSSOM树形成渲染树
浏览器开始渲染并绘制页面

6,连接结束

15,new操作符

1,创建一个新的对象
2,将对象与构建函数通过原型链连接起来
3,将构建函数中的this绑定到新建的对象obj上
4,根据构建函数返回类型作判断,如果是原始值则被忽略,如果是返回对象,需要正常处理

手写
function mynew(Func, ...args) {
	const obj = {} // 创建一个新对象
	obj.__proto__ = Func.prototype // 新对象原型指向构造函数原型对象
	let result = Func.apply(obj, args) // 将构建函数的this指向新对象
	return result instanceof Object ? result : obj
}

16,try...catch 可以捕获到异步代码中的错误吗?

不能

17,说说WebSocket和HTTP的区别

HTTP1,是单向的,客户端发送请求,服务器发送响应
2TCP协议之上运行的无状态协议
3HTTP协议的长连接和短连接

WebSocket:
1,是双向的,在客户端-服务器通信的场景中使用的全双工协议,ws://与wss://

18,e.target 和 e.currentTarget 区别

冒泡 & 捕获

当你触发一个元素的事件的时候,该事件从该元素的祖先元素传递下去,此过程捕获,而到达此元素之后,又会向其祖先元素传播上去,此过程为冒泡

addEventListener
第一个参数:绑定的事件名
第二个参数:执行的函数
第三个参数:
	false:默认,代表冒泡时绑定
	true:代表捕获时绑定

e.target:触发事件的元素
e.currentTarget:绑定事件的元素

19,Map 和 Set 区别

Map1,是一组键值对的结构,和 JSON 对象类似
2,key 不仅可以是字符串还可以是对象

Set1,对象类似于数组,且成员的值都是唯一的

1,初始化需要的值不一样,Map需要的是一个二维数组,而Set 需要的是一维 Array 数组
2MapSet 都不允许键重复
3Map的键是不能修改,但是键对应的值是可以修改的;Set不能通过迭代器来改变Set的值,因为Set的值就是键
4Map 是键值对的存在,值也不作为健;而 Set 没有 value 只有 key,value 就是 key

20,如何确保你的构造函数只能被new调用,而不能被普通调用?

1,使用 instanceof 实现
2new.target
3,使用ES6 Class

21,请简述 == 的机制

请添加图片描述

22,怎么做移动端的样式适配?

1,响应式设计(Responsive Design):
	使用CSS媒体查询(Media Queries)来根据设备的特征(如屏幕宽度、高度、方向等)应用不同的样式
	通过设置百分比宽度、最大宽度或相对单位(比如 rem)来确保元素相对于其容器的大小进行自适应

2,弹性布局(Flexbox)和网格布局(Grid):
	使用弹性布局和网格布局可以更方便地创建灵活的布局,使页面元素能够根据屏幕大小自动调整位置

3,移动端优先(Mobile-first):
	首先定义移动端的样式,然后使用媒体查询逐渐添加更大屏幕上的样式,以确保基本功能在小屏幕上也能正常工作

4,图片和多媒体适配:
	使用max-width: 100%确保图片和多媒体在小屏幕上不会溢出其容器
	使用picture元素或srcset属性提供不同尺寸的图片

23,arguments 这种类数组,如何遍历类数组?

所谓的类数组对象:拥有一个 length 属性和若干索引属性的对象

类数组调用:
var arrayLike = {0: 'name', 1: 'age', 2: 'sex', length: 3}
Array.prototype.join.call(arrayLike, '&') // name&age&sex
Array.prototype.slice.call(arrayLike, 0) // ['name', 'age', 'sex']
Array.prototype.map.call(arrayLike, function(item) {
	return item.toUpperCase()
})

类数组转数组
var arrayLike = {0: 'name', 1: 'age', 2: 'sex', length: 3}
Array.prototype.slice.call(arrayLike)
Array.prototype.splice.call(arrayLike, 0)
Array.from(arrayLike)
Array.prototype.concat.apply([], arrayLike)

24,数组方法

1for
2,forEach
对数组中的每一元素运行给定的函数,没有返回值,常用来遍历元素
3for...in
任意顺序遍历一个对象的除symbol以外的可枚举属性,包括继承的可枚举属性
3for...of(不能遍历对象)
在可迭代对象(具有 iterator 接口)(ArrayMapSetStringarguments)上创建一个迭代循环,调用自定义迭代钩子,并为每个不同属性的值执行语句,不能遍历对象
4,map
只能遍历数组,不能中断,返回值是修改后的数组
5,every
对数组中的每一运行给定的函数,如果该函数对每一项都返回true,则该函数返回true
6,some
对数组中的每一运行给定的函数,如果该函数有一项返回true,就返回true,所有项返回false才返回false
7,reduce
方法对数组中的每个元素执行一个由你提供的reducer函数(升序执行),将其结果汇总为单个返回值
8,filter
对数组中的每一运行给定的函数,会返回满足该函数的项组成的数组

25,说说下面代码的输出结果

Promise.resolve().then(() => {
    console.log(0)
    return Promise.resolve(4)
}).then((res) => {
    console.log(res)
})

Promise.resolve().then(() => {
    console.log(1)
}).then(() => {
    console.log(2)
}).then(() => {
    console.log(3)
}).then(() => {
    console.log(5)
}).then(() => {
    console.log(6)
})

结果
0
1
2
3
4
5
6

解析
Promise.resolve() => new Promise(resolve => resolve())

Promise.resolve().then(() => {
    console.log(0)
    return Promise.resolve(4)
}).then((res) => {
    console.log(res)
})
变形
new Promise((resolve) => {
	console.log(0)
	return resolve(new Promise((resolve) => {
		resolve(4)
	})) // return 4
})
.then() // 多了2个微任务
.then()
.then((res) => {
	console.log(res)
})

26,ES6-ES12的了解

ES620151,类(class2,模块化(ES Module)
3,箭头(Arrow)函数
4,函数参数默认值
5,模板字符串
6,解构赋值
7,延展操作符
8,对象属性简写
9Promise
10letconst

ES720161Array.prototype.includes()
2,指数操作符 2**10 // 1024

ES820171async/await
2Object.values()
3Object.entries()
4String padding
5,函数参数列表结尾允许逗号
6Object.getOwnPropertyDescriptors()
7SharedArrayBuffer对象
8Atomics对象

ES920181,异步迭代
2Promise.finally()
3Rest/Spread 属性
4,正则表达式命名捕获组
5,正则表达式反向断言
6,正则表达式dotAll模式

ES1020191Array.flat()和Array.flatMap()
2String.trimStart()和String.trimEnd()
3String.prototype.matchAll
4Symbol.prototype.description
5Object.fromEntries()
6,可选 Catch

ES1120201Nullish coalescing Operator(空值处理)
空值合并操作符(??) 是一个逻辑操作符,当左侧的操作数为 null 或者 undefined 时,返回其右侧操作数,否则返回左侧操作数。
null ?? 'aaa' // 'aaa
2Optional chaining(可选链)
3Promise.allSettled
4import()
5,新基本数据类型BigInt
BigInt是一种新的数据类型,用于当整数值大于Number数据类型支持的范围时。这种数据类型允许我们安全地对 大整数 执行算术操作,表示高分辨率的时间戳,使用大整数id,等等,而不需要使用库

6,globalThis

ES1220211,replaceAll
2Promise.any
3WeakRefs
4,逻辑运算符和赋值表达式
5,数字分隔符

27,前端跨页面通信,你知道哪些方法?

1LocalStorageSessionStorage
2,cookies
3PostMessage
4Broadcast Channel
5SharedWorker
6IndexedDB
7WebSockets

28,async/await、generator、promise 这三者的关联和区别

async/await:是 ES引入的新特性,可以让异步编程看起来像同步编程,更加易读易写。async/await 只能用于函数内部,不能用于顶层代码(例如全局作用域)

Promise:是 ES6 引入的新特性,使用 then() 方法和 catch() 方法注册回调函数,实现异步编程。Promise 可以使用 race() 方法和 all() 方法处理多个异步操作

Generator:是 ES6 引入的新特性,可以通过 yield 表达式暂停和恢复代码执行,实现异步流程控制。Generator 需要手动执行 next() 方法,才能继续执行下一步操作

29,如何让 Proxy 去监听基本数据类型?

Proxy无法直接监听基本数据类型(如字符串、数字、布尔值等),因为它们是不可变的。Proxy只能在对象级别上进行操作,而不是基本数据类型

30,如果空数组调用reduce会发生什么?

当空数组调用reduce方法时,如果没有提供初始值参数,则会抛出一个TypeError错误。这是因为在空数组上调用reduce方法时,无法得到初始累积值。

31,数组中的reduce方法有用过吗,说说它的具体用途?

reduce方法在JavaScript中是一个高阶函数,用于对数组中的每个元素进行累积操作,最终返回一个单一的值
reduce方法接受两个参数:回调函数和可选的初始值。回调函数在每个数组元素上被调用,并且可以接受四个参数:累积值(上一次回调函数的返回值或初始值)、当前值、当前索引和原始数组
1,如果提供了初始值,则将其作为累积值(accumulator),否则使用数组的第一个元素作为初始累积值
2,对于数组中的每个元素,都调用回调函数,并传递当前累积值、当前值、当前索引和原始数组作为参数
3,回调函数返回的值作为下一次调用的累积值
4,reduce方法返回最后一次调用回调函数的返回值。

32,addEventListener 第三个参数

addEventListener(type, listener); 
addEventListener(type, listener, options); 
addEventListener(type, listener, useCapture); 

options: 可选一个指定有关 listener属性的可选参数对象。
capture
once
passive
signal

useCapture: 可选一个布尔值

33,下面代码的输出是什么?

const obj = {
	fn1: () => console.log(this),
	fn2: function() {console.log(this)}
}

obj.fn1() // window/undefined
obj.fn2() // obj

const x = new obj.fn1() // not constructor
const y = new obj.fn2() // y

34,cookie 的有效时间设置为 0 会怎么样

Cookie过期时间设置为0,表示跟随系统默认,其销毁与Session销毁时间相同,会在浏览器关闭后删除

35,下面代码的输出是什么?

console.log(typeof typeof typeof null); // string
console.log(typeof console.log(1)) // undefined console.log(1)没有返回值

36,canvas 和 webgl 区别

Canvas1Canvas使用2D渲染上下文(2D context)来绘制图形和图像。它基于像素的绘图系统,通过JavaScript脚本控制渲染过程
2Canvas提供了简单的2D图形绘制功能,包括绘制基本形状、路径、文本和图像等。它适用于绘制简单的图形和动画
3,使用Canvas进行2D图形绘制相对简单,仅需基本的JavaScript知识和绘图API的了解即可开始绘制

webgl:
1WebGLWeb Graphics Library)是基于OpenGL ES标准的JavaScript API,它可以利用GPU进行硬件加速的3D图形渲染。WebGL使用着色器(shaders)编程,允许更复杂和高性能的图形渲染
2WebGL提供了强大的3D图形渲染功能,包括高级的着色器编程、纹理映射、深度缓冲、光照效果等。它适用于创建复杂的3D图形、游戏和交互式可视化
3WebGL的编程相对复杂,需要了解着色器编程和3D图形渲染的概念。使用WebGL需要掌握OpenGL ES或类似的图形编程知识。

37,说说下面代码的输出是什么?

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()

1,首先,调用 Foo.a() 方法,输出 4。这是因为 Foo.a 是一个静态方法,直接在函数对象上定义的,所以可以通过函数名直接调用执行
2,然后,创建一个 Foo 类型的实例 obj,调用 obj.a() 方法,输出 2。这是因为在构造函数 Foo 中,使用 this.a 定义了实例属性 a,会覆盖原型中的同名属性
3,最后,再次调用 Foo.a() 方法,输出 1。虽然在上面已经定义了一个静态方法 Foo.a,但是在构造函数 Foo 中又重新定义了一个同名属性,导致静态方法被覆盖了,所以此时输出的是在构造函数中定义的方法

38,浏览器有哪几种缓存,各种缓存的优先级是什么样的?

1,强制缓存:通过设置 Cache-ControlExpires 等响应头实现,可以让浏览器直接从本地缓存中读取资源而不发起请求
2,协商缓存:通过设置 Last-ModifiedETag 等响应头实现,可以让浏览器发送条件请求,询问服务器是否有更新的资源。如果服务器返回 304 Not Modified 响应,则表示客户端本地缓存仍然有效,可直接使用缓存的资源
3Service Worker 缓存:Service Worker 是一种特殊的 JS 脚本,可以拦截网络请求并返回缓存的响应,以实现离线访问和更快的加载速度等功能
4Web Storage 缓存:包括 localStorage 和 sessionStorage。localStorage 用于存储用户在网站上的永久性数据,而 sessionStorage 则用于存储用户会话过程中的临时数据

39,Promise 的 finally 怎么实现的?

Promise.prototype.finally = function(callback) {
	const p = this.constructor
	return this.then(
		value => p.resolve(callback()).then(() => value),
		reason => p.resolve(callback()).then(() => throw reason)
	)
}

40,为什么要区分宏任务和微任务?它们的执行优先级是什么?

宏任务通常包括以下几种:
1setTimeoutsetInterval 定时器
2DOM 事件处理程序
3AJAX 请求的回调函数
4,script 标签的加载和执行

而微任务通常包括以下几种:
1Promise 的 then 方法和 catch 方法
2async/await 中的 await 表达式
3MutationObserver 监听器

对于微任务,JavaScript 引擎也会将其添加到任务队列中,但是微任务的执行在当前宏任务执行结束后立即进行,也就是说微任务具有更高的执行优先级,可以优先于下一个宏任务执行

41,以下等式是成立的吗:1_000_000 === 1000000 ?

 结果为 true
 _,这是数字分隔符规范(Numeric Separators),也就是允许在数字值中使用下划线来提高数值的可读性。

42,页面加载的过程中,JS 文件是不是一定会阻塞 DOM 和 CSSOM 的构建?

不一定
JavaScript文件被放置在head标签内部:
当JavaScript文件被放置在head标签内部时,浏览器会先加载JavaScript文件并执行它,然后才会继续解析HTML文档。因此,如果JavaScript文件过大或服务器响应时间过长,就会导致页面一直处于等待状态,进而影响DOMCSSOM的构建

JavaScript代码修改了DOM结构:
在JavaScript代码执行时,如果对DOM结构进行了修改,那么浏览器需要重新计算布局(reflow)和重绘(repaint),这个过程会较为耗时,并且会阻塞DOMCSSOM的构建

通过设置 script 标签的 async 、defer 属性避免阻塞DOMCSSOM的构建
async:异步加载JavaScript文件,脚本的下载和执行将与其他工作同时进行(例如从服务器请求其他资源、渲染页面等),而不必等到脚本下载完成才开始这些操作。因此,在使用 async 属性时,脚本的加载和执行是异步的,并且不保证脚本在页面中的顺序

defer属性 :属性也告诉浏览器立即下载脚本文件,但有一个重要的区别:当文档解析时,脚本不会执行,直到文档解析完成后才执行。这意味着脚本将按照它们在页面上出现的顺序执行,并且在执行之前,整个文档已经被解析完毕了

Web WorkersWeb Workers 是一种运行在后台线程的JavaScript脚本,它不会阻塞DOMCSSOM的构建,并且可以利用多核CPU提高JavaScript代码执行速度

43,var、let、const之间有什么区别?

三者区别可以围绕下面五点展开:
1,变量提升
var 声明的变量存在变量提升,即变量可以在声明之前调用,值为 undefined
let/const 不存在变量提升,即它们所声明的变量一定要在声明后使用,否则报错

2,暂时性死区
var 不存在暂时性死区
let/const 存在暂时性死区,只有等到声明变量的那一行代码出现,才可以获取和使用该变量

3,块级作用域
var 不存在块级作用域
let/const 存在块级作用域

4,重复声明
var 允许重复声明变量
let/const 在同一作用域不允许重复声明变量

5,修改声明的变量
var/let 可以
const 声明一个只读的常量。一旦声明,常量的值就不能改变

6,使用

44,cookie、localStorage和sessionStorage 三者之间有什么区别

cookie:
1,可设置失效时间,没有设置的话,默认是关闭浏览器后失效
2,4KB左右
3,每次都会携带在HTTP头中,如果使用cookie保存过多数据会带来性能问题

localStorage1,除非被手动清除,否则将会永久保存
2,保存5MB的信息
3,仅在客户端(即浏览器)中保存,不参与和服务器的通信

sessionStorage :
1,仅在当前网页会话下有效,关闭页面或浏览器后就会被清除
2,保存5MB的信息
3,仅在客户端(即浏览器)中保存,不参与和服务器的通信

45,下面代码的输出是什么?

Promise.reject('err!!!')
	.then(
		(res) => {console.log('success', res)},
		(err) => {console.log('error', err)}
	)
	.catch(err => {console.log('catch', err)})

46,js函数有哪几种声明方式?有什么区别?

表达式,声明式
1,函数声明式变量会声明提前 函数表达式变量不会声明提前
2,函数声明中的函数名是必需的,而函数表达式中的函数名则是可选的
3,函数表达式可以在定义的时候直接在表达式后面加()执行,而函数声明则不可以
4,自执行函数即使带有函数名,它里面的函数还是属于函数表达式

47,“立即执行函数”的理解

JS立即执行函数模式是一种语法,可以让你的函数在定义后立即被执行,这种模式本质上就是函数表达式(命名的或者匿名的),在创建后立即执行

1,匿名函数包裹在一个括号运算符中,后面跟一个小括号
(function(){}){}
2,匿名函数后面跟一个小括号,整个包裹在一个括号运算符中
(function(){}())

(),!,+,-,=等运算符都能起到立即执行的作用,这些运算符的作用就是将匿名函数或函数声明转换为函数表达式。

总结:立即执行函数会形成一个单独的作用域,可以封装一些临时变量或者局部变量,避免污染全局变量。

48,怎么解决canvas中获取跨域图片数据的问题?

1,如果使用跨域的资源画到canvas中,并且资源没有使用CORS去请求,canvas会被认为是被污染了, canvas可以正常展示,但是没办法使用toDataURL()或者toBlob()导出数据,见Allowing cross-origin use of images and canvas。 所以通过在img标签上设置crossorigin,启用CORS,属性值为anonymous,在CORS请求时不会发送认证信息,见HTML attribute: crossorigin

2,在启用CORS请求跨域资源时,资源必须允许跨域,才能正常返回,最简单的方式设置响应头Access-Control-Allow-Origin

3,图片已经通过img标签加载过,浏览器默认会缓存下来,下次使用js方式再去请求,直接返回缓存的图片,如果缓存中的图片不是通过CORS 请求或者响应头中不存在Access-Control-Allow-Origin,都会导致报错

49,前端怎么实现跨域请求?

同源策略是一种约定,它是浏览器最核心也最基本的安全功能,如果缺少了同源策略,浏览器很容易受到XSS、CSRF等攻击。所谓同源是指"协议+域名+端口"三者相同,即便两个不同的域名指向同一个ip地址,也非同源

同源策略限制内容有:
1,Cookie、LocalStorage、IndexedDB 等存储性内容
2,DOM 节点
3,AJAX 请求发送后,结果被浏览器拦截了

但是有三个标签是允许跨域加载资源:
1,<img src=XXX>
2,<link href=XXX>
3,<script src=XXX>

跨域有哪些方案:
1document.domain + iframe:适用主域名相同,子域名不同的跨域场景
2window.name + iframe:利用name值最长可以 2M ,并用不同页面或不同域名加载后依然存在的特性
3,location.hash + iframe:适用通过 C 页面来实现 A 页面与 B 页面通信的场景
4,CORS
5,Nginx代理跨域
6,Node中间件代理跨域
7,WebSocket
8,postMessage
9,JSONP

50,Map 和 WeakMap 有什么区别?

Map的特点
1Map默认情况下不包含任何键,所有键都是自己添加进去的。不同于Object原型链上有一写默认的键
2Map的键可以时任何类型数据,就连函数都可以
3Map的键值对个数可以轻易通过size属性获取,Object需要手动计算
4Map在频繁增删键值对的场景下性能比Object更好

WeakMap的特性
1,WeakMap只能将对象作为键名
2,WeakMap的键名引用的对象是弱引用
3,不可遍历

Map和WeakMap区别
1Map的键可以是任意类型,WeakMap只接受对象作为键,不接受其它类型的值作为
2Map的键实际上是跟内存地址绑定的,只要内存地址不一样,就视为两个键;WeakMap的键是弱引用,键所指向的对象是可以被垃圾回收,此时键是无效的
3Map可以被遍历,WeakMap不能被遍历

51,导致页面加载白屏时间长的原因有哪些,怎么进行优化?

白屏的过程
1,首先,在浏览器地址栏中输入url
2,浏览器先查看浏览器缓存-系统缓存-路由器缓存,如果缓存中有,会直接在屏幕中显示页面内容。若没有,则跳到第三步操作
3,在发送http请求前,需要域名解析(DNS解析),解析获取相应的IP地址
4,浏览器向服务器发起tcp连接,与浏览器建立tcp三次握手
5,握手成功后,浏览器向服务器发送http请求,请求数据包
6,服务器处理收到的请求,将数据返回至浏览器
7,浏览器收到HTTP响应
8,读取页面内容,浏览器渲染,解析html源码
9,生成Dom树、解析css样式、js交互,渲染显示页面

白屏-性能优化
1,DNS解析优化
DNS缓存优化
DNS预加载策略
稳定可靠的DNS服务器

2,TCP网络链路优化
多花点钱吧

3,服务端处理优化
服务端的处理优化,是一个非常庞大的话题,会涉及到如Redis缓存、数据库存储优化或是系统内的各种中间件以及Gzip压缩等

4,浏览器下载、解析、渲染页面优化
尽可能的精简HTML的代码和结构
尽可能的优化CSS文件和结构
一定要合理的放置JS代码,尽量不要使用内联的JS代码
将渲染首屏内容所需的关键CSS内联到HTML中,能使CSS更快速地下载。在HTML下载完成之后就能渲染了,页面渲染的时间提前,从而缩短首屏渲染时间
延迟首屏不需要的图片加载,而优先加载首屏所需图片(offsetTop<clientHeight)

52,如何判断页面是通过PC端还是移动端访问?

1,navigator.userAgent
navigator.userAgent属性拿到这个字符串,只要里面包含mobi,android,iphone等关键字,就可以认定是移动设备

这种方法的优点是简单方便,缺点是不可靠,因为用户可以修改这个字符串,让手机浏览器伪装成桌面浏览器

2window.screen,window.innerWidth
对象返回用户设备的屏幕信息,该对象的width属性是屏幕宽度(单位为像素)

另一个属性window.innerWidth返回浏览器窗口里面的网页可见部分的宽度,比较适合指定网页在不同宽度下的样式

缺点在于,如果手机横屏使用

3window.orientation
侦测屏幕方向,手机屏幕可以随时改变方向(横屏或竖屏),桌面设备做不到

注意,iPhone 的 Safari 浏览器不支持该属性。

4,touch 事件
手机浏览器的 DOM 元素可以通过ontouchstart属性,为touch事件指定监听函数。桌面设备没有这个属性

5window.matchMedia()
CSS 通过 media query(媒介查询)为网页指定响应式样式。如果某个针对手机的 media query 语句生效了,就可以认为当前设备是移动设备

6,工具包
react-device-detect

53,怎么把十进制的 0.2 转换成二进制?

1,十进制转二进制:num.toString(2)
2,二进制转十进制:parseInt(num, 2)
(0.2).toString(2)/(0.2).toString(2)

54,如何获取页面的滚动距离值?

在获取页面滚动距离的高度时候,往往有不同的获取方式,而且不同的属性浏览器支持稍有差别:
1,pageYOffset:属window对象,IE9+、Firefox、Chrome、Opera均支持该方式获取页面滚动敢赌值,并且会忽略DOCTYPE定义规则
window.pageYOffset

2,scrollY:属于window对象,Firefox、Chrome、Opera均支持,IE不支持,忽略DOCTYPE定义规则
window.scrollY

3,页面如果未定义DOCTYPE文档头,所有浏览器都支持docume.body.scrollTop属性获取滚动高度
docume.body.scrollTop

4,如果页面定义了DOCTYPE文档头,那么HTML元素上的scrollT属性在IE、Firefox、Opera(presto内核)下都可以获取滚动高度值,而在Chrome和Safari下其值为0
document.documentElement.scrollTop

5,此在获取页面滚动高度的时候优先考虑使用 window.pageYOffset 然后在使用scrollTop
var _scrollLeft = window.scrollX || window.pageXOffset || document.documentElement.scrollLeft

var _scrollTop = window.scrollY || window.pageYOffset || document.documentElement.scrollTop

55,如何顺序执行10个异步任务?

1for 循环 + await
(async () => {
	const sleep = delay = {
		return new Promise((resolve, reject) => {
			setTimeout(_ => resolve(), delay)
		})
	}

	const task = (i) => {
		return new Promise(async (resolve, reject) => {
			await sleep(500)
			console.log(`now is ${i}`)
			++i
			resolve(i)
		})
	}

	let param = 0
	for (let i = 0; i < 4; i++) {
		param = await task(param)
	}
})()

2,Array.prototype.reduce
const sleep = delay = {
	return new Promise((resolve, reject) => {
		setTimeout(_ => resolve(), delay)
	})
}

const task = (i) => {
	return new Promise(async (resolve, reject) => {
		await sleep(500)
		console.log(`now is ${i}`)
		++i
		resolve(i)
	})
}

[task, task, task, task].reduce(async (prev, task) => {
	const res = await prev
	return task(res)
}, 0)

56,JQuery中的$(document).ready与window.onload有什么区别?

windowwindow对象表示浏览器中打开的窗口
window对象可以省略

documentdocument对象是window对象的一部分
浏览器的html文档成为document对象

ready与load执行顺序
1,解析HTML结构
2,加载外部脚本和样式表文件
3,解析并执行脚本代码
4,构造HTML DOM模型 //ready
5,加载图片等外部文件
6,页面加载完毕 //load

两者区别
1,执行时间
$(window).load()必须等到页面内包括图片的所有元素加载完毕后才能执行(比如图片和媒体资源,它们的加载速度远慢于DOM的加载速度)加载完成之后才执行
$(document).ready()是DOM结构绘制完毕后就执行,不必等到加载完毕。但这并不代表页面的所有数据已经全部加载完成,一些大的图片有会在建立DOM树之后很长一段时间才行加载完成

2,编写个数不同
$(window).load()不能同时编写多个,如果有多个
$(document).ready()可以同时编写多个,并且都可以得到执行

3,简化写法
$(window).load()没有简化写法
$(document).ready(function(){})可以简写成$(function(){})或者$().ready(function(){})

4,执行的效率不同
如要在dom的元素节点中添加onclick属性节点,这时用$(document).ready()就要比用$(window).load()的效率高
但是在某些时候还必须得用$(window).load()才行,比如按钮图片出现后添加事件

57,如何让Promise.all在抛出异常后依然有效

1,在promise.all队列中,使用map每一个过滤每一个promise任务,其中任意一个报错后,return一个返回值,确保promise能正常执行走到.then中
var p1 = new Promise((resolve, reject) => {
	resolve('p1')
})
var p2 = new Promise((resolve, reject) => {
	reject('p2')
})
Promise.all([p1, p2]).map(p => p.catch(e => '报错')))
	.then(values => {
		console.log(values)
	})
	.catch(err => { console.log(err) })

2,使用 Promise.allSettled 替代 Promise.all()
方法返回一个promise,该promise在所有给定的promise已被解析或被拒绝后解析,并且每个对象都描述每个promise的结果。

58,介绍一下 setTimeout 的运行机制

setTimeout()函数:用来指定某个函数或某段代码在多少毫秒之后执行。它返回一个整数,表示定时器timer的编号,可以用来取消该定时器

1,所有同步任务都在主线程上执行,形成一个执行栈(Call Stack)
2,主线程之外,还存在一个"任务队列"(task queue)。只要异步任务有了运行结果,就在"任务队列"之中放置一个事件
3,一旦"执行栈"中的所有同步任务执行完毕,系统就会读取"任务队列",看看里面有哪些事件。那些对应的异步任务,于是结束等待状态,进入执行栈,开始执行
4,主线程不断重复上面的第三步

setTimeout 和 setInterval的运行机制,其实就是将指定的代码移出本次执行,等到下一轮 Event Loop 时,再检查是否到了指定时间。如果到了,就执行对应的代码;如果不到,就等到再下一轮 Event Loop 时重新判断
这意味着,setTimeout指定的代码,必须等到本次执行的所有同步代码都执行完,才会执行

59,['1','2','3'].map(parseInt) 的返回值是什么?

1,数组的map函数,接受三个参数,当前值,当前索引,当前数组
2,parseInt接受两个参数,需要转换的字符串,基数(基数取值范围2~36var new_arrary = arr.map(function callback(currentValue, index, array) {
	// Return new_arry
})
parseInt(string, radix)

['1','2','3'].map((item, index) => {
	return parseInt(item, index)
})
// parseInt('1', 0) 1
// parseInt('2', 1) NaN
// parseInt('3', 2) NaN

4.如果我们需要返回123需要怎么办?
function parseIntFun(item) {
	return parsetInt(item, 10)
}
['1','2','3'].map(parseIntFun)

60,写一个返回数据类型的函数,要求自定义的类实例化的对象返回定义的类名

1,typeof
由于由于历史原因,在判断原始类型时,typeof null会等于object。而且对于对象(Object)、数组(Array)来说,都会转换成object

2,instanceof
instanceof是通过原型链来判断的,但是对于对象来说,Array也会被转换成Object,而且也不能区分基本类型string和boolean。可以左边放你要判断的内容,右边放类型来进行JS类型判断,只能用来判断复杂数据类型,因为instanceof 是用于检测构造函数(右边)的 prototype 属性是否出现在某个实例对象(左边)的原型链上

3,Constructor
constructor 判断方法跟instanceof相似,但是constructor检测Object与instanceof不一样,constructor还可以处理基本数据类型的检测,不仅仅是对象类型

4,Array.isArray()
Array.isArray() 用于确定传递的值是否是一个 Array。如果对象是 Array ,则返回true,否则为false

5Object.prototype.toString.call()

61,连续 bind()多次,输出的值是什么?

var bar = function() {
	console.log(this.x)
}
var foo = {
	x: 3
}
var sed = {
	x: 4
}

var func = bar.bind(foo).bind(sed)
func()  // 3

var fiv = {
	x: 5
}
var func = bar.bind(foo).bind(sed).bind(fiv)
func() // 3

在Javascript中,多次 bind() 是无效的
更深层次的原因, bind() 的实现,相当于使用函数在内部包了一个 call/apply ,第二次 bind()  相当于再包住第一次 bind() ,故第二次以后的 bind 是无法生效的

62,ajax、axios、fetch有什么区别?

AJAX:
它是一种在无需重新加载整个网页的情况下,能够更新部分网页的技术。通过在后台与服务器进行少量数据交换,Ajax 可以使网页实现异步更新。这意味着可以在不重新加载整个网页的情况下,对网页的某部分进行更新。传统的网页(不使用 Ajax)如果需要更新内容,必须重载整个网页页面。其缺点如下:
1,本身是针对MVC编程,不符合前端MVVM的浪潮
2,基于原生XHR开发,XHR本身的架构不清晰
3,不符合关注分离(Separation of Concerns)的原则
4,配置和调用方式非常混乱,而且基于事件的异步模型不友好

Fetch:
fetch号称是AJAX的替代品,是在ES6出现的,使用了ES6中的promise对象。Fetch是基于promise设计的。Fetch的代码结构比起ajax简单多。fetch不是ajax的进一步封装,而是原生js,没有使用XMLHttpRequest对象
优点
1,语法简洁,更加语义化
2,基于标准 Promise 实现,支持 async/await
3,更加底层,提供的API丰富(request, response)
4,脱离了XHR,是ES规范里新的实现方式
缺点
1,fetch只对网络请求报错,对400500都当做成功的请求,服务器返回 400500 错误码时并不会 reject,只有网络错误这些导致请求不能完成时,fetch 才会被 reject
2,fetch默认不会带cookie,需要添加配置项: fetch(url, {credentials: 'include'})
3,fetch不支持abort,不支持超时控制,使用setTimeout及Promise.reject的实现的超时控制并不能阻止请求过程继续在后台运行,造成了流量的浪费
4,fetch没有办法原生监测请求的进度,而XHR可以

Axios
1,浏览器端发起XMLHttpRequests请求
2,node端发起http请求
3,支持Promise API
4,监听请求和返回
5,对请求和返回进行转化
6,取消请求
7,自动转换json数据
8,客户端支持抵御XSRF攻击

63,将数组的length设置为0,取第一个元素会返回什么?

length = 0  会清空数组,所以会返回 undefined

64,CSS动画和JS实现的动画分别有哪些优缺点?

CSS动画
优点:
1,浏览器可以对动画进行优化
2,代码相对简单,性能调优方向固定
3,对于帧速表现不好的低版本浏览器,CSS3可以做到自然降级,而JS则需要撰写额外代码
缺点:
1,运行过程控制较弱,无法附加事件绑定回调函数
2,代码冗长,想用CSS实现稍微复杂一点动画,最后CSS代码都会变得非常笨重

JS动画
优点:
1,控制能力很强, 可以在动画播放过程中对动画进行控制:开始、暂停、回放、终止、取消都是可以做到的
2,动画效果比css3动画丰富,有些动画效果,比如曲线运动,冲击闪烁,视差滚动效果,只有js动画才能完成
3,CSS3有兼容性问题,而JS大多时候没有兼容性问题
缺点:
1,代码的复杂度高于CSS动画
2,在浏览器的主线程中运行,而主线程中还有其它需要运行的JavaScript脚本、样式计算、布局、绘制任务等,对其干扰导致线程可能出现阻塞,从而造成丢帧的情况

65,前端实现动画有哪些方式?

css3的
1,transition 属性
用来设置样式的属性值是如何从一种状态平滑过渡到另外一种状态
只需指定动画的开始和结束状态,整个动画的过程是由特定的函数控制
transition: property duration timing-function delay;

2,animation 属性
可以对动画过程中的各个关键帧进行设置

3,原生JS动画
4,使用canvas绘制动画
5,SVG动画
6,Jquery的animate函数
7,使用gif图片

66,给一个dom同时绑定两个点击事件,一个用捕获,一个用冒泡,说下会执行几次事件,然后会先执行冒泡还是捕获?

addEventListener绑定几次就执行几次
先捕获,后冒泡

67,promise.catch后面的.then还会执行吗?

会继续执行
.catch只会处理rejected的情况,并且也会返回一个新的Promise实例
.catch(onRejected)与then(undefined, onRejected)then(undefined, onRejected)

68,如何获取到一个实例对象的原型对象?

1,构造函数 获得 原型对象:
构造函数.prototype

2,对象实例 获得 父级原型对象
方法一: 对象实例.__proto__
        【 有兼容性问题,不建议使用】
方法二:Object.getPrototypeOf( 对象实例 )

69,jquery的链式调用是怎么实现的?

链式调用的核心就在于调用完的方法将自身实例返回

70,如何实现浏览器内多个标签页之间的通信?

1,Broadcast Channel
2,localStorage
3,SharedWorker
4,WebSocket通讯
5,定时器setInterval+cookie
6,postMessage

71,Promise相关知识

1async函数中awaitnew Promise要是没有返回值的话则不执行后面的内容
2,.then函数中的参数期待的是函数,如果不是函数的话会发生透传
3,在a1中await后面的Promise是没有返回值的,也就是它的状态始终是pending状态,因此相当于一直在awaitawaitawait却始终没有响应...
async function a1() {
	await new Promise(resolve => {
		console.log('promise1')
	})
	// 不执行
	console.log(333)
}
4,Promise 的 .then 或者 .catch 可以被调用多次,但 Promise 构造函数只执行一次。或者说 promise 内部状态一经改变,并且有了一个值,那么后续每次调用 .then 或者 .catch 都会直接拿到该值

72,说说你对以下几个页面生命周期事件的理解:DOMContentLoaded,load,beforeunload,unload

1,DOMContentLoaded —— 浏览器已完全加载 HTML,并构建了 DOM 树,但像 <img> 和样式表之类的外部资源可能尚未加载完成
2,load —— 浏览器不仅加载完成了 HTML,还加载完成了所有外部资源:图片,样式等
3,beforeunload/unload —— 当用户正在离开页面时

总结:
1,当 DOM 准备就绪时,document 上的 DOMContentLoaded 事件就会被触发。在这个阶段,我们可以将 JavaScript 应用于元素
诸如 <script>...</script> 之类的脚本会阻塞 DOMContentLoaded,浏览器将等待它们执行结束。
图片和其他资源仍然可以继续被加载。
2,当页面和所有资源都加载完成时,window 上的 load 事件就会被触发。我们很少使用它,因为通常无需等待那么长时间。
3,当用户想要离开页面时,window 上的 beforeunload 事件就会被触发。如果我们取消这个事件,浏览器就会询问我们是否真的要离开(例如,我们有未保存的更改)。
4,当用户最终离开时,window 上的 unload 事件就会被触发。在处理程序中,我们只能执行不涉及延迟或询问用户的简单操作。正是由于这个限制,它很少被使用。我们可以使用 navigator.sendBeacon 来发送网络请求。

73,js中的undefined和 ReferenceError: xxx is not defined 有什么区别?

1,ReferenceError:当尝试引用一个未定义的变量/函数时,就会抛出ReferenceError。
2,undefined:当一个变量声明后,没有被赋值,那么它就是undefined类型。

74,Math.ceil()、Math.round()、Math.floor()三者的区别是什么?

1,Math.ceil()上取整
2,Math.round() 四舍五入
3,Math.floor()下取整

75,解释下如下代码的意图:Array.prototype.slice.apply(arguments)

arguments 为类数组对象,并不是真正的数组。
slice可以实现数组的浅拷贝。
由于 arguments不是真正的数组,所以没有slice方法,通过apply可以调用数组对象的slice方法,从而将arguments 类数组转换为数组。

76,直接在script标签中写 export 为什么会报错?

现代浏览器可以支持用 script 标签引入模块或者脚本,如果要引入模块,必须给 script 标签添加 type=“module”。如果引入脚本,则不需要 type

77,offsetWidth/offsetHeight,clientWidth/clientHeight 与 scrollWidth/scrollHeight 的区别?

1,clientWidth/clientHeight 返回的是元素的内部宽度,它的值只包含 content + padding,如果有滚动条,不包含滚动条。
2,clientTop 返回的是上边框的宽度。
3,clientLeft 返回的左边框的宽度。
4,offsetWidth/offsetHeight 返回的是元素的布局宽度,它的值包含 content + padding + border 包含了滚动条。
5,offsetTop 返回的是当前元素相对于其 offsetParent 元素的顶部的距离。
6,offsetLeft 返回的是当前元素相对于其 offsetParent 元素的左部的距离。
7,scrollWidth/scrollHeight 返回值包含 content + padding + 溢出内容的尺寸。
8,scrollTop 属性返回的是一个元素的内容垂直滚动的像素数。
9,scrollLeft 属性返回的是元素滚动条到元素左边的距离。

78,toPrecision 和 toFixed 和 Math.round 有什么区别?

1,toPrecision 用于处理精度,精度是从左至右第一个不为 0 的数开始数起。
2,toFixed 是对小数点后指定位数取整,从小数点开始数起。
3,Math.round 是将一个数字四舍五入到一个整数。

79,什么是 Polyfill ?

Polyfill 指的是用于实现浏览器并不支持的原生 API 的代码。
比如说 querySelectorAll 是很多现代浏览器都支持的原生 Web API,但是有些古老的浏览器并不支持,那么假设有人写了一段代码来实现这个功能使这些浏览器也支持了这个功能,那么这就可以成为一个 Polyfill。

80,怎么检测浏览器版本?

一种是检测 window.navigator.userAgent 的值,但这种方式很不可靠,因为 userAgent 可以被改写,并且早期的浏览器如 ie,会通过伪装自己的 userAgent 的值为 Mozilla 来躲过服务器的检测。
第二种方式是功能检测,根据每个浏览器独有的特性来进行判断,如 ie 下独有的 ActiveXObject

81,什么是“前端路由”?什么时候适合使用“前端路由”?“前端路由”有哪些优点和缺点?

1,前端路由就是把不同路由对应不同的内容或页面的任务交给前端来做,之前是通过服务端根据 url 的不同返回不同的页面实现的。
2,在单页面应用,大部分页面结构不变,只改变部分内容的使用
3,前端路由一共有两种实现方式,一种是通过 hash 的方式,一种是通过使用 pushState 的方式。

82,什么是点击穿透,怎么解决?

在发生触摸动作约300ms之后,移动端会模拟产生click动作,它底下的具有点击特性的元素也会被触发,这种现象称为点击穿透。
方法一:书写规范问题,不要混用touch和click。既然touch之后300ms会触发click,只用touch或者只用click就自然不会存在问题了。
方法二:吃掉(或者说是消费掉)touch之后的click,依旧用tap,只是在可能发生点击穿透的情形做额外的处理,拿个东西来挡住、或者tap后延迟350毫秒再隐藏mask、pointer-events、在下面元素的事件处理器里做检测(配合全局flag)等。

83,移动端的点击事件的有延迟,时间是多久,为什么会有? 怎么解决这个延时?

移动端点击有 300ms 的延迟是因为移动端会有双击缩放的这个操作,因此浏览器在 click 之后要等待 300ms,看用户有没有下一次点击,来判断这次操作是不是双击。
1,通过 meta 标签禁用网页的缩放。
2,通过 meta 标签将网页的 viewport 设置为 ideal viewport。
3,调用一些 js 库,比如 FastClick
click 延时问题还可能引起点击穿透的问题,就是如果我们在一个元素上注册了 touchStart 的监听事件,这个事件会将这个元素隐藏掉,我们发现当这个元素隐藏后,触发了这个元素下的一个元素的点击事件,这就是点击穿透。

84,Promise.all 和 Promise.allSettled 有什么区别?

Promise.allSettled永远不会被reject。

85,JS中怎么阻止事件冒泡和默认事件?

1,event.stopPropagation()方法
这是阻止事件的冒泡方法,不让事件向 document 上蔓延,但是默认事件任然会执行,当你掉用这个方法的时候,如果点击一个连接,这个连接仍然会被打开

2,event.preventDefault()方法
这是阻止默认事件的方法,比如在a标签的绑定事件上调用此方法,链接则不会被打开,但是会发生冒泡,冒泡会传递到上一层的父元素

3return false
这个方法比较暴力,他会同时阻止事件冒泡也会阻止默认事件;写上此代码,连接不会被打开,事件也不会传递到上一层的父元素;可以理解为return false就等于同时调用了event.stopPropagation()和event.preventDefault()

86,浏览器的垃圾回收机制有哪些?

JS会在创建变量时自动分配内存,在不使用的时候会自动周期性的释放内存,释放的过程就叫 "垃圾回收"。

一方面自动分配内存减轻了开发者的负担,开发者不用过多的去关注内存使用,但是另一方面,正是因为因为是自动回收,所以如果不清楚回收的机制,会很容易造成混乱,而混乱就很容易造成"内存泄漏"。

由于是自动回收,所以就存在一个 "内存是否需要被回收的" 的问题,但是这个问题的判定在程序中意味着无法通过某个算法去准确完整的解决,后面探讨的回收机制只能有限的去解决一般的问题。

回收算法
垃圾回收对是否需要回收的问题主要依赖于对变量的判定是否可访问,由此衍生出两种主要的回收算法:
1,标记清理
2,引用计数

标记清理
标记清理是js最常用的回收策略,2012年后所有浏览器都使用了这种策略,此后的对回收策略的改进也是基于这个策略的改进。其策略是:
1,变量进入上下文,也可理解为作用域,会加上标记,证明其存在于该上下文;
2,将所有在上下文中的变量以及上下文中被访问引用的变量标记去掉,表明这些变量活跃有用;
3,在此之后再被加上标记的变量标记为准备删除的变量,因为上下文中的变量已经无法访问它们;
4,执行内存清理,销毁带标记的所有非活跃值并回收之前被占用的内存;

局限:
1,由于是从根对象(全局对象)开始查找,对于那些无法从根对象查询到的对象都将被清除
2,回收后会形成内存碎片,影响后面申请大的连续内存空间

引用计数
引用计数策略相对而言不常用,因为弊端较多。其思路是对每个值记录它被引用的次数,通过最后对次数的判断(引用数为0)来决定是否保留,具体的规则有:
1,声明一个变量,赋予它一个引用值时,计数+12,同一个值被赋予另外一个变量时,引用+13,同一个值被赋予另外一个变量时,引用+14,引用为0,回收内存;

87,xml和json有什么区别?

1、JSON是JavaScript Object Notation;XML是可扩展标记语言。
2、JSON是基于JavaScript语言;XML源自SGML。
3、JSON是一种表示对象的方式;XML是一种标记语言,使用标记结构来表示数据项。
4、JSON不提供对命名空间的任何支持;XML支持名称空间。
5、JSON支持数组;XML不支持数组。
6、XML的文件相对难以阅读和解释;与XML相比,JSON的文件非常易于阅读。
7、JSON不使用结束标记;XML有开始和结束标签。
8、JSON的安全性较低;XML比JSON更安全。
9、JSON不支持注释;XML支持注释。
10、JSON仅支持UTF-8编码;XML支持各种编码。

88,document.write和innerHTML有什么区别

1,document.write是直接写入到页面的内容流,如果在写之前没有调用document.open, 浏览器会自动调用open。每次写完关闭之后重新调用该函数,会导致页面被重写。
2,innerHTML则是DOM页面元素的一个属性,代表该元素的html内容。你可以精确到某一个具体的元素来进行更改。如果想修改document的内容,则需要修改document.documentElement.innerElement。
3,innerHTML将内容写入某个DOM节点,不会导致页面全部重绘
4,innerHTML很多情况下都优于document.write,其原因在于其允许更精确的控制要刷新页面的那一个部分。

89,123['toString'].length + 123 的输出值是多少?

function的length
function的length,就是第一个具有默认值之前的参数个数。

function fn1(name) {} // 1
function fn2 (name = '123') {} // 0
function fn3 (name, age = 22) {} // 1
function fn4 (name, age = 22, gender) {} // 1
function fn5 (name, age, sex = '213') {} // 2

length 是函数对象的一个属性值,指该函数有多少个必须要传入的参数,即形参的个数。形参的数量不包括剩余参数个数,仅包括第一个具有默认值之前的参数个数

90,for...in和for...of有什么区别?

for…of 是ES6新增的遍历方式,允许遍历一个含有iterator接口的数据结构(数组、对象等)并且返回各项的值,和ES3中的for…in的区别如下:
1for…of 遍历获取的是对象的键值,for…in 获取的是对象的键名;
2for… in 会遍历对象的整个原型链,性能非常差不推荐使用,而 for … of 只遍历当前对象不会遍历原型链;
3,对于数组的遍历,for…in 会返回数组中所有可枚举的属性(包括原型链上可枚举的属性),for…of 只返回数组的下标对应的属性值;

总结: for...in 循环主要是为了遍历对象而生,不适用于遍历数组;for...of 循环可以用来遍历数组、类数组对象,字符串、Set、Map 以及 Generator 对象。

91,JavaScript脚本延迟加载的方式有哪些?

1,defer 属性: 给 js 脚本添加 defer 属性,这个属性会让脚本的加载与文档的解析同步解析,然后在文档解析完成后再执行这个脚本文件,这样的话就能使页面的渲染不被阻塞。多个设置了 defer 属性的脚本按规范来说最后是顺序执行的,但是在一些浏览器中可能不是这样。
2,async 属性: 给 js 脚本添加 async 属性,这个属性会使脚本异步加载,不会阻塞页面的解析过程,但是当脚本加载完成后立即执行 js 脚本,这个时候如果文档没有解析完成的话同样会阻塞。多个 async 属性的脚本的执行顺序是不可预测的,一般不会按照代码的顺序依次执行。
3,动态创建 DOM 方式: 动态创建 DOM 标签的方式,可以对文档的加载事件进行监听,当文档加载完成后再动态的创建 script 标签来引入 js 脚本。
4,使用 setTimeout 延迟方法: 设置一个定时器来延迟加载js脚本文件
5,让 JS 最后加载: 将 js 脚本放在文档的底部,来使 js 脚本尽可能的在最后来加载执行。

92,如果new一个箭头函数会怎么样?

箭头函数是ES6中的提出来的,它没有prototype,也没有自己的this指向,更不可以使用arguments参数,所以不能New一个箭头函数。

new操作符的实现步骤如下:
1、创建一个空的简单JavaScript对象(即{})
2、为步骤1新创建的对象添加属性__proto__,将该属性链接至构造函数的原型对象 
3、将步骤1新创建的对象作为this的上下文 
4、如果该函数没有返回对象,则返回this

93,object.assign和扩展运算法是深拷贝还是浅拷贝,两者区别是什么?

Object.assign()方法接收的第一个参数作为目标对象,后面的所有参数作为源对象。然后把所有的源对象合并到目标对象中。它会修改了一个对象,因此会触发 ES6 setter。

扩展操作符(…)使用它时,数组或对象中的每一个值都会被拷贝到一个新的数组或对象中。它不复制继承的属性或类的属性,但是它会复制ES6的 symbols 属性。

94,typeof NaN 的结果是什么?

NaN 指“不是一个数字”(not a number),NaN 是一个“警戒值”(sentinel value,有特殊用途的常规值),用于指出数字类型中的错误情况,即“执行数学运算没有成功,这是失败后返回的结果”。

NaN 是一个特殊值,它和自身不相等,是唯一一个非自反(自反,reflexive,即 x === x 不成立)的值。而 NaN !== NaN 为 true

95,Object.is() 与比较操作符 “===”、“==” 的区别?

1,使用双等号(==)进行相等判断时,如果两边的类型不一致,则会进行强制类型转化后再进行比较。
2,使用双等号(==)进行相等判断时,如果两边的类型不一致,则会进行强制类型转化后再进行比较。
3,使用三等号(===)进行相等判断时,如果两边的类型不一致时,不会做强制类型准换,直接返回 false

96,isNaN 和 Number.isNaN 函数有什么区别?

函数 isNaN 接收参数后,会尝试将这个参数转换为数值,任何不能被转换为数值的的值都会返回 true,因此非数字值传入也会返回 true ,会影响 NaN 的判断。

函数 Number.isNaN 会首先判断传入参数是否为数字,如果是数字再继续判断是否为 NaN ,不会进行数据类型的转换,这种方法对于 NaN 的判断更为准确。

和全局函数 isNaN() 相比,Number.isNaN() 不会自行将参数转换成数字,只有在参数是值为 NaN 的数字时,才会返回 true。

Number.isNaN() 方法确定传递的值是否为NaN,并且检查其类型是否为Number。它是原来的全局isNaN() 的更稳妥的版本。

97,如何使用js计算一个html页面有多少种标签?

1,获取所有的DOM节点
document.querySelectorAll('*')
此时得到的是一个NodeList集合,我们需要将其转化为数组,然后对其筛选。

2,转化为数组
[...document.querySelectorAll('*')]

3,获取数组每个元素的标签名
[...document.querySelectorAll('*')].map(ele => ele.tagName)

4,去重
new Set([...document.querySelectorAll('*')].map(ele => ele.tagName)).size

98,介绍一下 tree shaking 及其工作原理

因为tree shaking只能在静态modules下工作。ECMAScript 6 模块加载是静态的,因此整个依赖树可以被静态地推导出解析语法树。所以在 ES6 中使用 tree shaking 是非常容易的。

ES6 Module引入进行静态分析,故而编译的时候正确判断到底加载了那些模块
静态分析程序流,判断那些模块和变量未被使用或者引用,进而删除对应代码

99,base64编码图片,为什么会让数据量变大?

Base64编码的思想是是采用64个基本的ASCII码字符对数据进行重新编码。它将需要编码的数据拆分成字节数组。以3个字节为一组。按顺序排列24位数据,再把这24位数据分成4组,即每组6位。再在每组的的最高位前补两个0凑足一个字节。这样就把一个3字节为一组的数据重新编码成了4个字节。当所要编码的数据的字节数不是3的整倍数,也就是说在分组时最后一组不够3个字节。这时在最后一组填充120字节。并在最后编码完成后在结尾添加12"="。

从以上编码规则可以得知,通过Base64编码,原来的3个字节编码后将成为4个字节,即字节增加了33.3%,数据量相应变大。所以20M的数据通过Base64编码后大小大概为20M*133.3%=26.67M。

100,谈谈对 window.requestAnimationFrame 的理解

window.requestAnimationFrame() 告诉浏览器——你希望执行一个动画,并且要求浏览器在下次重绘之前调用指定的回调函数更新动画。该方法需要传入一个回调函数作为参数,该回调函数会在浏览器下一次重绘之前执行。

与setTimeout相比,requestAnimationFrame最大的优势是由系统来决定回调函数的执行时机。具体一点讲,如果屏幕刷新率是60Hz,那么回调函数就每16.7ms被执行一次,如果刷新率是75Hz,那么这个时间间隔就变成了1000/75=13.3ms,换句话说就是,requestAnimationFrame的步伐跟着系统的刷新步伐走。它能保证回调函数在屏幕每一次的刷新间隔中只被执行一次,这样就不会引起丢帧现象,也不会导致动画出现卡顿的问题。

requestAnimationFrame还有以下两个优势:
1,CPU节能:使用setTimeout实现的动画,当页面被隐藏或最小化时,setTimeout 仍然在后台执行动画任务,由于此时页面处于不可见或不可用状态,刷新动画是没有意义的,完全是浪费CPU资源。而requestAnimationFrame则完全不同,当页面处理未激活的状态下,该页面的屏幕刷新任务也会被系统暂停,因此跟着系统步伐走的requestAnimationFrame也会停止渲染,当页面被激活时,动画就从上次停留的地方继续执行,有效节省了CPU开销。
2,函数节流:在高频率事件(resize,scroll等)中,为了防止在一个刷新间隔内发生多次函数执行,使用requestAnimationFrame可保证每个刷新间隔内,函数只被执行一次,这样既能保证流畅性,也能更好的节省函数执行的开销。一个刷新间隔内函数执行多次时没有意义的,因为显示器每16.7ms刷新一次,多次绘制并不会在屏幕上体现出来。

101,谈谈 Object.defineProperty 与 Proxy 的区别

在 Vue2.x 的版本中,双向绑定是基于 Object.defineProperty 方式实现的。而 Vue3.x 版本中,使用了 ES6 中的 Proxy 代理的方式实现。

Object.defineProperty(obj, prop, descriptor)
使用 Object.defineProperty 会产生三个主要的问题:
1,不能监听数组的变化
在 Vue2.x 中解决数组监听的方法是将能够改变原数组的方法进行重写实现(比如:push、 pop、shift、unshift、splice、sort、reverse),举例:
2,必须遍历对象的每个属性
可以通过 Object.keys() 来实现
3,必须深层遍历嵌套的对象
通过递归深层遍历嵌套对象,然后通过 Object.keys() 来实现对每个属性的劫持

Proxy
1,Proxy 针对的整个对象,Object.defineProperty 针对单个属性,这就解决了 需要对对象进行深度递归(支持嵌套的复杂对象劫持)实现对每个属性劫持的问题
2,Proxy 解决了 Object.defineProperty 无法劫持数组的问题

102,html文档渲染过程,css文件和js文件的下载,是否会阻塞渲染?

浏览器内有多个进程,其中渲染进程被称为浏览器内核,负责页面渲染和执行 JS 脚本等。渲染进程负责浏览器的解析和渲染,内部有 JS 引擎线程、 GUI 渲染线程、事件循环管理线程、定时器线程、HTTP 线程。

JS 引擎线程负责执行 JS 脚本,GUI 渲染线程负责页面的解析和渲染,两者是互斥的,也就是执行 JS 的时候页面是停止解析和渲染的。这是因为如果在页面渲染的同时 JS 引擎修改了页面元素,比如清空页面,会造成后续页面渲染的不必要和错误。而由于 JS 经常要操作 DOM ,就要涉及 JS 引擎线程和 GUI 渲染线程的通信,而线程间通信代价是非常昂贵的,这也是造成 JS 操作 DOM 效率不高的原因。

浏览器的 HTML/CSS 的解析和渲染都属于 GUI渲染线程,所以和 JS 引擎线程是互斥、阻塞的。下面从代码实际运行的角度分析浏览器解析和渲染的顺序,以及互相间的阻塞关系。

CSS 阻塞
1,css 文件的下载和解析不会影响 DOM 的解析,但是会阻塞 DOM 的渲染。因为 CSSOM Tree 要和 DOM Tree 合成 Render Tree 才能绘制页面
2,css 文件没下载并解析完成之前,后续的 js 脚本不能执行
3,css 文件的下载不会阻塞前面的 js 脚本执行

js 阻塞
1,js 文件的下载和解析会阻塞 GUI 渲染进程,也就是会阻塞 DOM 和 CSS 的解析和渲染
2,js 文件没下载并解析完成之前,后续的 HTML 和 CSS 无法解析
3

103,为什么JavaScript是单线程?

JavaScript语言的一大特点就是单线程,也就是说,同一个时间只能做一件事

为了利用多核CPU的计算能力,HTML5提出Web Worker标准,允许JavaScript脚本创建多个线程,但是子线程完全受主线程控制,且不得操作DOM。所以,这个新标准并没有改变JavaScript单线程的本质。

104,ES6中的 Reflect 对象有什么用?

Reflect 对象不是构造函数,所以创建时不是用 new 来进行创建

1,将 Object 对象的一些明显属于语言内部的方法(比如 Object.defineProperty),放到 Reflect 对象上。现阶段,某些方法同时在 Object 和 Reflect 对象上部署,未来的新方法将只部署在 Reflect 对象上。也就是说,从 Reflect 对象上可以拿到语言内部的方法

2,修改某些 Object 方法的返回结果,让其变得更合理。比如,Object.defineProperty(obj, name, desc)在无法定义属性时,会抛出一个错误,而 Reflect.defineProperty(obj, name, desc)则会返回 false

3,让 Object 操作都变成函数行为。某些 Object 操作是命令式,比如 name in obj 和 delete obj[name],而 Reflect.has(obj, name)和 Reflect.deleteProperty(obj, name)让它们变成了函数行为

4,Reflect 对象的方法与 Proxy 对象的方法一一对应,只要是 Proxy 对象的方法,就能在 Reflect 对象上找到对应的方法。这就让 Proxy 对象可以方便地调用对应的 Reflect 方法,完成默认行为,作为修改行为的基础。也就是说,不管 Proxy 怎么修改默认行为,你总可以在 Reflect 上获取默认行为

105,简单介绍下 ES6 中的 Iterator 迭代器

Iterator迭代器就是为了解决不同的数据结构提供统一的访问机制。(目前Map、Set、Array支持Iterator)

Iterator规范————Iterator迭代器包含一个next()方法,方法调用返回返回两个属性:done和value;通过定义一个对象的Symbol.iterator属性,即可将此对象修改为迭代器对象,支持for...of遍历

106,js对象中,可枚举性(enumerable)是什么?

可枚举性(enumerable)用来控制所描述的属性,是否将被包括在for...in循环之中(除非属性名是一个Symbol)。具体来说,如果一个属性的enumerable为false,下面三个操作不会取到该属性

1for..in循环
2,Object.keys方法
3,JSON.stringify方法

可枚举属性是指那些内部 “可枚举” 标志设置为 true 的属性。对于通过直接的赋值和属性初始化的属性,该标识值默认为即为 true。但是对于通过 Object.defineProperty 等定义的属性,该标识值默认为 false

107,如何中断Promise?

Promise 有个缺点就是一旦创建就无法取消,所以本质上 Promise 是无法被终止的

1,中断调用链
就是在某个 then/catch 执行之后,不想让后续的链式调用继续执行了
一种方法是在then中直接抛错, 这样就不会执行后面的then, 直接跳到catch方法打印err(但此方法并没有实际中断)。但如果链路中对错误进行了捕获,后面的then函数还是会继续执行

2,中断Promise
注意这里是中断而不是终止,因为 Promise 无法终止,这个中断的意思是:在合适的时候,把 pending 状态的 promise 给 reject 掉。例如一个常见的应用场景就是希望给网络请求设置超时时间,一旦超时就就中断,我们这里用定时器模拟一个网络请求,随机 3 秒之内返回

108,“严格模式”是什么?

除了正常运行模式,ECMAscript 5添加了第二种运行模式:"严格模式"(strict mode)。顾名思义,这种模式使得Javascript在更严格的条件下运行。

1,消除Javascript语法的一些不合理、不严谨之处,减少一些怪异行为;
2,消除代码运行的一些不安全之处,保证代码运行的安全;
3,提高编译器效率,增加运行速度;
4,为未来新版本的Javascript做好铺垫。

109,Object.create 和 new 有什么区别?

js中创建对象的方式一般有两种Object.create和new
在讲述两者区别之前,我们需要知道:
1,构造函数Foo的原型属性Foo.prototype指向了原型对象
2,构造函数Foo的原型属性Foo.prototype指向了原型对象
3,js中只有函数有 prototype 属性,所有的对象只有 proto 隐式属性

110,为什么部分请求中,参数需要使用 encodeURIComponent 进行转码?

一般来说,URL只能使用英文字母、阿拉伯数字和某些标点符号,不能使用其他文字和符号

encodeURIComponent()中统统会被编码
它对应的解码函数是decodeURIComponent()。

111,JS代码中的use strict是什么意思?

use strict是一种ECMAscript5添加的(严格)运行模式,这种模式使得Javascript 在更严格的条件下运行

1,消除Javascript语法的一些不合理、不严谨之处,减少一些怪异行为;消除代码运行的一些不安全之处,保证代码运行的安全
2,提高编译器效率,增加运行速度;
3,为未来新版本的Javascript 做好铺垫。

区别
1,禁止使用with语句。
2,禁止使用with语句。
3,对象不能有重名的属性。

112,什么是变量提升

函数在运行的时候,会首先创建执行上下文,然后将执行上下文入栈,然后当此执行上下文处于栈顶时,开始运行执行上下文。

在创建执行上下文的过程中会做三件事:创建变量对象,创建作用域链,确定 this 指向,其中创建变量对象的过程中,首先会为 arguments 创建一个属性,值为 arguments,然后会扫码 function 函数声明,创建一个同名属性,值为函数的引用,接着会扫码 var 变量声明,创建一个同名属性,值为 undefined,这就是变量提升。

113,箭头函数和普通函数有啥区别?箭头函数能当构造函数吗?

1、语法更加简洁、清晰
2、箭头函数不会创建自己的this
3、箭头函数继承而来的this指向永远不变
4、.call()/.apply()/.bind()无法改变箭头函数中this的指向
5、箭头函数不能作为构造函数使用
new操作:
① JS内部首先会先生成一个对象; 
② 再把函数中的this指向该对象; 
③ 然后执行构造函数中的语句; 
④ 最终返回该对象实例。
6、箭头函数没有自己的arguments
7、箭头函数没有原型prototype
8、箭头函数不能用作Generator函数,不能使用yeild关键字

114,WebSocket 中的心跳是为了解决什么问题?

1,为了定时发送消息,使连接不超时自动断线,避免后端设了超时时间自动断线。所以需要定时发送消息给后端,让后端服务器知道连接还在通消息不能断
2,为了检测在正常连接的状态下,后端是否正常。如果我们发了一个定时检测给后端,后端按照约定要下发一个检测消息给前端,这样才是正常的。如果后端没有正常下发,就要根据设定的超时进行重连

115,说说对 WebSocket 的了解

HTML5开始提供的一种浏览器与服务器进行全双工通讯的网络技术,属于应用层协议。它基于TCP传输协议,并复用HTTP的握手通道

1,支持双向通信,实时性更强。
2,更好的二进制支持。
3,较少的控制开销。连接创建后,ws客户端、服务端进行数据交换时,协议控制的数据包头部较小。在不包含头部的情况下,服务端到客户端的包头只有2~10字节(取决于数据包长度),客户端到服务端的的话,需要加上额外的4字节的掩码。而HTTP协议每次通信都需要携带完整的头部
4,支持扩展。ws协议定义了扩展,用户可以扩展协议,或者实现自定义的子协议。(比如支持自定义压缩算法等)

116,Promise中,resolve后面的语句是否还会执行?

会被执行。如果不需要执行,需要在 resolve 语句前加上 return

117,JSBridge是什么?

JSBridge是给 JavaScript 提供调用 Native 功能的接口,让混合开发中的前端部分可以方便地使用 Native 的功能(例如:地址位置、摄像头)

实际上,JSBridge 就像其名称中的Bridge的意义一样,是 Native 和非 Native 之间的桥梁,它的核心是构建 Native 和非 Native 间消息通信的通道,而且这个通信的通道是双向的。

双向通信的通道:
JS 向 Native 发送消息: 调用相关功能、通知 Native 当前 JS 的相关状态等。
Native 向 JS 发送消息: 回溯调用结果、消息推送、通知 JS 当前 Native 的状态等。

118,Babel 是什么?

Babel 是一个 JavaScript 编译器

Babel 是一个工具链,主要用于将采用 ECMAScript 2015+ 语法编写的代码转换为向后兼容的 JavaScript 语法,以便能够运行在当前和旧版本的浏览器或其他环境中

119,npm 是什么?

npm是Node.js的包管理工具,它的诞生也极大的促进了前端的发展,在现代前端开发中都离不开npm的身影

1,允许用户从NPM服务器下载别人编写的第三方包到本地使用
2,允许用户从NPM服务器下载并安装别人编写的命令行程序到本地使用
3,允许用户将自己编写的包或命令行程序上传到NPM服务器供别人使用

120,CSR和SSR分别是什么?

对于html的加载,以React为例,我们习惯的做法是加载js文件中的React代码,去生成页面渲染,同时,js也完成页面交互事件的绑定,这样的一个过程就是CSR(客户端渲染)

但如果这个js文件比较大的话,加载起来就会比较慢,到达页面渲染的时间就会比较长,导致首屏白屏。这时候,SSR(服务端渲染)就出来了:由服务端直接生成html内容返回给浏览器渲染首屏内容

但是服务端渲染的页面交互能力有限,如果要实现复杂交互,还是要通过引入js文件来辅助实现,我们把页面的展示内容和交互写在一起,让代码执行两次,这种方式就叫同构

CSR和SSR的区别在于,最终的html代码是从客户端添加的还是从服务端

121,== ![]结果是什么?

== 中,左右两边都需要转换为数字然后进行比较。
[]转换为数字为0。
![] 首先是转换为布尔值,由于[]作为一个引用类型转换为布尔值为true, 因此![]为false,进而在转换成数字,变为00 == 0 , 结果为true

122,forEach中return有效果吗?如何中断forEach循环?

在forEach中用return不会返回,函数会继续执行。
中断方法:
1,使用try监视代码块,在需要中断的地方抛出异常。
2,官方推荐方法(替换方法):用every和some替代forEach函数。
every在碰到return false的时候,中止循环。
some在碰到return true的时候,中止循环。

123,'1'.toString()为什么不会报错?

var s = new Object('1')
s.toString()
s = null

第一步: 创建Object类实例。注意为什么不是String ? 由于Symbol和BigInt的出现,对它们调用new都会报错,目前ES6规范也不建议用new来创建基本类型的包装类。

第二步: 调用实例方法。

第三步: 执行完方法立即销毁这个实例。

基本包装类型 的性质,而基本包装类型恰恰属于基本数据类型,包括Boolean, Number和String。

124,什么是内存泄漏?什么原因会导致呢?

内存泄露的解释:程序中己动态分配的堆内存由于某种原因未释放或无法释放
1,根据JS的垃圾回收机制,当内存中引用的次数为0的时候内存才会被回收
2,全局执行上下文中的对象被标记为不再使用才会被释放
3,全局变量过多。通常是变量未被定义或者胡乱引用了全局变量
4,闭包。 未手动解决必包遗留的内存引用。定义了闭包就要消除闭包带来的副作用
5,事件监听未被移除
6,缓存。建议所有缓存都设置好过期时间