一、CSS1.css盒模型w3c盒模型:content + padding + border + margin。元素宽高(css)等于content的宽高。IE盒模型:元素的宽高(css)等于content + padding + border的宽度。2.BFC块级格式化上下文形成条件:根元素;position: absolute/fixed; float;overflow:not visible;display: inline-block / table。应用:清除浮动3.居中布局水平居中行内元素:text-align: center;块级元素:margin:auto;垂直居中line-height; absolute; flex;水平垂直居中absolute;伪元素形成行内元素居中;flex + justify-content + align-items4.清除浮动设置父元素高度;形成bfc;伪元素clear: both;5.flex常用api引用:Flex 布局教程:语法篇 容器元素display: flex;容器属性flex-direction // 决定主轴的方向(即项目的排列方向)。flex-wrap // 如果一条轴线排不下,如何换行。flex-flow // 是flex-direction属性和flex-wrap属性的简写形式,默认值为row nowrap。justify-content // 定义了项目在主轴上的对齐方式。align-items // 属性定义项目在交叉轴上如何对齐。align-content // 属性定义了多根轴线的对齐方式。如果项目只有一根轴线,该属性不起作用。元素属性order // 属性定义项目的排列顺序。数值越小,排列越靠前,默认为0。flex-grow // 属性定义项目的放大比例,默认为0,即如果存在剩余空间,也不放大。flex-shrink // 属性定义了项目的缩小比例,默认为1,即如果空间不足,该项目将缩小flex-basis // 属性定义了在分配多余空间之前,项目占据的主轴空间(main size)flex // flex-grow, flex-shrink 和 flex-basis的简写,默认值为0 1 autoalign-self // align-self属性允许单个项目有与其他项目不一样的对齐方式,可覆盖align-items属性。二、JavaScript1.原型与继承原型:prototype函数的对象属性,包含一个constructor属性,指向函数自身:Fun.prototype.constructor === Fun。原型链原型链由原型对象构成,是js对象继承的机制。每个对像都有[[prototype]]属性,主流浏览器以__proto__暴露给用户。proto__指向对象的构造函数的原型属性(prototype)。prototype的__proto__又指向它的构造函数的原型;通过__proto__属性,形成一个引用链。在访问对象的属性时,如果对象本身不存在,就会沿着这条[[prototype]]链式结构一层层的访问。构造函数可以通过new来新建一个对象的函数。原型与继承函数拥有原型(prototype);而对象可以通过原型链关系([[prototype]])实现继承。函数是一种特殊对象,也拥有__proto,即函数也会继承。函数的原型链是:函数静态方法—>原生函数类型的原型(拥有函数原生方法)—>原生对象原型(拥有对象原生方法) —> null class A {}; class B extends A {};类的继承B.proto = A.prototype; B.prototype.proto = A.prototype;new Fun操作符原理Object.create(Fun.prototype);执行Fun,即类似类中的调用constructor()(为什么是执行constructor,因为constructor指向函数本身);如果constructor没有明确返回函数或对象,则返回第一步创建的对象。2.函数执行相关堆内存与栈内存栈内存:由编译器自动分配与释放。我们可以直接操作栈内存中的值。js的基本类型存放在栈内存中,按值引用;堆内存:链表结构的类型,可以动态分配大小,js引用类型占用内存空间的大小不固定,存储在堆内存中。由于JS不允许直接访问堆内存中的位置,因此我们不能直接操作js的引用类型。而是生成一个指针,并将它放到栈内存中,通过这个指针来操作引用类型。垃圾回收类型:标记引用(无法解决循环引用问题);标记清除(现在主流回收算法)。标记清除算法的核型概念是:从根部(在JS中就是全局对象)出发定时扫描内存中的对象。凡是能从根部到达的对象,都是还需要使用的。那些无法由根部出发触及到的对象被标记为不再使用,稍后进行回收。内存泄漏对于不再用到的内存,没有及时释放,就叫做内存泄漏(memory leak)。常见内存泄漏:不合理的计时器(引用过时的外部变量)被共享的闭包作用域(函数内部的函数共享一个闭包环境)脱离dom引用(dom中元素被清除,但是变量还保持这对dom元素的引用)意外的全局变量(未声明或者指向全局的this.xxx)事件循环事件循环是指: 执行一个宏任务,然后执行清空微任务列表,循环再执行宏任务,再清微任务列表微任务 microtask(jobs): promise / ajax / Object.observe(该方法已废弃)宏任务 macrotask(task): setTimout / script / IO / UI Rendering每次事件循环:执行完主执行线程中的任务。取出micro-task中任务执行直到清空。取出macro-task中一个任务执行。取出micro-task中任务执行直到清空。重复3和4。执行环境及作用域执行环境执行环境定义了变量或函数有权访问的其他数据,决定了它们各自的行为。每个执行环境斗鱼一个与之关联的变量对象,环境中定义的所有变量和函数都保存在这个对象中。 全局执行环境是最外围的执行环境,在web浏览器中,全局执行环境被认为是window对象,因此所有全局变量和函数都是作为window对象的属性和方法被创建的。某个执行环境中的所有代码执行完毕后,该环境被销毁,保存在其中的所有变量和函数定义也随之销毁。(全局执行环境直到应用程序推出时才会被销毁)。作用域链当代码在一个环境中执行时,会创建变量对象的一个作用域链,保证对执行环境有权访问的所有变量和函数的有序访问。 作用域链的前端,始终都是当前执行的代码所在环境的变量对象。如果这个环境是函数,则将其活动对象作为变量对象,其在最开始的时候只包含一个变量:arguments。作用域链的下一个变量来自包含环境,而️再下一个变量对象则来自下一个包含环境一直延续到全局执行环境。作用域链在创建的时候就会被生成,保存在内部的[[scope]]属性当中。其本质是一个指向变量对象的指针列表,它指引用但不实际包含变量对象。[[scope]]函数创建过程中,会创建一个预先包含外部活动对象的作用域链,并始终包含全局环境的变量对象。这个作用域链被保存在内部的[[scope]]属性中,当函数执行时,会通过复制该属性中的对象构建起执行环境的作用域链。执行上下文定义执行上下文是指当前Javascript代码被解析和执行时所在环境的抽象概念,JavaScript 任何代码的运行都是在执行上下文中。类型全局执行上下文函数执行上下文eval函数执行上下文如何工作执行上下文分为两个过程:创建阶段执行阶段创建阶段主要做了三件事this绑定(这解释了为什么this的值取决于函数的调用方式)创建词法环境。(创建活动对象,变量和函数声明的位置标记(声明提升过程);根据[[scope]]创建作用域链)创建变量环境(变量环境也是词法法环境,用于存储var声明的变量。)执行阶段变量赋值,语句执行等。闭包闭包是指有权访问另一个函数作用域中的变量的函数。其本质是函数的作用域链中保存着外部函数变量对象的引用。可以通过[[scope]]属性查看。因此,即使外部函数被销毁,但是由于外部环境中定义的变量在闭包的scope中还保持着引用,所以这些变量暂时不会被垃圾回收机制回收,因此依然可以在闭包函数中正常访问。注意:同一个函数内部的闭包函数共享同一个外部闭包环境。从下图可以看出,即使返回的闭包里面没有引用c,d变量,但是由于内部函数closure1中引用链,所以即使closure1未调用,闭包作用域中依然保存这些变量的引用。3.js引用方式html 静态<script>引入js 动态插入<script><script defer>: 延迟加载,元素解析完成后执行<script async>: 异步加载,但执行时会阻塞元素渲染其中蓝色代表js脚本网络加载时间,红色代表js脚本执行时间,绿色代表html解析。 更多分析见下文浏览器->页面的加载与渲染4.对象的拷贝浅拷贝拷贝一层,内部如果是对象,则只是单纯的复制指针地址。Object.assign(){ …obj }深拷贝JSON.parse(JSON.stringify(source)),缺点层次太深会爆栈,无法解决循环引用问题。参考深拷贝的终极探索(99%的人都不知道),采用循环代替递归实现深层次的拷贝。大致思想如下:// 保持引用关系function cloneForce(x) { // ============= const uniqueList = []; // 用来去重 // ============= let root = {}; // 循环数组 const loopList = [ { parent: root, key: undefined, data: x, } ]; while(loopList.length) { // 深度优先 const node = loopList.pop(); const parent = node.parent; const key = node.key; const data = node.data; // 初始化赋值目标,key为undefined则拷贝到父元素,否则拷贝到子元素 let res = parent; if (typeof key !== ‘undefined’) { res = parent[key] = {}; } // ============= // 数据已经存在 let uniqueData = find(uniqueList, data); if (uniqueData) { parent[key] = uniqueData.target; break; // 中断本次循环 } // 数据不存在 // 保存源数据,在拷贝数据中对应的引用 uniqueList.push({ source: data, target: res, }); // ============= for(let k in data) { if (data.hasOwnProperty(k)) { if (typeof data[k] === ‘object’) { // 下一次循环 loopList.push({ parent: res, key: k, data: data[k], }); } else { res[k] = data[k]; } } } } return root;}function find(arr, item) { for(let i = 0; i < arr.length; i++) { if (arr[i].source === item) { return arr[i]; } } return null;}5.模块化es6模块commonJS/AMD模块es6模块与CommonJS模块的差异CommonJS模块输出的是一个值的拷贝,ES6模块输出的是值的引用。CommonJS模块是运行时加载,ES6模块是编译时输出接口。6.防抖和节流防抖 (debounce)将多次高频操作优化为只在最后一次执行,通常使用的场景是:用户输入,只需再输入完成后做一次输入校验即可function debounce(fn, wait, immediate) { let timer = null; return function(…args) { if (immediate && !timer) { fn.apply(this, args); } if (timer) clearTimeout(timer) timer = setTimeout(() => { fn.apply(this, args); }, wait); };}节流(throttle)每隔一段时间后执行一次,也就是降低频率,将高频操作优化成低频操作,通常使用场景: 滚动条事件 或者 resize 事件,通常每隔 100~500 ms执行一次即可。function throttle(fn, wait, immediate) { let timer = null; let callNow = immediate; return function(…args) { if (callNow) { fn.apply(this, args); callNow = false; } if (!timer) { timer = setTimeout(() => { fn.apply(this, args) timer = null; }, wait); } }}7.ES6/ES7let/constlet和const可以形成块级作用域,并且不存在声明提升。并且在同一个块级作用域内不可重复声明。解构赋值比如 let [a, b, c] = [1, 2, 3]; let { foo } = { foo: ‘bar’ };函数扩展参数默认值rest参数箭头函数数组扩展Array.from() // 将类数组转化为数组Array.of() // 更语义明确的数组构建函数:用于将一组值,转换为数组。entries(),keys() 和 values()实例方法includesflat(num?)// 数组扁平化数组空位,es6明确将数组空位转化成undefined对象扩展属性简写属性名表达式可枚举性for…in循环:只遍历对象自身的和继承的可枚举的属性。Object.keys():返回对象自身的所有可枚举的属性的键名。JSON.stringify():只串行化对象自身的可枚举的属性。Object.assign(): 忽略enumerable为false的属性,只拷贝对象自身的可枚举Object.is()Object.assign()Object.keys(),Object.values(),Object.entries()Symbollet s = Symbol();typeof s// “symbol"Symbol.for(), Symbol.keyFor()Set,MapSet结构类似数组,成员唯一。const s = new Set();[2, 3, 5, 4, 5, 2, 2].forEach(x => s.add(x));for (let i of s) { console.log(i);}// 2 3 5 4操作方法:add(value):添加某个值,返回 Set 结构本身。delete(value):删除某个值,返回一个布尔值,表示删除是否成功。has(value):返回一个布尔值,表示该值是否为Set的成员。clear():清除所有成员,没有返回值。WeapSetWeakSet 结构与 Set 类似,也是不重复的值的集合。但是,它与 Set 有两个区别。 首先,WeakSet 的成员只能是对象,而不能是其他类型的值。const ws = new WeakSet();ws.add(1)// TypeError: Invalid value used in weak setws.add(Symbol())// TypeError: invalid value used in weak set其次,WeakSet 中的对象都是弱引用,即垃圾回收机制不考虑 WeakSet 对该对象的引用,也就是说,如果其他对象都不再引用该对象,那么垃圾回收机制会自动回收该对象所占用的内存,不考虑该对象还存在于 WeakSet 之中。Map类似于对象,也是键值对的集合,但是“键”的范围不限于字符串,各种类型的值(包括对象)都可以当作键。也就是说,Object 结构提供了“字符串—值”的对应,Map 结构提供了“值—值”的对应,是一种更完善的 Hash 结构实现。如果你需要“键值对”的数据结构,Map 比 Object 更合适。const m = new Map();const o = {p: ‘Hello World’};m.set(o, ‘content’)m.get(o) // “content"m.has(o) // truem.delete(o) // truem.has(o) // falseWeakMapWeakMap与Map的区别有两点。首先,WeakMap只接受对象作为键名(null除外),不接受其他类型的值作为键名。其次,WeakMap的键名所指向的对象,不计入垃圾回收机制。ProxyProxy 可以理解成,在目标对象之前架设一层“拦截”,外界对该对象的访问,都必须先通过这层拦截,因此提供了一种机制,可以对外界的访问进行过滤和改写ES6 原生提供 Proxy 构造函数,用来生成 Proxy 实例。var proxy = new Proxy(target, handler);Promise实现一个简单的Promiseconst FULLFILLED = ‘FULLFILLED’;const REJECTED = ‘REJECTED’;const PENDING = ‘PENDING’;const isFn = val => typeof val === ‘function’;class Promise { constructor(handler) { this._state = PENDING; this._value = null; this.FULLFILLED_CBS = []; this.REJECTED_CBS = []; try { handler(this._resolve.bind(this), this._reject.bind(this)); } catch (err) { this._reject(err); } } _resolve(value) { const async = () => { if (this._state !== PENDING) { return; } this._state = FULLFILLED; const fullfilLed = (val) => { this._value = val; this.FULLFILLED_CBS.forEach(cb => cb(val)); }; const rejected = (err) => { this._value = err; this.REJECTED_CBS.forEach(cb => cb(err)); }; if (value instanceof Promise) { value.then(fullfilLed, rejected); } else { fullfilLed(value); } } requestAnimationFrame(async); } _reject(err) { const async = () => { if (this._state !== PENDING) { return; } this._state = REJECTED; this._value = err; this.REJECTED_CBS.forEach(cb => cb(err)); } requestAnimationFrame(async); } then(onFullfilled, onRejected) { return new Promise((resolve, reject) => { const handerResolve = (value) => { try { if (!isFn(onFullfilled)) { resolve(value); } const res = onFullfilled(value); if (res instanceof Promise) { res.then(resolve, reject); } else { resolve(res); } } catch (err) { reject(err); } }; const handlerReject = (err) => { try { if (!isFn(onRejected)) { reject(err); } const res = onRejected(err); if (res instanceof Promise) { res.then(resolve, reject); } else { reject(res); } } catch (err) { reject(err); } }; switch(this._state) { case PENDING: this.FULLFILLED_CBS.push(handerResolve); this.REJECTED_CBS.push(handlerReject); break; case FULLFILLED: handerResolve(this._value); break; case REJECTED: handlerReject(this._value); break; default: break; } }); } catch(onRejected) { return this.then(undefined, onRejected); } finally(cb) { const P = this.constructor; return this.then( /** 使用then保证promise的流是正常的,因为promise的下一步总是建立在上一步执行完的基础上 */ value => P.resolve(cb()).then(() => value), reason => P.resolve(cb()).then(() => { throw reason }) ); } static all(list) { return new Promise(resolve, reject) { let count = 0; const values = []; const P = this.constructor; for (let [key, fn] of list) { P.resolve(fn).then(res => { values[key] = res; count++; if (count === list.length) { resolve(values); } }, err => reject(err)); } } } static race(list) { return new Promise((resolve, reject) => { const values = []; let count = 0; const P = this.constructor; for (let fn of list) { P.resolve(fn).then(res => { resolve(res); }, err => { reject(err); }) } }) } static resolve(value) { if (value instanceof Promise) { return value; } return new Promise(resolve => resolve(value)); } static reject(value) { return new Promise((resolve, reject) => reject(value)); }}async/awaitasync 函数是什么?一句话,它就是 Generator 函数的语法糖。async函数返回一个promise对象。async函数内部return语句返回的值,会成为then方法回调函数的参数。Classes6的类语法是es5构造函数的语法糖,但是也有一些不同点class的原型属性不可枚举不使用new操作符,class会报错class新增存取函数不存在声明提升Class的继承抓住两点即可:假设A为父类,B为子类那么B.proto = A; // 子类的__proto__属性,表示构造函数的继承,总是指向父类。B.prototype.proto = A.prototype; // 子类prototype属性的__proto__属性,表示方法(实例)的继承,总是指向父类的prototype属性。Modulees6模块commonJS/AMD模块es6模块与CommonJS模块的差异CommonJS模块输出的是一个值的拷贝,ES6模块输出的是值的引用。CommonJS模块是运行时加载,ES6模块是编译时输出接口。8.函数柯里化在一个函数中,首先填充几个参数,然后再返回一个新的函数的技术,称为函数的柯里化。通常可用于在不侵入函数的前提下,为函数 预置通用参数,供多次重复调用。const add = function add(x) { return function (y) { return x + y }}const add1 = add(1)add1(2) === 3add1(20) === 219.正则表达式3.浏览器1.页面的加载与渲染从输入 url 到展示的过程DNS查找相应的ip地址 浏览器DNS缓存->电脑本地host文件查找->运营商DNS的解析三次握手客户端发送 syn(同步序列编号) 请求,进入 syn_send 状态,等待确认服务端接收并确认 syn 包后发送 syn+ack 包,进入 syn_recv 状态客户端接收 syn+ack 包后,发送 ack 包,双方进入 established 状态发送请求,分析 url,设置请求报文(头,主体)服务器返回请求的文件 (html)浏览器渲染HTML parser –> DOM TreeCSS parser –> Style Treeattachment –> Render Treelayout: 布局浏览器加载与渲染浏览器下载html文件并进行编译,转化成类似下面的结构图片来源再谈 load 与 DOMContentLoaded 浏览器会对转化后的数据结构自上而下进行分析:首先开启下载线程,对所有的资源进行优先级排序下载(注意,这里仅仅是下载,区别于解析过程,解析过程可能被阻塞)。同时主线程会对文档进行解析css文件的解析header中的css的下载不会阻塞html的解析,当css文件下载完成,将阻塞html解析,优先解析css,会阻碍渲染。body中的css下载和解析都不会阻碍html的解析,会阻碍渲染。(存在特殊情况,见下文)js文件的解析1. 普通的script:遇到 script 标签时,首先阻塞后续内容的解析(但是不会阻塞下载,chrome对同一个域名支持6个http同时下载),当下载完成后,便执行js文件中的同步代码。然后接着解析html2. defer和async如果script标签设置了async或者defer标签,下载过程不会阻塞文档解析。defer执行将会放到html解析完成之后,dcl事件之前。async则是下载完就执行,并且阻塞解析。body中的首个script/link在 body 中第一个 script 资源下载完成之前,浏览器会进行首次渲染。将该 script 标签前面的 DOM 树和 CSSOM 合并成一棵 Render 树,渲染到页面中。这是页面从白屏到首次渲染的时间节点,比较关键。如果是第一个标签是link,表现有点奇怪,下载和解析会阻塞html的解析,并有可能进行首次渲染,也可能不渲染。渲染过程大致为:计算样式Recalculate style->布局:layout->更新布局树update layer tree -> 绘制paint3.相关概念dom构建DOM 构建的意思是,将文档中的所有 DOM 元素构建成一个树型结构。css构建将文档中的所有 css 资源合并。生成css rule treerender 树将 DOM 树和 CSS 合并成一棵渲染树,render 树在合适的时机会被渲染到页面中。(比如遇到 script 时, 该 script 还没有下载到本地时)。4.总结如下图:浏览器首先下载该地址所对应的 html 页面。(如果html很大,浏览器并不会等待html完全下载完,而是采用分段下载,分段解析)浏览器解析 html 页面的 DOM 结构。开启下载线程对文档中的所有资源按优先级排序下载。主线程继续解析文档,到达 head 节点 ,head 里的外部资源无非是外链样式表和外链 js。发现有外链 css 或者外链 js,如果是外链 js ,则停止解析后续内容,等待该资源下载,下载完后立刻执行。如果是外链 css,继续解析后续内容。解析到 body在 body 中第一个 script 资源下载完成之前,浏览器会进行首次渲染。将该 script 标签前面的 DOM 树和 CSSOM 合并成一棵 Render 树,渲染到页面中。这是页面从白屏到首次渲染的时间节点,比较关键。如果是第一个标签是link,表现有点奇怪,下载和解析会阻塞html的解析,并有可能进行首次渲染,也可能不渲染。随后js的下载和执行依然会阻塞解析,但是css的下载和解析则不会阻塞html的解析。文档解析完毕,页面重新渲染。当页面引用的所有 js 同步代码执行完毕,触发 DOMContentLoaded 事件。然后执行布局:layout->更新布局树update layer tree -> 绘制painthtml 文档中的图片资源,js 代码中有异步加载的 css、js 、图片资源都加载完毕之后,load 事件触发(与DOMContentLoaded没有必然的先后顺序关系)。2.跨域解决方案4.计算机网络基础5.前端工程化6.Vue源码相关7.React源码相关8.算法相关引用参考ECMAScript 6 入门中高级前端大厂面试秘籍,为你保驾护航金三银四,直通大厂(上)JS事件循环Flex 布局教程:语法篇前端笔记–JS执行上下文script标签中defer和async属性的区别[译]页面生命周期:DOMContentLoaded, load, beforeunload, unload解析再谈 load 与 DOMContentLoaded面试带你飞:这是一份全面的 计算机网络基础 总结攻略深拷贝的终极探索(99%的人都不知道)