- 浏览器内核
- 盒模型、flex 布局、两/三栏布局、水平/垂直居中;
- BFC、清除浮动;
- css3 动画、H5 新特性。
- 继承、原型链、this 指向、设计模式、call, apply, bind,;
- new 实现、防抖节流、let, var, const 区别、暂时性死区、event、loop;
- promise 使用及实现、promise 并行执行和顺序执行;
- async/await 的优缺点;
- 闭包、垃圾回收和内存泄漏、数组方法、数组乱序, 数组扁平化、事件委托、事件监听、事件模型
-
vue 数据双向绑定原理;
-
vue computed 原理、computed 和 watch 的区别;
-
vue 编译器结构图、生命周期、vue 组件通信;
-
mvvm 模式、mvc 模式理解;
-
vue dom diff、vuex、vue-router
-
nextTick存在的意义是什么
异步执行,减少不必要的计算和dom更新。当数据发生变化的时候,不会立刻执行。而是会把watcher push到一个队列里边(去重)。数字0循环+1加到100,触发getter100次,如果立刻执行执行就会执行100次渲染。最终的结果是0变成了100,中间的是没有意义的。
网络:
- HTTP1, HTTP2, HTTPS、常见的 http 状态码;
- 浏览从输入网址到回车发生了什么;
- 前端安全(CSRF、XSS)
- 前端跨域、浏览器缓存、cookie, session, token, localstorage, sessionstorage;
- TCP 连接(三次握手, 四次挥手)
性能相关
-
图片优化的方式
-
500 张图片,如何实现预加载优化
-
懒加载具体实现
-
减少 http 请求的方式
-
webpack 如何配置大型项目
-
介绍一下 js 的数据类型有哪些,值是如何存储的
JavaScript 一共有 8 种数据类型,其中有 7 种基本数据类型:Undefined、Null、Boolean、Number、String、Symbol(es6 新增,表示独一无二的值)和 BigInt(es10 新增);
1 种引用数据类型——Object(Object 本质上是由一组无序的名值对组成的)。里面包含 function、Array、Date 等。JavaScript 不支持任何创建自定义类型的机制,而所有值最终都将是上述 8 种数据类型之一。
原始数据类型:直接存储在栈(stack)中,占据空间小、大小固定,属于被频繁使用数据,所以放入栈中存储。
引用数据类型:同时存储在栈(stack)和堆(heap)中,占据空间大、大小不固定。引用数据类型在栈中存储了指针,该指针指向堆中该实体的起始地址。当解释器寻找引用值时,会首先检索其在栈中的地址,取得地址后从堆中获得实体。
-
==类型转换
-
JS 中数据类型的判断( typeof,instanceof,constructor,Object.prototype.toString.call()
这里有一个坑,如果我创建一个对象,更改它的原型,constructor就会变得不可靠了。
**Object.prototype.toString.call()**使用 Object 对象的原型方法 toString ,使用 call 进行狸猫换太子,借用 Object 的 toString 方法
-
Javascript 的作用域和作用域链
作用域: 作用域是定义变量的区域,它有一套访问变量的规则,这套规则来管理浏览器引擎如何在当前作用域以及嵌套的作用域中根据变量(标识符)进行变量查找。
作用域链: 作用域链的作用是保证对执行环境有权访问的所有变量和函数的有序访问,通过作用域链,我们可以访问到外层环境的变量和 函数。
作用域链的本质上是一个指向变量对象的指针列表。变量对象是一个包含了执行环境中所有变量和函数的对象。作用域链的前 端始终都是当前执行上下文的变量对象。全局执行上下文的变量对象(也就是全局对象)始终是作用域链的最后一个对象。
当我们查找一个变量时,如果当前执行环境中没有找到,我们可以沿着作用域链向后查找。
作用域链的创建过程跟执行上下文的建立有关....
-
javascript 创建对象的几种方式?
-
JavaScript 继承的几种实现方式?
-
寄生式组合继承的实现?
-
谈谈你对 this、call、apply 和 bind 的理解
-
JavaScript 原型,原型链?有什么特点?
-
js 获取原型的方法?
p.__proto__
- p.constructor.prototype
- Object.getPrototypeOf(p)
-
什么是闭包,为什么要用它?
闭包是指有权访问另一个函数作用域内变量的函数,创建闭包的最常见的方式就是在一个函数内创建另一个函数,创建的函数可以 访问到当前函数的局部变量。
闭包有两个常用的用途。
- 闭包的第一个用途是使我们在函数外部能够访问到函数内部的变量。通过使用闭包,我们可以通过在外部调用闭包函数,从而在外部访问到函数内部的变量,可以使用这种方法来创建私有变量。
- 函数的另一个用途是使已经运行结束的函数上下文中的变量对象继续留在内存中,因为闭包函数保留了这个变量对象的引用,所以这个变量对象不会被回收。
-
什么是 DOM 和 BOM?
DOM 指的是文档对象模型,它指的是把文档当做一个对象来对待,这个对象主要定义了处理网页内容的方法和接口。
BOM 指的是浏览器对象模型,它指的是把浏览器当做一个对象来对待,这个对象主要定义了与浏览器进行交互的法和接口。BOM 的核心是 window,而 window 对象具有双重角色,它既是通过 js 访问浏览器窗口的一个接口,又是一个 Global(全局) 对象。这意味着在网页中定义的任何对象,变量和函数,都作为全局对象的一个属性或者方法存在。window 对象含有 locati on 对象、navigator 对象、screen 对象等子对象,并且 DOM 的最根本的对象 document 对象也是 BOM 的 window 对 象的子对象。
-
三种事件模型是什么?
事件 是用户操作网页时发生的交互动作或者网页本身的一些操作,现代浏览器一共有三种事件模型。
-
DOM0 级模型: ,这种模型不会传播,所以没有事件流的概念,但是现在有的浏览器支持以冒泡的方式实现,它可以在网页中直接定义监听函数,也可以通过 js 属性来指定监听函数。这种方式是所有浏览器都兼容的。
-
IE 事件模型: 在该事件模型中,一次事件共有两个过程,事件处理阶段,和事件冒泡阶段。事件处理阶段会首先执行目标元素绑定的监听事件。然后是事件冒泡阶段,冒泡指的是事件从目标元素冒泡到 document,依次检查经过的节点是否绑定了事件监听函数,如果有则执行。这种模型通过 attachEvent 来添加监听函数,可以添加多个监听函数,会按顺序依次执行。
-
DOM2 级事件模型: 在该事件模型中,一次事件共有三个过程,第一个过程是事件捕获阶段。捕获指的是事件从 document 一直向下传播到目标元素,依次检查经过的节点是否绑定了事件监听函数,如果有则执行。后面两个阶段和 IE 事件模型的两个阶段相同。这种事件模型,事件绑定的函数是 addEventListener,其中第三个参数可以指定事件是否在捕获阶段执行。
-
-
事件委托是什么?
事件委托 本质上是利用了浏览器事件冒泡的机制。因为事件在冒泡过程中会上传到父节点,并且父节点可以通过事件对象获取到 目标节点,因此可以把子节点的监听函数定义在父节点上,由父节点的监听函数统一处理多个子元素的事件,这种方式称为事件代理。
使用事件代理我们可以不必要为每一个子元素都绑定一个监听事件,这样减少了内存上的消耗。并且使用事件代理我们还可以实现事件的动态绑定,比如说新增了一个子节点,我们并不需要单独地为它添加一个监听事件,它所发生的事件会交给父元素中的监听函数来处理。
-
什么是事件传播?
当事件发生在 DOM 元素上时,该事件并不完全发生在那个元素上。在“当事件发生在 DOM 元素上时,该事件并不完全发生在那个元素上。
事件传播有三个阶段:
- 捕获阶段–事件从 window 开始,然后向下到每个元素,直到到达目标元素事件或 event.target。
- 目标阶段–事件已达到目标元素。
- 冒泡阶段–事件从目标元素冒泡,然后上升到每个元素,直到到达 window。
-
什么是事件捕获?
当事件发生在 DOM 元素上时,该事件并不完全发生在那个元素上。在捕获阶段,事件从 window 开始,一直到触发事件的元素。
window----> document----> html----> body ---->目标元素
-
什么是事件冒泡?
事件冒泡刚好与事件捕获相反,
当前元素---->body ----> html---->document ---->window
。当事件发生在 DOM 元素上时,该事件并不完全发生在那个元素上。在冒泡阶段,事件冒泡,或者事件发生在它的父代,祖父母,祖父母的父代,直到到达 window 为止。 -
常用的正则表达式(仅做收集,涉及不深)
//(1)匹配 16 进制颜色值 var color = /#([0-9a-fA-F]{6}|[0-9a-fA-F]{3})/g; //(2)匹配日期,如 yyyy-mm-dd 格式 var date = /^[0-9]{4}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$/; //(3)匹配 qq 号 var qq = /^[1-9][0-9]{4,10}$/g; //(4)手机号码正则 var phone = /^1[34578]\d{9}$/g; //(5)用户名正则* var username = /^[a-zA-Z\$][a-zA-Z0-9_\$]{4,16}$/; //(6)Email正则* var email = /^([A-Za-z0-9_\-\.])+\@([A-Za-z0-9_\-\.])+\.([A-Za-z]{2,4})$/; //(7)身份证号(18位)正则* var cP = /^[1-9]\d{5}(18|19|([23]\d))\d{2}((0[1-9])|(10|11|12))(([0-2][1-9])|10|20|30|31)\d{3}[0-9Xx]$/; //(8)URL正则* var urlP= /^((https?|ftp|file):\/\/)?([\da-z\.-]+)\.([a-z\.]{2,6})([\/\w \.-]*)*\/?$/; // (9)ipv4地址正则* var ipP = /^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/; // (10)//车牌号正则* var cPattern = /^[京津沪渝冀豫云辽黑湘皖鲁新苏浙赣鄂桂甘晋蒙陕吉闽贵粤青藏川宁琼使领A-Z]{1}[A-Z]{1}[A-Z0-9]{4}[A-Z0-9挂学警港澳]{1}$/; // (11)强密码(必须包含大小写字母和数字的组合,不能使用特殊字符,长度在8-10之间): var pwd = /^(?=.\d)(?=.[a-z])(?=.[A-Z]).{8,10}$/*
-
Ajax 是什么? 如何创建一个 Ajax?
//1:创建Ajax对象* var xhr = window.XMLHttpRequest?new XMLHttpRequest():new ActiveXObject('Microsoft.XMLHTTP');*// 兼容IE6及以下版本* //2:配置 Ajax请求地址* xhr.open('get','index.xml',true); //3:监听请求,接受响应* xhr.onreadysatechange=function(){ if(xhr.readySates==4&&xhr.status==200 || xhr.status==304 ) console.log(xhr.responsetXML) } //4:发送请求* xhr.send();
-
js 延迟加载的方式有哪些?
js 的加载、解析和执行会阻塞页面的渲染过程,因此我们希望 js 脚本能够尽可能的延迟加载,提高页面的渲染速度。
我了解到的几种方式是:
- 将 js 脚本放在文档的底部,来使 js 脚本尽可能的在最后来加载执行。
- 给 js 脚本添加 defer 属性,这个属性会让脚本的加载与文档的解析同步解析,然后在文档解析完成后再执行这个脚本文件,这样的话就能使页面的渲染不被阻塞。多个设置了 defer 属性的脚本按规范来说最后是顺序执行的,但是在一些浏览器中可能不是这样。
- 给 js 脚本添加 async 属性,这个属性会使脚本异步加载,不会阻塞页面的解析过程,但是当脚本加载完成后立即执行 js 脚本,这个时候如果文档没有解析完成的话同样会阻塞。多个 async 属性的脚本的执行顺序是不可预测的,一般不会按照代码的顺序依次执行。
- 动态创建 DOM 标签的方式,我们可以对文档的加载事件进行监听,当文档加载完成后再动态的创建 script 标签来引入 js 脚本。
-
谈谈你对模块化开发的理解?
-
js 的几种模块规范?
-
AMD 和 CMD 规范的区别?
它们之间的主要区别有两个方面。
- 第一个方面是在模块定义时对依赖的处理不同。AMD 推崇依赖前置,在定义模块的时候就要声明其依赖的模块。而 CMD 推崇就近依赖,只有在用到某个模块的时候再去 require。
- 第二个方面是对依赖模块的执行时机处理不同。首先 AMD 和 CMD 对于模块的加载方式都是异步加载,不过它们的区别在于 模块的执行时机,AMD 在依赖模块加载完成后就直接执行依赖模块,依赖模块的执行顺序和我们书写的顺序不一定一致。而 CMD 在依赖模块加载完成后并不执行,只是下载而已,等到所有的依赖模块都加载好后,进入回调函数逻辑,遇到 require 语句 的时候才执行对应的模块,这样模块的执行顺序就和我们书写的顺序保持一致了。
-
ES6 模块与 CommonJS 模块、AMD、CMD 的差异。
1.
CommonJS
模块输出的是一个值的拷贝,ES6 模块输出的是值的引用。CommonJS
模块输出的是值的,也就是说,一旦输出一个值,模块内部的变化就影响不到这个值。ES6 模块的运行机制与 CommonJS 不一样。JS 引擎对脚本静态分析的时候,遇到模块加载命令 import,就会生成一个只读引用。等到脚本真正执行时,再根据这个只读引用,到被加载的那个模块里面去取值。2.
CommonJS
模块是运行时加载,ES6 模块是编译时输出接口。CommonJS
模块就是对象,即在输入时是先加载整个模块,生成一个对象,然后再从这个对象上面读取方法,这种加载称为“运行时加载”。而 ES6 模块不是对象,它的对外接口只是一种静态定义,在代码静态解析阶段就会生成。 -
requireJS 的核心原理是什么?
require.js 的核心原理是通过动态创建 script 脚本来异步引入模块,然后对每个脚本的 load 事件进行监听,如果每个脚本都加载完成了,再调用回调函数。
-
谈谈 JS 的运行机制
- js 单线程
JavaScript 语言的一大特点就是单线程,即同一时间只能做一件事情。
JavaScript 的单线程,与它的用途有关。作为浏览器脚本语言,JavaScript 的主要用途是与用户互动,以及操作 DOM。这决定了它只能是单线程,否则会带来很复杂的同步问题。比如,假定 JavaScript 同时有两个线程,一个线程在某个 DOM 节点上添加内容,另一个线程删除了这个节点,这时浏览器应该以哪个线程为准?所以,为了避免复杂性,从一诞生,JavaScript 就是单线程,这已经成了这门语言的核心特征,将来也不会改变。
2.事件循环
面试中该如何回答呢?下面是我个人推荐的回答:
- 首先 js 是单线程运行的,在代码执行的时候,通过将不同函数的执行上下文压入执行栈中来保证代码的有序执行。
- 在执行同步代码的时候,如果遇到了异步事件,js 引擎并不会一直等待其返回结果,而是会将这个事件挂起,继续执行执行栈中的其他任务
- 当同步事件执行完毕后,再将异步事件对应的回调加入到与当前执行栈中不同的另一个任务队列中等待执行。
- 任务队列可以分为宏任务对列和微任务对列,当当前执行栈中的事件执行完毕后,js 引擎首先会判断微任务对列中是否有任务可以执行,如果有就将微任务队首的事件压入栈中执行。
- 当微任务对列中的任务都执行完成后再去判断宏任务对列中的任务。
自己说
js主程序开始执行,遇到异步任务会把异步任务放到任务队列里边,不同的任务有不同的优先级,主要分为宏任务和微任务,对应宏任务队列和微任务队列,微任务的执行由县级更高,没执行完一个宏任务,会把微任务队列清空,再取出下一个宏任务执行。就是这样一个循环的过程。
-
简单介绍一下 V8 引擎的垃圾回收机制
v8 的垃圾回收机制基于分代回收机制,这个机制又基于世代假说,这个假说有两个特点,一是新生的对象容易早死,另一个是不死的对象会活得更久。基于这个假说,v8 引擎将内存分为了新生代和老生代。
新创建的对象或者只经历过一次的垃圾回收的对象被称为新生代。经历过多次垃圾回收的对象被称为老生代。
新生代被分为 From 和 To 两个空间,To 一般是闲置的。当 From 空间满了的时候会执行 Scavenge 算法进行垃圾回收。当我们执行垃圾回收算法的时候应用逻辑将会停止,等垃圾回收结束后再继续执行。这个算法分为三步:
(1)首先检查 From 空间的存活对象,如果对象存活则判断对象是否满足晋升到老生代的条件,如果满足条件则晋升到老生代。如果不满足条件则移动 To 空间。
(2)如果对象不存活,则释放对象的空间。
(3)最后将 From 空间和 To 空间角色进行交换。
新生代对象晋升到老生代有两个条件:
(1)第一个是判断是对象否已经经过一次 Scavenge 回收。若经历过,则将对象从 From 空间复制到老生代中;若没有经历,则复制到 To 空间。
(2)第二个是 To 空间的内存使用占比是否超过限制。当对象从 From 空间复制到 To 空间时,若 To 空间使用超过 25%,则对象直接晋升到老生代中。设置 25% 的原因主要是因为算法结束后,两个空间结束后会交换位置,如果 To 空间的内存太小,会影响后续的内存分配。
老生代采用了标记清除法和标记压缩法。标记清除法首先会对内存中存活的对象进行标记,标记结束后清除掉那些没有标记的对象。由于标记清除后会造成很多的内存碎片,不便于后面的内存分配。所以了解决内存碎片的问题引入了标记压缩法。
由于在进行垃圾回收的时候会暂停应用的逻辑,对于新生代方法由于内存小,每次停顿的时间不会太长,但对于老生代来说每次垃圾回收的时间长,停顿会造成很大的影响。 为了解决这个问题 V8 引入了增量标记的方法,将一次停顿进行的过程分为了多步,每次执行完一小步就让运行逻辑执行一会,就这样交替运行。
-
哪些操作会造成内存泄漏?
1.意外的全局变量
2.被遗忘的计时器或回调函数
3.脱离 DOM 的引用
4.闭包
第一种情况是我们由于使用未声明的变量,而意外的创建了一个全局变量,而使这个变量一直留在内存中无法被回收。
第二种情况是我们设置了
setInterval
定时器,而忘记取消它,如果循环函数有对外部变量的引用的话,那么这个变量会被一直留在内存中,而无法被回收。第三种情况是我们获取一个 DOM 元素的引用,而后面这个元素被删除,由于我们一直保留了对这个元素的引用,所以它也无法被回收。
第四种情况是不合理的使用闭包,从而导致某些变量一直被留在内存当中。
-
ECMAScript 是什么?
ECMAScript 是编写脚本语言的标准,这意味着 JavaScript 遵循 ECMAScript 标准中的规范变化,因为它是 JavaScript 的蓝图。
ECMAScript 和 Javascript,本质上都跟一门语言有关,一个是语言本身的名字,一个是语言的约束条件 只不过发明 JavaScript 的那个人(Netscape 公司),把东西交给了 ECMA(European Computer Manufacturers Association),这个人规定一下他的标准,因为当时有 java 语言了,又想强调这个东西是让 ECMA 这个人定的规则,所以就这样一个神奇的东西诞生了,这个东西的名称就叫做 ECMAScript。
javaScript = ECMAScript + DOM + BOM(自认为是一种广义的 JavaScript)
ECMAScript 说什么 JavaScript 就得做什么!
JavaScript(狭义的 JavaScript)做什么都要问问 ECMAScript 我能不能这样干!如果不能我就错了!能我就是对的!
——突然感觉 JavaScript 好没有尊严,为啥要搞个人出来约束自己,
那个人被创造出来也好委屈,自己被创造出来完全是因为要约束 JavaScript。
-
ECMAScript 2015(ES6)有哪些新特性?
-
块作用域
-
类
-
箭头函数
-
模板字符串
-
加强的对象字面
-
对象解构
-
Promise
-
模块
-
Symbol
-
代理(proxy)Set
-
函数默认参数
-
rest 和展开
-
var
,let
和const
的区别是什么?var是函数作用域,在函数范围里内都可以访问。定义在全局时,会挂在window上。
let和const是块作用域。不能声明同名变量。在声明前调用会报错,暂时性死区。声明提升,初始化没提升,不能访问。
const是常量,声明时需要赋值。
ReferenceError: Cannot access 'h' before initialization
-
什么是箭头函数?
箭头函数表达式的语法比函数表达式更简洁,并且没有自己的
this,arguments,super或new.target
。箭头函数表达式更适用于那些本来需要匿名函数的地方。不能用作构造函数。因为没有原型?
-
什么是类?
类(class)是在 JS 中编写构造函数的新方法。它是使用构造函数的语法糖,在底层中使用仍然是原型和基于原型的继承。
-
set map
另外还有
WeakSet
, 与Set
类似,也是不重复的值的集合。但是WeakSet
的成员只能是对象,而不能是其他类型的值。WeakSet
中的对象都是弱引用,即垃圾回收机制不考虑WeakSet
对该对象的引用。 -
什么是 Proxy?
Proxy 用于修改某些操作的默认行为,等同于在语言层面做出修改,所以属于一种“元编程”,即对编程语言进行编程。
Proxy 可以理解成,在目标对象之前架设一层“拦截”,外界对该对象的访问,都必须先通过这层拦截,因此提供了一种机制,可以对外界的访问进行过滤和改写。Proxy 这个词的原意是代理,用在这里表示由它来“代理”某些操作,可以译为“代理器”。
-
xss攻击
-
web安全
-
服务端渲染和客户端渲染有什么不同
-
performance api
-
Time to first byte
let ttfb = time.responseStart - time.navigationStart;
-
Page load time
let pageloadtime = time.loadEventStart - time.navigationStart;
-
DNS lookup time
let dns = time.domainLookupEnd - time.domainLookupStart;
-
是可交互时间
可交互是什么意思?
是可以安全的执行js?
domComplete
是脚本执行完?
domContentLoadedEventEnd
-
tcp
tcp = time.connectEnd - time.connectStart;
-
-
performace中的其他api
- performance.timing 信息简单计算出网页性能数据
- 使用performance.getEntries() 获取所有资源请求的时间数据
- 使用 performance.now() 精确计算程序执行时间(微秒级)
- 使用 performance.mark() 也可以精确计算程序执行时间
-
移动端和pc开发有什么区别
-
Flutter 和 RN 原理的区别
-
性能优化
-
错误监控
异常捕获:
-
window.onerror,若该函数返回true,则阻止执行默认事件处理函数,也就是不会在控制台打印错误。
-
try...catch...,同步代码
-
promise catch API
-
vue
之所以会存在这种场景,是因为 Vue 自身已经通过 try...catch... 处理,而不会触发 window.onerror 事件,所以我们有时候也需要专门对 Vue 进行异常捕获。我们可以使用 Vue.config.errorHandler 对 Vue 进行全局的异常捕获。
-
跨域
加载来自不同域的脚本发生错误的时候,为了避免信息泄露,语法细节不会再上报,而是简单的 "Script error"
解决方法是,在 script 标签中使用 crossorigin 属性并要求服务器端发送适当的 CORS HTTP 响应头,则可以解决这个问题,也就是要求服务端设置 Access-Control-Allow-Origin。
<script src="http://cdn.xxx.com/index.js" crossorigin="anonymous"></script>
-
sourcemap
-
-
离线缓存sw
-
diff算法
-
怎么mock数据
- 内网部署yapi,在上面定义接口和返回数据
- easy-mock,内置Mock.js,定义数据生成模板,生成随机数据
- 自己的node服务+mockjs
- 纯mockjs