基础
scrollWidth,clientWidth,offsetWidth
- scrollWidth不包含边框宽度,包括溢出当前区域需要滚动才能看到的内容
- clientWidth是可视区域宽度,不包括滚动条、溢出当前区域需要滚动才能看到的内容、边框
- offsetWidth包括滚动条、边框宽度
浏览器进程有哪些,线程有哪些?
进程:主进程(控制各子进程)、各页面单独进程(防止页面奔溃导致整个浏览器奔溃,也避免使用线程因共享内存带来的安全问题)、插件进程、GPU进程(3D渲染)
线程:渲染线程、js线程、定时触发器线程、事件线程、异步http请求线程
执行上下文、变量对象
- 对于每个执行上下文都有三个重要属性:作用域链、变量对象、
this
- 全局上下文的变量对象是
window
,this
指向window
。 - 变量对象存储了当前上下文中的变量、函数声明。
- 函数执行时创建执行上下文,激活变量对象
activation object
。变量对象中包含实参、内部变量声明、内部函数声明。 - 函数执行时,顺序执行代码,改变
activation object
中的值
js作用域链是在函数声明时确定的,还是执行时确定的?
js是词法作用域,是静态的,在声明时确定,github。
什么是预编译?预编译发生了什么?
就是预解释,去解析执行代码,进行变量提升。
js
的变量提升
-
var
分为三个阶段:创建、初始化、赋值,其中创建和初始化会被预解释执行,初始化为undefined
- 、
function
也分为三个阶段,不同于var
的是,函数是将整个声明语句提升,导致赋值也提升。
var a = 1
function a(){}
console.log('变量:', a)
-
let
也分为三个阶段:不同点在于,let
只做了创建提升,初始化是执行到当前行代码时才做的。所以不能使用,也就是暂时性死区。 -
const
分为两个阶段:创建和初始化。创建变量,将值初始化为对应的值。
image.png - 所谓的暂时性死区就是在初始化之前不能使用该变量。
flatMap
let arr1 = ["it's Sunny in", "", "California"]
console.log(arr1.flatMap(v => v.split(" ")))
console.log(arr1.map(v => v.split(" ")).flat(1))
String.prototype.match
和RegExp.prototype.exec
区别
- 当有全局搜索修饰符时,
match
会一次性返回所有匹配结果,返回值是数组,包含了所有匹配的子字符串,exec
则是返回从lastIndex
位置起的第一个匹配的结果。当有分组匹配时,对match
没有影响,exec
返回的数组第一项为整个正则匹配的结果字符串,第二项开始会得到分组的结果,如果有多个分组匹配成功,则在数组中依次放置分组匹配结果。
var str = 'a-b-b'
var reg = /([a-z]+)-/g
console.log(reg.exec(str))
console.log(str.match(reg))
git rebase
和git merge
区别
- git rebase branch如果没有冲突,是把当前分支的那些commit移动到了branch分支后面。
- git rebase test1 test2 等于以下写法
gco test2
git rebase test1
得到的结果是test2分支的修改移动到了test1分支的后面。
-
git pull -r
的时候其实是git pull --rebase
,相当于git rebase remoteBranch
。将本地的commit往后移动,远程分支别人提交的commit放在前面。如果有冲突的话解决冲突。 - 不要在公共分支rebase分支,原因?
git revert
、git reset --soft
、git reset --hard
区别?
-
git revert
是使用一个新的commit
来做回滚,reset --hard
直接删除之前的commit
,这样会有个问题是。如果老分支中含有之前的commit
,将老分支合并到该分支时,还会把原来的那些commit
带上。 -
soft
还会将文件改动保留在暂存区,hard
会直接将改动删除。
版本号带在文件后面和文件名是hash
有什么区别?
- 版本号带后面,问题在于如果只变更了其中一个文件,其他文件都没有变更,那么其他文件。
- 如果版本号放到文件后面,那就是覆盖式发布,一旦动态生成的页面
html
和放在CDN
上的css
资源同时有改动。更新谁都不成,如果先更新html
,那么在更新静态资源的间隔时间,加载到的是旧版本的css
,因为新版本还没发。样式会错乱。如果先更新css
,那么如果本地有缓存的用户,没有问题,但是新用户加载到的就是旧html
+新css
,那么就会有问题。知道发布新的html,则会去加载新的css
,才会正常。
pachage.json中~
和^
区别
~
安装1.1.x
的最新版本,不超过1.2.0
^
安装1.x.x
的最新版本,不超过2.0.0
npm script中&
和&&
的区别
&并行。&&串行。a&b&&c的执行顺序是a和(b+c)并行,b和c之间串行。
Nginx
文章
epoll
模型、长链接和content-length
的关系
进程和线程
- 进程和线程都是
CPU
工作时间段的描述,进程粒度更大。 - 一个进程下的单个线程复用该进程的上下文环境。
-
CPU
在切换进程时消耗的资源更多,这时候就需要开启线程,切换线程因为复用上下文,消耗能源少。 - 但是也不能全用线程,因为一旦一个线程崩了,因为共享上下文环境(内存),就会导致其他线程也不能正常工作。进程则是独立的,每个子进程不互相影响。所以多个独立的应用是开启独立的子进程,单个应用内开启多个线程提高工作效率。
package.json
中dependencies
、peerDependence
、devDependencies
区别
对于项目来说,没啥区别,只是用于程序员自己区分是开发环境的依赖还是生产环境的依赖。对于npm
包来说,在被用户安装时,devDependencies
不会被用户安装,dependencies
会被安装。同时在npm2
版本中peerDependence
中的包会自动被安装到和依赖包同一层级而不是当前包的子目录node_modules
中,解决项目中依赖同一个包而版本不同的问题。npm3
后不会自动安装,但是会在命令后中提示用户。
instanceof
必须是 something instanceof object
,右边必须是对象,否则报错,右边是null
也报错。
前端性能优化
- DNS预读取 X-DNS-Prefetch-Control
- preload预先加载各种类型的文件,用于当前页面使用,高级浏览器默认开启
- 压缩资源,开启gzip
- 小图片base64,减少请求数量。
为什么js放页面底部,css放页面上方?
-
js
会阻塞浏览器解析html
文档,浏览器的domContentLoaded
事件会等js执行完才进,放在上方和底部没什么影响。但是现代浏览器会边解析边渲染,如果把js放上方,会阻塞html的解析,dom解析被延迟。空白时间就会加长。如果是旧浏览器,那就没啥区别。 -
css
不阻塞html的解析,但是阻塞html的渲染。如果放到底部,由于现代浏览器从上到下边解析边渲染,会先渲染没有样式的html,再渲染有样式的html。如果是旧浏览器,等样式全部下载完再渲染,也会导致css资源加载时间较晚导致渲染时间推迟。
尾调用优化
起因在于函数调用形成调用帧,函数内嵌套函数调用,形成调用栈。外层函数帧需要等内层函数调用完毕才会消失。但是直接return
函数调用结果的话,外层函数调用帧可以直接消失,因为不会有其他操作,只保留内部函数帧即可。常见的就是Fibonacci
序列。
尾递归的实现,往往需要改写递归函数,确保最后一步只调用自身。做到这一点的方法,就是把所有用到的内部变量改写成函数的参数。
PWA
- 为什么需要PWA?
- 性能优化瓶颈,首屏加载速度
- 用户留存率低,网站在被关闭后没办法和用户建立联系。不像app可以在后台通知用户
-
什么是PWA
PWA 它不是特指某一项技术,而是应用多项技术来改善用户体验的 Web App,其核心技术包括 Web App Manifest,Service Worker,Web Push 等,用户体验才是 PWA 的核心。 -
Web App Manifest是什么?
Web App Manifest 体现在代码上主要是一个 JSON 文件:manifest.json,开发者可以在这个 JSON 文件中配置 PWA 的相关信息,应用名称、图标、启动方式、背景颜色、主题颜色等等。添加到桌面后,PWA 并不是一个快捷方式,而是能够在系统中作为一个独立的 App 存在的,用户可以设置它的权限,清除它的缓存,就和 Native App 一样。 -
Service Worker作用域是什么?
和service workser所在服务器文件路径有关,url路径下的所有子路径都是scope范围
https://hostname/a
https://hostname/a/b
需要考虑到作用域问题,在注册前先注销掉其他service-worker -
Service Worker有bug如何处理?
- 修改index.html页面,注销掉所有service-worker
- 通过动态请求js,根据js内的service-worker开关变量来判定是否要注销掉所有service-worker
获取元素的相对位置和绝对位置
-
getBoundingClientRect.left|top
获取距离视口的位置 - getBoundingClientRect.left + document.documentElement.scrollLeft获得绝对位置
微任务和宏任务区分
microtask:promise 中的 then,MutationObserver,ie中的setImmediate
macrotask:ajax,setTimeout,setInterval,事件绑定,postMessage(MessageChannel)
children和childnotes区别
substr、substring、slice区别?
- 三者作用相同,都是获取子字符串,都不会改变原字符串。
- substring和slice的参数都是起始位置,结束位置,左闭右开。
- str.substr(起始位置, 长度),第一个参数为负数表示倒数(length + 负数),第二个参数为负数会被转成0
- slice两个参数为负数都会被转为倒数第n个
- substring两个参数为负数都会被转为0。如果第二个参数比第一个大会互换位置。因为slice是转为负数,所以不会有自动换位置的操作。
字符串中获取位置的api?
-
'12w1w'.match('w').index
// 可正则 -
'12w1w'.search('w')
// 可正则 '12w1w'.indexOf('w')
'12w1w'.lastIndexOf('w')
数据类型
基础数据类型
- String
- Number
- Boolean
- undefined
- null
- bigint
- symbol
引用类型
- object
ES7
Array.prototype.includes
- 求幂运算
**
ES8
-
async
、await
- 求幂运算
**
- Object.values
- Object.entries
- padStart、padnd
有哪些遍历对象的方法
-
Object.keys
获取非原型链上的所有可枚举属性,ES5中的prototypes默认可枚举,ES6 class默认不可枚举。 -
for...in
获取包括原型链上的所有可枚举属性 -
Object.entries
获取非原型链上的所有可枚举属性, -
Object.getOwnPropertyNames
获取非原型链上的所有非symbol属性,包括不可枚举属性 -
Object.getOwnPropertySymbols
获取非原型链上的所有symbol属性,包括不可枚举属性 -
Reflect.ownKeys
=Object.getOwnPropertySymbols
+Object.getOwnPropertyNames
以上得到的数组顺序都相同。 -
for...in
循环出的是key
,for...of
循环出的是value
import
和require
区别
- import值的引用,require是值的复制,import引入的变量会被影响。
- require是运行时加载,import是编译时输出接口
call、apply、bind使用场景
1.call
常用于继承,实例属性
2.apply
可用于求最值
Math.max.apply(undefined, [1,2,3])
3.bind
常用于传递参数
如何防止递归函数被复写
arguments.callee
可以读取到当前函数
如何团队提效
函数防抖和函数节流
防抖:只有最后一次会被执行,输入框输入内容搜索
function debounce (fn = () => {}, timeout = 300) {
let timeoutID = null
return function (...args) {
if (timeoutID) {
clearTimeout(timeoutID)
}
timeoutID = setTimeout(() => {
// 这里的this是上下文中的this
// 但是函数直接执行,this取的是window
fn.call(this, ...args)
}, timeout)
}
};
const onInput = debounce(function (...args) {
console.log(this)
console.log(...args)
})
window.input.addEventListener('input',function (...args) {
onInput.call(this, ...args)
}, false)
节流:一段时间内只触发一次,CF游戏按住鼠标一直射击,子弹匀速打出
function throttle (fn = () => {}, timeout = 300){
let lastTime = null
let timeoutID = null
return function (...args) {
const now = Date.now()
// 如果之前执行过并且还没到预定时间
if (lastTime && now < timeout + lastTime) {
clearTimeout(timeoutID)
timeoutID = setTimeout(() => {
fn.call(this, ...args)
}, timeout)
} else {
// 第一次进入则直接执行,并初始化最近一次执行时间
lastTime = now
fn.call(this, ...args)
}
}
}
function shot () {
console.log('shot', this)
}
const throttledShot = throttle(shot, 100)
gun.on('shot', function(...args){
throttledShot(...args)
})
基本思路,注意这里的没有考虑好this指向的问题。
图片懒加载
var num = document.getElementsByTagName('img').length;
var img = document.getElementsByTagName("img");
var n = 0; //存储图片加载到的位置,避免每次都从第一张图片开始遍历
lazyload(); //页面载入完毕加载可是区域内的图片
window.onscroll = throttle(lazyload, 500);
function lazyload() { //监听页面滚动事件
var seeHeight = document.documentElement.clientHeight; //可见区域高度
// 这里是因为旧版本谷歌浏览器只支持document.body.scrollTop
var scrollTop = document.documentElement.scrollTop || document.body.scrollTop; //滚动条距离顶部高度
for (var i = n; i < num; i++) {
// offsetTop是节点离其最近的position非static的元素或者body
if (img[i].offsetTop < seeHeight + scrollTop) {
if (img[i].getAttribute("src") == "default.jpg") {
img[i].src = img[i].getAttribute("data-src");
}
n = i + 1;
}
}
}
函数式编程
- 纯函数:没有副作用,不依赖除参数外的变量,不改变参数及函数外的变量,任何时候调用得到相同结果
- 细粒度拆分步骤,进行函数组合
- 模块化,可复用
柯里化
当函数参数很多的时候,只传递给函数一部分参数来调用它,让它返回一个函数去处理剩下的参数。
function currying (fn, ...args1) {
return function (...args2) {
return fn(...args1, ...args2)
}
}
function add (x, y) {
return x + y
}
var increment = currying(add, 1)
increment(2) === 3
作用域链
当查找变量的时候,会先从当前上下文的变量对象中查找,如果没有找到,就会从父级(词法层面上的父级)执行上下文的变量对象中查找,一直找到全局上下文的变量对象,也就是全局对象。这样由多个执行上下文的变量对象构成的链表就叫做作用域链。
闭包
在函数内使用函数外的变量。
跨域解决方案、JSONP
CORS兼容到ie8,ie8和ie9是通过XDomainRequest
实现,注意如果要跨域带cookie则xhr.withCredentials = true
。
// 允许跨域访问的域名:若有端口需写全(协议+域名+端口),若没有端口末尾不用加'/'
response.setHeader("Access-Control-Allow-Origin", "http://www.domain1.com");
// 允许前端带认证cookie:启用此项后,上面的域名不能为'*',必须指定具体的域名,否则浏览器会提示
response.setHeader("Access-Control-Allow-Credentials", "true");
// 提示OPTIONS预检时,后端需要设置的两个常用自定义头
response.setHeader("Access-Control-Allow-Headers", "Content-Type,X-Requested-With");
call实现
ES6
实现
Function.prototype.call1 = function (context, ...rest) {
// 哪个函数调用call,this就指向它
context = context || window
const fn = Symbol('fn')
context[fn] = this
const result = context[fn](...rest)
delete context[fn]
return result
}
ES5
实现错误示例
// 错误示例
Function.prototype.call2 = function (context) {
// 哪个函数调用call,this就指向它
context = context || window
// 防止覆盖上下文中的属性
const fn = '_fn' + Math.random()
let args = []
for (let i = 1; i < arguments.length; ++i) {
args.push(arguments[i])
}
context[fn] = this
// 不能用join:用了之后函数参数就变成一个了,参数值是join后的字符串
// const result = eval('context[fn](args.join())')
// 不能直接使用args的原因:
// [1,2,3].toString() ===> '1,2,3'
// ['1',2,3].toString() ===> '1,2,3'
// ['abc',1,2].toString() ===> 'abc,1,2' ===> abc is not defined
// const result = eval('context[fn](' + args + ')')
delete context[fn]
return result
}
ES5
正确示例
Function.prototype.call3 = function (context) {
context = context || window
const fn = '_fn' + Math.random()
context[fn] = this
let args = []
for (let i = 1; i < arguments.length; ++i) {
args.push('arguments[' + i + ']')
}
const result = eval('context[fn](' + args + ')')
delete context[fn]
return result
}
apply实现
和call
的注意点没什么区别,详见call
错误示例
Function.prototype.apply1 = function (context, arr) {
context = context || window
arr = arr || []
const fn = '_fn' + Math.random()
context[fn] = this
let args = []
for (let i = 0; i < arr.length; i++) {
args[i] = 'arr[' + i + ']'
}
const ret = eval('context[fn](' + args + ')')
delete context[fn]
return ret
}
let a = ['name',18,3]
function test(name, age) {
return {
name: name,
age: age
}
}
console.log(test.apply1(null, a))
bind实现
Function.prototype.bind2 = function (context) {
const self = this
// const outerArgs = [...arguments].slice(1)
const outerArgs = Array.prototype.slice.call(arguments, 1)
function noop (){}
function bound (...args) {
return self.apply(this instanceof self ? this : context, outerArgs.concat(args))
}
noop.prototype = self.prototype
bound.prototype = new noop()
return bound
}
var value = 1
let obj = {
value: 2
}
function testBind (a, b) {
console.log(this.value)
console.log(a, b)
this.yy = 'yy'
}
testBind.prototype.xx = 'xx'
const boundFn = testBind.bind2(obj, 3)
// boundFn(4)
let instance = new boundFn(5, 6)
console.log(instance.xx)
console.log(instance.yy)
数据的判定方式
Object.prototype.toString.call()
websocket握手过程
前端持久化的方式、区别
sessionStorage
端口号不同没关系,但是域名、协议必须一样。
生命周期是打开至关闭一个tab页。
在一个页面内通过a标签、window.open打开新页面时,新页面能够共享sessionStorage的值,但是拿到的只是初始化的值,在两个页面
各自改变值不会影响其他页面。如果是新打开的页面,则为一个新的session,值和其他页面无关。
localStorage
遵循同源策略:协议、域名、端口号必须全部一样。4K
cookie
遵循同源策略:协议、域名、端口号必须全部一样。5M,随请求发送,占用带宽。
indexedDB
阮一峰
张鑫旭 indexedDB vs web SQL Database
什么是关系型数据库,什么是非关系型数据库
为什么需要索引
回调函数的劣势
- 嵌套多层,难以理解
- 难以维护,容易出bug
如何实现有过期时间的localstorage
将过期时间存储在数据中,读取的时候判断一下,如果过期就执行删除逻辑。
Promise和async的区别
async
是generator
函数的语法糖,需要不断执行迭代器的next
方法,返回一个promise
对象。
文件上传
浏览器事件先捕获后冒泡
事件委托优缺点
this指向
异步编程优缺点
mouseover和mouseenter的区别
DOM
DOM是文档对象模型,作用是将网页转化为一个对象,从而可以使用脚本来操作网页的内容。
- 插入节点:
- parentNode.append(多个节点)
- parentNode.prepend(多个节点)
- parentNode.appendChild(单个节点)
- parentNode.insertBefore(newNode, 某个子节点)
- node.before(单个节点) 在节点前新增节点
- node.after(单个节点) 在节点后新增节点
- 删除节点
- parentNode. removeChild(单个自节点)
- node.remove(),移除自己
- node.replaceWith(新节点) 用新节点替换当前节点
- node.
append和appendChild区别
- append() 方法可以直接追加字符串为文本节点,比如 append("text") ,appendChild() 不行
- append() 方法支持追加多个参数,appendChild() 只能追加一个
- append() 方法没有返回值,而 appendChild() 会返回追加进去的那个节点
- 和 append() 同时期加入 DOM 规范的方法还有 prepend() 、before()、after() 等
- jQuery 中存在的 appendTo() 方法并没有和 append() 一起加入到 DOM 规范里
BOM
BOMBOM
proxy作用
- 拦截属性读取,只读属性读取时报错
- 相比于Object.defineProperty,多了很多拦截器,比如apply、deleteProperty等
- 也可以拦截对数组的操作
css文件会阻塞DOM解析吗?会阻塞DOM渲染吗?会阻塞js执行吗?
- 不会阻塞DOM解析,文档对象模型(DOM)树和css对象模型(CSSOM)树是并行解析的
- 谷歌浏览器会阻塞DOM渲染,等CSS文件完全加载完毕才会执行渲染,避免重复计算布局、渲染。各浏览器的渲染步骤
- 会阻塞js执行,因为css文件和js都有可能改变样式,这就可能导致多次渲染,所以先等css加载完毕再执行js。JS 也有可能会去获取 DOM 的样式,所以 JS 会等待样式表加载完毕。
- 参考文章
浏览器是边解析边渲染吗?
不是,会等所有内容解析执行完成再渲染。
segmentfault.com
DOMContentLoaded和onload区别?
- DOMContentLoaded是等页面上html和js解析完成,不等待图片、css文件加载完成
- 有 defer 属性的脚本会阻止 DOMContentLoaded 事件,直到defer脚本被加载并且解析完成。
- onload是等所有资源加载执行完成
- 页面生命周期文章
js会等样式文件加载完成再执行,但是一旦将script标签往上移动,就会立即执行,因为script标签没有样式要等,而DOMContentLoaded事件不等css样式文件的加载。
<link rel="stylesheet" href="css.php">
<script>
document.addEventListener('DOMContentLoaded',function(){
console.log('3 seconds passed');
});
</script>
错误处理
stopImmediatePropagation
、stopPropagation
、preventDefault
、return false
- stopImmediatePropagation用于阻止调用后续的监听相同事件的函数
-
stopPropagation
用于停止冒泡 -
preventDefault
用于阻止默认事件发生 return false
addEventListener('click', fn, useCaptcha)
useCaptcha默认为false,表示在冒泡阶段接收事件,如果设置为true,则是捕获阶段接收事件
document.querySelector('.parent').addEventListener('click', () => {
console.log('设为false,冒泡阶段才接收,后面输出;设为true,捕获阶段就接收,因为事件流是先捕获,所以会先于子节点输出')
}, false)
document.querySelector('.child').addEventListener('click', () => {
console.log('111')
})
addEventListener 的passive是怎么回事?
总的来说,在鼠标滚轮或者触摸滑动时,浏览器必须等touchmove
、mousemove
执行完才能知道要不要阻止屏幕滚动,这样就会导致屏幕滚动会卡顿。所以加入了该参数来显式声明不会有preventDefault
的行为。是一种性能优化。
紫云飞
已刷面经
commonjs和esmodules的区别
- commonjs引入非引用类型变量时,变量变化时,外部模块内得到的值还是旧的,这是因为只执行commonjs的模块只是在被引入的时候执行一次,后面全部到installedModules[moduleId]. exports中取。所以取到的是值的拷贝。所以引入引用类型变量时,是会跟随者一起变化。因为拷贝的是引用类型变量的地址
- esmodule引入变量时,注意分两种情况。如果是通过export default导出的非引用类型,则也是导出值的拷贝,通过export {}导出的,则是导出值的引用,在其他模块取到的是一个地址。
- CommonJS 模块是运行时加载,ES6 模块是编译时输出接口
- CommonJS 模块的require()是同步加载模块,ES6 模块的import命令是异步加载,有一个独立模块依赖的解析阶段
参考地址