前端面试基础知识整理
栏目: JavaScript · 发布时间: 5年前
内容简介:设置父元素高度;形成bfc;伪元素clear: both;引用:容器元素
一、CSS
1.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-items
4.清除浮动
设置父元素高度;形成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 auto align-self // align-self属性允许单个项目有与其他项目不一样的对齐方式,可覆盖align-items属性。
二、JavaScript
1.原型与继承
原型: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引用方式
<script> <script> <script defer> <script async>
其中蓝色代表js脚本网络加载时间,红色代表js脚本执行时间,绿色代表html解析。
更多分析见下文 浏览器->页面的加载与渲染
4.对象的拷贝
浅拷贝
拷贝一层,内部如果是对象,则只是单纯的复制指针地址。
Object.assign()
深拷贝
- 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/ES7
let/const
let和const可以形成块级作用域,并且不存在声明提升。并且在同一个块级作用域内不可重复声明。
解构赋值
比如 let [a, b, c] = [1, 2, 3]; let { foo } = { foo: 'bar' }
;
函数扩展
- 参数默认值
- rest参数
- 箭头函数
数组扩展
- Array.from() // 将类数组转化为数组
- Array.of() // 更语义明确的数组构建函数:用于将一组值,转换为数组。
- entries(),keys() 和 values()
- 实例方法includes
- flat(num?)// 数组扁平化
- 数组空位,es6明确将数组空位转化成undefined
对象扩展
- 属性简写
- 属性名表达式
-
可枚举性
- for...in循环:只遍历对象自身的和继承的可枚举的属性。
- Object.keys():返回对象自身的所有可枚举的属性的键名。
- JSON.stringify():只串行化对象自身的可枚举的属性。
- Object.assign(): 忽略enumerable为false的属性,只拷贝对象自身的可枚举
- Object.is()
- Object.assign()
- Object.keys(),Object.values(),Object.entries()
Symbol
let s = Symbol(); typeof s // "symbol"
Symbol.for(), Symbol.keyFor()
Set,Map
Set
结构类似数组,成员唯一。
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():清除所有成员,没有返回值。
WeapSet
WeakSet 结构与 Set 类似,也是不重复的值的集合。但是,它与 Set 有两个区别。
首先,WeakSet 的成员只能是对象,而不能是其他类型的值。
const ws = new WeakSet(); ws.add(1) // TypeError: Invalid value used in weak set ws.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) // true m.delete(o) // true m.has(o) // false
WeakMap
WeakMap与Map的区别有两点。
首先,WeakMap只接受对象作为键名(null除外),不接受其他类型的值作为键名。
其次,WeakMap的键名所指向的对象,不计入垃圾回收机制。
Proxy
Proxy 可以理解成,在目标对象之前架设一层“拦截”,外界对该对象的访问,都必须先通过这层拦截,因此提供了一种机制,可以对外界的访问进行过滤和改写
ES6 原生提供 Proxy 构造函数,用来生成 Proxy 实例。
var proxy = new Proxy(target, handler);
Promise
实现一个简单的Promise
const 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/await
async 函数是什么?一句话,它就是 Generator 函数的语法糖。
async函数返回一个promise对象。async函数内部return语句返回的值,会成为then方法回调函数的参数。
Class
es6的类语法是es5构造函数的语法糖,但是也有一些不同点
- class的原型属性不可枚举
- 不使用new操作符,class会报错
- class新增存取函数
- 不存在声明提升
Class的继承
抓住两点即可:假设A为父类,B为子类那么
B.__proto__ = A; // 子类的__proto__属性,表示构造函数的继承,总是指向父类。 B.prototype.__proto__ = A.prototype; // 子类prototype属性的__proto__属性,表示方法(实例)的继承,总是指向父类的prototype属性。
Module
- es6模块
- 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) === 3 add1(20) === 21
9.正则表达式
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 Tree
- CSS parser --> Style Tree
- attachment --> Render Tree
- layout: 布局
浏览器加载与渲染
浏览器下载html文件并进行编译,转化成类似下面的结构
图片来源 再谈 load 与 DOMContentLoaded 浏览器会对转化后的数据结构自上而下进行分析:首先开启下载线程,对所有的资源进行优先级 排序 下载(注意,这里仅仅是下载,区别于解析过程,解析过程可能被阻塞)。同时主线程会对文档进行解析
css文件的解析
- header中的css的下载不会阻塞html的解析,当css文件下载完成,将阻塞html解析,优先解析css,会阻碍渲染。
- body中的css下载和解析都不会阻碍html的解析,会阻碍渲染。(存在特殊情况,见下文)
js文件的解析
1. 普通的script:
遇到 script 标签时,首先阻塞后续内容的解析(但是不会阻塞下载,chrome对同一个域名支持6个http同时下载),当下载完成后,便执行js文件中的同步代码。然后接着解析html
2. 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 -> 绘制paint
3.相关概念
dom构建
DOM 构建的意思是,将文档中的所有 DOM 元素构建成一个树型结构。
css构建
将文档中的所有 css 资源合并。生成css rule tree
render 树
将 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 -> 绘制paint
- html 文档中的图片资源,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%的人都不知道)
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:- 干货|前端程序员容易出错的基础知识
- 后端工程师学前端(二):CSS基础知识
- 前端必须掌握的知识之Http协议基础以及发展进程
- 基础知识:css3核心知识整理
- 密码学基础(二):数字证书、密钥基础知识
- SVG基础知识
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
从需求到产品:0岁产品经理进阶之道
权莉 / 人民邮电出版社 / 2018-7 / 49.80元
本书主要针对刚入职的初级产品经理,从贴近工作状态的场景切入,对各阶段的知识点进行分类总结,旨在提供一套经过实践检验的产品方法论,为读者从初级产品经理成长为产品经理奠定坚实的基础。 书中提炼的方法和案例涵盖初级产品经理工作的方方面面,从基本技能到思维方式,从需求管理到产品规划定义,从框架选型到流程梳理,从工作模块拆解到案例剖析,用具体且贴合实际工作场景的内容,还原真实的产品工作方法及实践案例,既有方......一起来看看 《从需求到产品:0岁产品经理进阶之道》 这本书的介绍吧!