基础

scrollWidth,clientWidth,offsetWidth

宽度
例子

  1. scrollWidth不包含边框宽度,包括溢出当前区域需要滚动才能看到的内容
  2. clientWidth是可视区域宽度,不包括滚动条、溢出当前区域需要滚动才能看到的内容、边框
  3. offsetWidth包括滚动条、边框宽度

浏览器进程有哪些,线程有哪些?

进程:主进程(控制各子进程)、各页面单独进程(防止页面奔溃导致整个浏览器奔溃,也避免使用线程因共享内存带来的安全问题)、插件进程、GPU进程(3D渲染)
线程:渲染线程、js线程、定时触发器线程、事件线程、异步http请求线程

执行上下文、变量对象

  1. 对于每个执行上下文都有三个重要属性:作用域链、变量对象、this
  2. 全局上下文的变量对象是windowthis指向window
  3. 变量对象存储了当前上下文中的变量、函数声明。
  4. 函数执行时创建执行上下文,激活变量对象activation object。变量对象中包含实参、内部变量声明、内部函数声明。
  5. 函数执行时,顺序执行代码,改变activation object中的值

github

js作用域链是在函数声明时确定的,还是执行时确定的?

js是词法作用域,是静态的,在声明时确定,github

什么是预编译?预编译发生了什么?

就是预解释,去解析执行代码,进行变量提升。

js的变量提升

  1. var分为三个阶段:创建、初始化、赋值,其中创建和初始化会被预解释执行,初始化为undefined
  2. function也分为三个阶段,不同于var的是,函数是将整个声明语句提升,导致赋值也提升。
var a = 1
function a(){}
console.log('变量:', a)
  1. let也分为三个阶段:不同点在于,let只做了创建提升,初始化是执行到当前行代码时才做的。所以不能使用,也就是暂时性死区。
  2. const分为两个阶段:创建和初始化。创建变量,将值初始化为对应的值。
    image.png
  3. 所谓的暂时性死区就是在初始化之前不能使用该变量。

方方的回答

flatMap

let arr1 = ["it's Sunny in", "", "California"]
console.log(arr1.flatMap(v => v.split(" ")))
console.log(arr1.map(v => v.split(" ")).flat(1))

MDN

String.prototype.matchRegExp.prototype.exec区别

  1. 当有全局搜索修饰符时,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 rebasegit merge 区别

  1. git rebase branch如果没有冲突,是把当前分支的那些commit移动到了branch分支后面。
  2. git rebase test1 test2 等于以下写法
gco test2
git rebase test1

得到的结果是test2分支的修改移动到了test1分支的后面。

  1. git pull -r的时候其实是git pull --rebase,相当于git rebase remoteBranch。将本地的commit往后移动,远程分支别人提交的commit放在前面。如果有冲突的话解决冲突。
  2. 不要在公共分支rebase分支,原因?

git revertgit reset --softgit reset --hard区别?

  1. git revert是使用一个新的commit来做回滚,reset --hard直接删除之前的commit,这样会有个问题是。如果老分支中含有之前的commit,将老分支合并到该分支时,还会把原来的那些commit带上。
  2. soft还会将文件改动保留在暂存区,hard会直接将改动删除。

版本号带在文件后面和文件名是hash有什么区别?

  1. 版本号带后面,问题在于如果只变更了其中一个文件,其他文件都没有变更,那么其他文件。
  2. 如果版本号放到文件后面,那就是覆盖式发布,一旦动态生成的页面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的关系

进程和线程

  1. 进程和线程都是CPU工作时间段的描述,进程粒度更大。
  2. 一个进程下的单个线程复用该进程的上下文环境。
  3. CPU在切换进程时消耗的资源更多,这时候就需要开启线程,切换线程因为复用上下文,消耗能源少。
  4. 但是也不能全用线程,因为一旦一个线程崩了,因为共享上下文环境(内存),就会导致其他线程也不能正常工作。进程则是独立的,每个子进程不互相影响。所以多个独立的应用是开启独立的子进程,单个应用内开启多个线程提高工作效率。

package.jsondependenciespeerDependencedevDependencies区别

对于项目来说,没啥区别,只是用于程序员自己区分是开发环境的依赖还是生产环境的依赖。对于npm包来说,在被用户安装时,devDependencies不会被用户安装,dependencies会被安装。同时在npm2版本中peerDependence中的包会自动被安装到和依赖包同一层级而不是当前包的子目录node_modules中,解决项目中依赖同一个包而版本不同的问题。npm3后不会自动安装,但是会在命令后中提示用户。

参考

instanceof

必须是 something instanceof object,右边必须是对象,否则报错,右边是null也报错。

前端性能优化

  1. DNS预读取 X-DNS-Prefetch-Control
  2. preload预先加载各种类型的文件,用于当前页面使用,高级浏览器默认开启
  3. 压缩资源,开启gzip
  4. 小图片base64,减少请求数量。

为什么js放页面底部,css放页面上方?

  1. js会阻塞浏览器解析html文档,浏览器的domContentLoaded事件会等js执行完才进,放在上方和底部没什么影响。但是现代浏览器会边解析边渲染,如果把js放上方,会阻塞html的解析,dom解析被延迟。空白时间就会加长。如果是旧浏览器,那就没啥区别。
  2. css不阻塞html的解析,但是阻塞html的渲染。如果放到底部,由于现代浏览器从上到下边解析边渲染,会先渲染没有样式的html,再渲染有样式的html。如果是旧浏览器,等样式全部下载完再渲染,也会导致css资源加载时间较晚导致渲染时间推迟。

尾调用优化

起因在于函数调用形成调用帧,函数内嵌套函数调用,形成调用栈。外层函数帧需要等内层函数调用完毕才会消失。但是直接return函数调用结果的话,外层函数调用帧可以直接消失,因为不会有其他操作,只保留内部函数帧即可。常见的就是Fibonacci序列。

尾递归的实现,往往需要改写递归函数,确保最后一步只调用自身。做到这一点的方法,就是把所有用到的内部变量改写成函数的参数。

PWA

  • 为什么需要PWA?
  1. 性能优化瓶颈,首屏加载速度
  2. 用户留存率低,网站在被关闭后没办法和用户建立联系。不像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如何处理?

  1. 修改index.html页面,注销掉所有service-worker
  2. 通过动态请求js,根据js内的service-worker开关变量来判定是否要注销掉所有service-worker

获取元素的相对位置和绝对位置

  1. getBoundingClientRect.left|top获取距离视口的位置
  2. getBoundingClientRect.left + document.documentElement.scrollLeft获得绝对位置

微任务和宏任务区分

microtask:promise 中的 then,MutationObserver,ie中的setImmediate
macrotask:ajax,setTimeout,setInterval,事件绑定,postMessage(MessageChannel)

children和childnotes区别

childnotes会把空白节点也算上

substr、substring、slice区别?

  1. 三者作用相同,都是获取子字符串,都不会改变原字符串。
  2. substring和slice的参数都是起始位置,结束位置,左闭右开。
  3. str.substr(起始位置, 长度),第一个参数为负数表示倒数(length + 负数),第二个参数为负数会被转成0
  4. slice两个参数为负数都会被转为倒数第n个
  5. substring两个参数为负数都会被转为0。如果第二个参数比第一个大会互换位置。因为slice是转为负数,所以不会有自动换位置的操作。

字符串中获取位置的api?

  1. '12w1w'.match('w').index // 可正则
  2. '12w1w'.search('w') // 可正则
  3. '12w1w'.indexOf('w')
  4. '12w1w'.lastIndexOf('w')

数据类型

基础数据类型

  1. String
  2. Number
  3. Boolean
  4. undefined
  5. null
  6. bigint
  7. symbol

引用类型

  1. object

ES7

  1. Array.prototype.includes
  2. 求幂运算 **

ES8

  1. asyncawait
  2. 求幂运算 **
  3. Object.values
  4. Object.entries
  5. padStart、padnd

有哪些遍历对象的方法

  1. Object.keys获取非原型链上的所有可枚举属性,ES5中的prototypes默认可枚举,ES6 class默认不可枚举。
  2. for...in 获取包括原型链上的所有可枚举属性
  3. Object.entries获取非原型链上的所有可枚举属性,
  4. Object.getOwnPropertyNames获取非原型链上的所有非symbol属性,包括不可枚举属性
  5. Object.getOwnPropertySymbols获取非原型链上的所有symbol属性,包括不可枚举属性
  6. Reflect.ownKeys = Object.getOwnPropertySymbols + Object.getOwnPropertyNames
    以上得到的数组顺序都相同。
  7. for...in循环出的是keyfor...of循环出的是value

importrequire区别

  1. import值的引用,require是值的复制,import引入的变量会被影响。
  2. 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;
        }
    }
}

函数式编程

  1. 纯函数:没有副作用,不依赖除参数外的变量,不改变参数及函数外的变量,任何时候调用得到相同结果
  2. 细粒度拆分步骤,进行函数组合
  3. 模块化,可复用

柯里化

当函数参数很多的时候,只传递给函数一部分参数来调用它,让它返回一个函数去处理剩下的参数。

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
  }

github
掘金评论区

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

4种方式的问题

websocket握手过程

参考

前端持久化的方式、区别

sessionStorage
端口号不同没关系,但是域名、协议必须一样。
生命周期是打开至关闭一个tab页。
在一个页面内通过a标签、window.open打开新页面时,新页面能够共享sessionStorage的值,但是拿到的只是初始化的值,在两个页面
各自改变值不会影响其他页面。如果是新打开的页面,则为一个新的session,值和其他页面无关。

localStorage
遵循同源策略:协议、域名、端口号必须全部一样。4K

cookie
遵循同源策略:协议、域名、端口号必须全部一样。5M,随请求发送,占用带宽。

indexedDB
阮一峰
张鑫旭 indexedDB vs web SQL Database

什么是关系型数据库,什么是非关系型数据库

数据库

为什么需要索引

CSDN

回调函数的劣势

  1. 嵌套多层,难以理解
  2. 难以维护,容易出bug

如何实现有过期时间的localstorage

将过期时间存储在数据中,读取的时候判断一下,如果过期就执行删除逻辑。

Promise和async的区别

asyncgenerator函数的语法糖,需要不断执行迭代器的next方法,返回一个promise对象。

文件上传

掘金

浏览器事件先捕获后冒泡

知乎

事件委托优缺点

个人博客

this指向

个人博客

异步编程优缺点

异步编程优缺点

mouseover和mouseenter的区别

mouseover和mouseenter的区别

DOM

DOM是文档对象模型,作用是将网页转化为一个对象,从而可以使用脚本来操作网页的内容。

  • 插入节点:
  1. parentNode.append(多个节点)
  2. parentNode.prepend(多个节点)
  3. parentNode.appendChild(单个节点)
  4. parentNode.insertBefore(newNode, 某个子节点)
  5. node.before(单个节点) 在节点前新增节点
  6. node.after(单个节点) 在节点后新增节点
  • 删除节点
  1. parentNode. removeChild(单个自节点)
  2. node.remove(),移除自己
  3. node.replaceWith(新节点) 用新节点替换当前节点
  4. node.

append和appendChild区别

  1. append() 方法可以直接追加字符串为文本节点,比如 append("text") ,appendChild() 不行
  2. append() 方法支持追加多个参数,appendChild() 只能追加一个
  3. append() 方法没有返回值,而 appendChild() 会返回追加进去的那个节点
  4. 和 append() 同时期加入 DOM 规范的方法还有 prepend() 、before()、after() 等
  5. jQuery 中存在的 appendTo() 方法并没有和 append() 一起加入到 DOM 规范里

BOM

BOM

BOM

proxy作用

  1. 拦截属性读取,只读属性读取时报错
  2. 相比于Object.defineProperty,多了很多拦截器,比如apply、deleteProperty等
  3. 也可以拦截对数组的操作

css文件会阻塞DOM解析吗?会阻塞DOM渲染吗?会阻塞js执行吗?

  1. 不会阻塞DOM解析,文档对象模型(DOM)树和css对象模型(CSSOM)树是并行解析的
  2. 谷歌浏览器会阻塞DOM渲染,等CSS文件完全加载完毕才会执行渲染,避免重复计算布局、渲染。各浏览器的渲染步骤
  3. 会阻塞js执行,因为css文件和js都有可能改变样式,这就可能导致多次渲染,所以先等css加载完毕再执行js。JS 也有可能会去获取 DOM 的样式,所以 JS 会等待样式表加载完毕。
  4. 参考文章

浏览器是边解析边渲染吗?

不是,会等所有内容解析执行完成再渲染。
segmentfault.com

DOMContentLoaded和onload区别?

  1. DOMContentLoaded是等页面上html和js解析完成,不等待图片、css文件加载完成
  2. 有 defer 属性的脚本会阻止 DOMContentLoaded 事件,直到defer脚本被加载并且解析完成。
  3. onload是等所有资源加载执行完成
  4. 页面生命周期文章

js会等样式文件加载完成再执行,但是一旦将script标签往上移动,就会立即执行,因为script标签没有样式要等,而DOMContentLoaded事件不等css样式文件的加载。

<link rel="stylesheet" href="css.php">
<script>
document.addEventListener('DOMContentLoaded',function(){
    console.log('3 seconds passed');
});
</script>

错误处理

stopImmediatePropagationstopPropagationpreventDefaultreturn false

  1. stopImmediatePropagation用于阻止调用后续的监听相同事件的函数
  2. stopPropagation用于停止冒泡
  3. preventDefault用于阻止默认事件发生
  4. 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是怎么回事?

总的来说,在鼠标滚轮或者触摸滑动时,浏览器必须等touchmovemousemove执行完才能知道要不要阻止屏幕滚动,这样就会导致屏幕滚动会卡顿。所以加入了该参数来显式声明不会有preventDefault的行为。是一种性能优化。
紫云飞

已刷面经

基础习题

commonjs和esmodules的区别

  1. commonjs引入非引用类型变量时,变量变化时,外部模块内得到的值还是旧的,这是因为只执行commonjs的模块只是在被引入的时候执行一次,后面全部到installedModules[moduleId]. exports中取。所以取到的是值的拷贝。所以引入引用类型变量时,是会跟随者一起变化。因为拷贝的是引用类型变量的地址
  2. esmodule引入变量时,注意分两种情况。如果是通过export default导出的非引用类型,则也是导出值的拷贝,通过export {}导出的,则是导出值的引用,在其他模块取到的是一个地址。
  3. CommonJS 模块是运行时加载,ES6 模块是编译时输出接口
  4. CommonJS 模块的require()是同步加载模块,ES6 模块的import命令是异步加载,有一个独立模块依赖的解析阶段
    参考地址