手记

前端记述

HTML语义化

HTML语义化就是用合理、正确的标签来展示内容,比如h1~h6定义标题
易于用户阅读,样式丢失的时候能让页面呈现清晰的结构。
有利于SEO,搜索引擎根据标签来确定上下文和各个关键字的权重。
方便其他设备解析,如盲人阅读器根据语义渲染网页有利于开发和维护,语义化更具可读性,
代码更好维护,与CSS3关系更和谐
头部 导航栏 区块(有语义化的div) 主要区域 主要内容 侧边栏 底部 视频播放标签 音频播放标签 绘图标签

前端工程化

前端工程化是使用软件工程的技术和方法来进行前端的开发流程、技术、工具、经验等规范化、标准化,其主要目的为了提高效率和降低成本,即提高开发过程中的开发效率,减少不必要的重复工作时间
前端工程化可以从模块化、组件化、规范化、自动化四个方面思考。
  1. 模块化就是将一个大文件拆分成相互依赖的小文件,再进行统一的拼装和加载。

  2. 组件化是在设计层面上,对UI(用户界面)的拆分,重要是一种分治思想。

  3. 规范化是项目初期规范制定的好坏会直接影响到后期的开发质量。

  4. 自动化是任何简单机械的重复劳动都应该让机器去完成。图标合并,持续继承,自动化构建,自动化部署,自动化测试

meta的viewport用处

name为viewport表示供移动设备使用. content定义了viewport的属性.
width表示移动设设备下显示的宽度为设备宽度(device-width)
height表示移动设设备下显示的宽度为设备宽度.
user-scalable表示用户缩放能力, no表示不允许.
initial-scale表示设备与视口的缩放比率
maximum和minimum分别表示缩放的最大最小值, 要注意的是, maximum必须必minimum大.
上面的meta标签就是告诉浏览器, 不要在移动端显示的时候缩放.

position的sticky

sticky粘性定位

position:sticky是css3定位新增属性;可以说是相对定位relative和固定定位fixed的结合;它主要用在对scroll事件的监听上;简单来说,在滑动过程中,某个元素距离其父元素的距离达到sticky粘性定位的要求时(比如bottom:100px);position:sticky这时的效果相当于fixed定位,固定到适当位置。sticky 英文字面意思是粘,粘贴,所以可以把它称之为粘性定位。position: sticky; 基于用户的滚动位置来定位。粘性定位的元素是依赖于用户的滚动,在 position:relative 与 position:fixed 定位之间切换。它的行为就像 position:relative; 而当页面滚动超出目标区域时,它的表现就像 position:fixed;,它会固定在目标位置。元素定位表现为在跨越特定阈值前为相对定位,之后为固定定位。

绝对定位和相对定位

绝对定位

绝对定位是相对于元素最近的已定位的祖先元素,如果元素没有已定位的祖先元素,那么它的位置则是相对于最初的包含块(也就是body)。绝对定位本身与文档流无关,因此不占空间,通过z-index属性来控制这些层的相应顺序

相对定位

相对位置的坐标参考系是以自己上一次的位置,元素仍然占据原来的空间,移动元素会导致它覆盖其它框。
都会隐式地改变display的类型为display:inline-block

行内元素和块级元素

块级元素

div p ul ol

行内元素

span,a,img,button,input

css水平居中和垂直居中

水平居中

父元素是块级元素以及子元素是行内元素,父元素设置text-align: center;
子元素是行内元素和父元素是行内元素,先设置父元素display: block;转为块级元素,再设置text-align:center;
需要水平居中的元素是块级元素宽度定时,设置margin: 0 auto;
使用定位属性,父元素设置相对定位属性position: relative;,绝对定位属性给子元素设置
background-color: green;
position: absolute;
left: 50%;
margin-left: -50px;(transform: translateX(-50%))
使用flex布局进行设置,display: flex; justify-content: center;

flex布局

flex布局中align-items是针对侧轴y轴的布局,针对子元素内容为单行。
align-content是针对侧轴y轴的布局,针对子元素内容为多行。
justify-content针对主轴x轴的布局。

垂直居中

对于单行的行内元素,设置行高等于盒子的高度
对于多行的行内元素,给父元素设置display:table-cell;和vertical-align: middle;
使用定位属性,父元素设置相对定位属性position: relative;,绝对定位属性给子元素设置
background-color: green;
position: absolute;
top: 50%;
margin-top: -50px;(transform: translateY(-50%))
使用flex布局进行设置, display: flex; align-items: center;

原始类型symbol

symbol表示独一无二的值,最大的用法是用来定义对象的唯一属性名,防止对象中属性名重复。
let sy = Symbol(“key1”); let syObject = {}; syObject[sy] = “kk”; console.log(syObject); // {Symbol(key1): “kk”}

判断一个对象是否为空

最常见的思路,for…in…遍历属性,为真则为“非空对象”;否则为“空对象”
通过JSON自带的.stringify方法判断(上传数据常用)
当然就是ES6的语法啦,Object.keys();

判断是否是一个数组

Array.isArray([]);可以进行判断。

判断一个数组是否为空

数组的length是否为零来进行判断。
进行for循环,当数组为空时for循环不会执行。

原型链

作用域链

执行期上下文:当函数执行时,会创建一个称为执行期上下文的内部对象(即AO或GO),一个执行期上下文定义了一个函数的执行环境,函数每次执行时对应的执行期上下文都是独一无二的,所以每次调用一个函数都会创建一个新的执行期上下文,当函数执行完毕,所产生的执行期上下文被销毁。作用域链就是函数中[[scope]]属性所存储的执行期上下文对象的集合,这个集合呈链式链接,我们把这种链式链接叫做作用域链。

instanceof和isprototypeof

instanceof : a instanceof b 判断a的原型链中是否有b的原型,这个是针对某个的原型去检查
isprototypeof: Object.prototype isprototypeof a 判断a的原型链上有没有Object.prototype,这个是针对某个的本身去检查。
for in 遍历的是数组的序号或者对象的key值,for of 遍历的是迭代器也就是数组类似set,map,array,nodelist对象的每一项。
for … in循环由于历史遗留问题,它遍历的实际上是对象的属性名称。一个Array数组实际上也是一个对象,它的每个元素的索引被视为一个属性。
对于

var a = [‘A’, ‘B’, ‘C’];
a.name = ‘Hello’;

for … in循环将把name包括在内,但Array的length属性却不包括在内。
遍历Array可以采用下标循环,遍历Map和Set就无法使用下标,所以在es6里面引入了for of进行遍历。

set和map和array

Set: ES6 提供了新的数据结构。它类似于数组,但是成员的值都是唯一的,没有重复的值。Set本身是一个构造函数,用来生成 Set 数据结构。
Map: ES6 提供了新的数据结构。它类似于对象,也是键值对的集合,但是“键”的范围不限于字符串,各种类型的值(包括对象)都可以当作键。Map 结构提供了“值—值”的对应。clear(): clear方法清除所有成员,没有返回值。delete(key): 删除某个键,返回true。如果删除失败,返回false。set(key, value)set方法设置键名key对应的键值为value,然后返回整个 Map 结构。如果key已经有值,则键值会被更新,否则就新生成该键。返回的是当前的Map对象。get(key): get方法读取key对应的键值,如果找不到key,返回undefined。has(key): 返回一个布尔值,表示某个键是否在当前 Map 对象之中。

js继承方法总结

堆内存和栈内存

堆内存和栈内存

栈:栈会自动分配内存空间,物理内存是连续的,存放基本类型,简单的数据段, 占据固定大小的空间。
基本类型:String,Number,Boolean,Null,Undefined

堆:动态分配的内存,物理地址不连续,大小不定也不会自动释放,存放引用类型, 指那些可能由多个值构成的对象,保存在堆内存中,包含引用类型的变量,实际上保存的不是变量本身,而是指向该对象的指针。
引用类型:Function,Array,Object

它们的区别

栈:所有在方法中定义的变量都是放在栈内存中,随着方法的执行结束,这个方法的内存栈也自然销毁。
栈中是进行传值操作。

优点:存取速度比堆快,仅次于直接位于CPU中的寄存器,数据可以共享;
缺点:存在栈中的数据大小与生存期必须是确定的,缺乏灵活性。

堆:堆内存中的对象不会随方法的结束而销毁,即使方法结束后,这个对象还可能被另一个引用变量所引用(参数传递)。
堆中进行传址操作

浅拷贝和深拷贝

浅拷贝:也就是只复制了第一层属性,复制对象是基本类型, 在复制基本类型时,直接使用等号完成,在复制引用类型时,循环遍历对象,对每个属性或值使用等号完成。
深拷贝:对属性中所有引用类型的值,遍历到是基本类型的值为止,利用递归来实现深拷贝。

原型链实现继承

function SuperType() {}
function SubType() {}

SubType.prototype = new SuperType();
var instance = new SubType();

圣杯模式

好处为Son.prototype添加属性时,全部都会加在这个对象里面,所以不会对父级产生影响

function inherit(Target,Origin){
function F(){};
F.prototype=Origin.prototype;
Target.prototype=new F();
}

借用构造函数继承

function SuperType() {
this.colors = [“red”,“blue”,“green”]
}
function SubType() {
SuperType.call(this); // 实现继承
}
var instance1 = new SubType();
var instance2 = new SubType();

instance2.colors.push(“black”);
console.log(instance1.colors"); // red,blue,green
console.log(instance2.colors"); //red,blue,green,black

组合式继承,能继承原型的方法和属性又可以继承实例的方法和属性

function SuperType(name) {
this.name = name;
this.colors = [“red”,“blue”,“green”]
}
SuperType.prototype.sayName = function() {
console.log(this.name);
}
function SubType(name,age) {
SuperType.call(this,name);
this.age = age;
}
SubType.prototype = new SuperType();
SubType.prototype.constructor = SubType;
SubType.prototype.sayAge = function() {
console.log(this.age);
}

var instance1 = new SubType(“Nicholas”, 29);
var instance2 =new SubType(“Greg”, 27);
instance1.colors.push(“black”);
console.log(instance1.colors); // red,blue,green,black
console.log(instance2.colors); // red,blue,green
instance1.sayName(); // Nicholas
instance2.sayName(); // 29
instance1.sayAge(); // Greg
instance2.sayAge(); // 27

寄生式继承

创建一个仅用于封装继承过程的函数,该函数在内部以某种方法来增强对象,最后返回该对象。

function createAnother(original) {
var clone = Object(original); // 通过调用函数创建一个新对象
clone.sayHi = function() {
console.log(“hi”);
}
return clone;
}

寄生组合式继承

避免了在SubType.prototype和SubType的实例上都会创建name和colors属性,最后SubType的实例上的name和colors属性会屏蔽掉SubType.prototype上的name和colors属性

function inheritPrototype(subType,SuperType) {
var prototype = Object(SuperType); // 创建对象
prototype.constructor = subType; // 增强对象
subType.prototype = prototype; // 指定对象
}

function SuperType(name) {
this.name = name;
this.colors = [“red”,“blue”,“green”]
}
SuperType.prototype.sayName = function() {
console.log(this.name);
}
function SubType(name,age) {
SuperType.call(this,name);
this.age = age;
}
inheritPrototype(SubType,SuperType);
SubType.prototype.sayAge = function() {
console.log(this.age);
}

es6中的继承

class Point {
}

class ColorPoint extends Point {
}

http的各个版本和几种请求方式对比

http 1.0 不能进行长链接,链接无法复用
http 1.1 支持长链接(在request和response中的header中的connection是close或者Keep-Alive进行控制)。支持http管道,客户端可以并行,服务端串行。客户端可以不用等待前一个请求返回,发送请求,但服务器端必须顺序的返回客户端的请求响应结果,这样会导致线头阻塞,一旦一个延迟后面的都会延迟。使用多个TCP链接,一般6个TCP链接,数字过大会导致资源占用过多。
http 2.0

支持多路复用,要给链接可以发多个请求通过requestid来区别。
新的二进制格式传输,不在基于文件。
服务端推送,服务器可以对客户端的一个请求发送多个响应。请求优先级,服务器端就可以根据优先级,控制资源分配,优先处理和返回最高优先级的请求帧给客户端。http2.0 主动push缓存,不需要等待对网页内容的解析再去发起请求,直接后端在response的header中进行推送文件,比如link一个css文件。

几种请求方式
GET:向特定的资源发出请求。
POST:向指定资源提交数据进行处理请求(例如提交表单或者上传文件)。数据被包含在请求体中。POST请求可能会导致新的资源的创建和/或已有资源的修改。
OPTIONS:返回服务器针对特定资源所支持的HTTP请求方法。也可以利用向Web服务器发送’*'的请求来测试服务器的功能性。
HEAD:向服务器索要与GET请求相一致的响应,只不过响应体将不会被返回。这一方法可以在不必传输整个响应内容的情况下,就可以获取包含在响应消息头中的元信息。
PUT:向指定资源位置上传其最新内容。
DELETE:请求服务器删除Request-URI所标识的资源。
TRACE:回显服务器收到的请求,主要用于测试或诊断。
CONNECT:HTTP/1.1协议中预留给能够将连接改为管道方式的代理服务器。
get和post的区别

get传送的数据量较小,不能大于2KB。post传送的数据量较大,一般被默认为不受限制。但理论上,IIS4中最大量为80KB,IIS5中为 100KB。

get和post理论上都没有数据大小限制,但是浏览器本身对url长度有限制,IE为2083B,火狐、chrome等为4098B。post数据时无论多大都不会报错,而是浏览器会崩溃。

options的作用

1、获取服务器支持的HTTP请求方法;
2、用来检查服务器的性能。例如:AJAX进行跨域请求时的预检,需要向另外一个域名的资源发送一个HTTP OPTIONS请求头,用以判断实际发送的请求是否安全。这是浏览器给我们加上的,后端并没有做任何操作。(简单来说就是对服务器的预检,比如跨域的时候先发一个请求验证安全性。比如进行跨域设置了超过content-type的下列内容之外的内容:
application/x-www-form-urlencoded
multipart/form-data
text/plain)

cores机制

CORS是通过添加新的HTTP header头实现的,浏览器在请求头部信息额外设置Origin: origin address,服务器响应的header中将会有Access-Control-Allow-Origin: 允许的address,若允许的address为* ,则表示可接收任意源的请求,否则需要判断origin address是否在允许的address中。

xmlHttpRequest状态码与状态值

xmlHttpRequest状态值

无论成功与否都会响应的步骤,相当于请求状态的流程
0 - (未初始化)还没有调用send()方法 1 - (载入)已调用send()方法,正在发送请求 2 - (载入完成)send()方法执行完成, 3 - (交互)正在解析响应内容 4 - (完成)响应内容解析完成,其中“0”状态是在定义后自动具有的状态值,而对于成功访问的状态(得到信息)我们大多数采用“4”进行判断。

xmlHttpRequest状态码(http状态码)

描述当前请求是否成功
200OK请求成功。一般用于GET与POST请求
301请求的页面永久性移到了新的url下,永久重定向;
302Found临时移动。与301类似。但资源只是临时被移动。客户端应继续使用原有URI
304Not Modified未修改。所请求的资源未修改,服务器返回此状态码时,不会返回任何资源。客户端通常会缓存访问过的资源,通过提供一个头信息指出客户端希望只返回在指定日期之后修改的资源
303明确表示客户端应当采用get方法获取资源,请求方式异常。
307 Temporary Redirect临时重定向。该状态码与302有相同的含义。尽管302标准禁止post变化get,但实际使用时大家不遵守。307会遵照浏览器标准,不会从post变为get。
400Bad Request客户端请求的语法错误,服务器无法理解
403Forbidden服务器理解请求客户端的请求,但是拒绝执行此请求
404Not Found服务器无法根据客户端的请求找到资源(网页)。通过此代码,网站设计人员可设置"您所请求的资源无法找到"的个性页面
405Method Not Allowed客户端请求中的方法被禁止
500Internal Server Error服务器内部错误,无法完成请求
505HTTP Version not supported服务器不支持请求的HTTP协议的版本,无法完成处理
502Bad Gateway作为网关或者代理工作的服务器尝试执行请求时,从远程服务器接收到了一个无效的响应
503由于临时的服务器维护或者过载,服务器当前无法处理请求。
503由于临时的服务器维护或者过载,服务器当前无法处理请求。
504 Gateway Time-out
501:服务器不具备完成请求的功能,服务器错误

事件循环(event-loop)

javascript调控同步和异步任务的机制称为事件循环

1、所有同步任务都在主线程上执行,形成一个执行栈
2、主线程之外,还存在一个"消息队列"。只要异步操作执行完成,就到消息队列中排队
3、一旦执行栈中的所有同步任务执行完毕,系统就会按次序读取消息队列中的异步任务,于是被读取的异步任务结束等待状态,进入执行栈,开始执行
4、主线程不断重复上面的第三步

宏仁务如果存在,一个个执行,每次执行前都会清空微任务队列。

运行机制

新版v8中

点击(此处)折叠或打开

  1. console.log(‘script start’)

  2. async function async1() {

  3. await async2()

  4. console.log(‘async1 end’)

  5. }

  6. async function async2() {

  7. console.log(‘async2 end’)

  8. return Promise.resolve().then(()=>{

  9. console.log(‘async2 end1’)

  10. })

  11. }

  12. async1()

  13. setTimeout(function() {

  14. console.log(‘setTimeout’)

  15. }, 0)

  16. new Promise(resolve => {

  17. console.log(‘Promise’)

  18. resolve()

  19. })

  20. .then(function() {

  21. console.log(‘promise1’)

  22. })

  23. .then(function() {

  24. console.log(‘promise2’)

  25. })

  26. console.log(‘script end’)

script start => async2 end => Promise => script end => async2 end1 => promise1 => promise2 => async1 end => setTimeout

解释:

此时执行完awit并不先把await后面的代码注册到微任务队列中去,而是执行完await之后,直接跳出async1函数,执行其他代码。然后遇到promise的时候,把promise.then注册为微任务。其他代码执行完毕后,需要回到async1函数去执行剩下的代码,然后把await后面的代码注册到微任务队列当中,注意此时微任务队列中是有之前注册的微任务的。所以这种情况会先执行async1函数之外的微任务(promise1,promise2),然后才执行async1内注册的微任务(async1 end).可以理解为,这种情况下,await 后面的代码会在本轮循环的最后被执行.

为什么要分微任务和宏仁务

微任务是线程之间的切换,速度快。不用进行上下文切换,可以快速的一次性做完所有的微任务。宏任务是进程之间的切换,速度慢,且每次执行需要切换上下文。因此一个Eventloop中只执行一个宏任务。而区分微任务和宏任务的根本原因是为了插队。由于微任务执行快,一次性可以执行很多个,在当前宏任务执行后立刻清空微任务可以达到伪同步的效果,这对视图渲染效果起到至关重要的作用。

同步和异步任务便于区分promise中的函数为同步任务进入主线程中执行。
对于异步任务会区分宏任务和微任务,宏任务是script、settimeout、settimeinterval;微任务是promise的then方法、process.nexttick;
主线程结束后执行微任务队列中的任务,之后再去宏任务队列中取任务进行执行。执行完微任务再执行宏任务。

跨域解决方案

浏览器为什么要做同源策略限制

同源策略限制的是(1)Cookie、LocalStorage、sessionStorage和 IndexDB 无法读取。(2) DOM 节点无法读取和设置。(3) AJAX 请求不能发送。
客户端可以正常地向服务端发出请求。但是,由于浏览器的同源限制策略,服务端响应的数据会被浏览器过滤掉,并抛出常见的跨域报错
如果没有 DOM 同源策略,也就是说不同域的 iframe 之间可以相互访问,那么黑客可以这样进行攻击
如果 XMLHttpRequest 没有同源策略,那么黑客可以进行 CSRF(跨站请求伪造) 攻击
在浏览器端发会带上对应域的 cookie。而 cookie 是存在浏览器端的,当然跟你服务器端发不一样。
跨域资源嵌入既是通过src属性来实现的。举例来说,假如A网站(www.aaa.com)需要展示一份豆瓣图书销量前一百的名单,由于同源策略的存在,A网站上的一个脚本(我们把这个脚本记作a)不能通过XHR请求来获取豆瓣上的数据,但是可以通过部分标签的src属性来跨域获取该书单数据。具体过程是,A网站上的a运行,“调用”豆瓣的某个脚本(记作b),豆瓣通过“脚本”b(应该说是一个接口,但接口的功能也是通过脚本实现的,所以在这里我用“脚本”来表示。)将书单数据传给A网站。我们可以把书单数据当做是b运行的结果,此过程中浏览器会限制a对豆瓣“脚本”b内容的读写,仅仅是只能“调用”,而不是限制对书单数据的读写。脚本b就是运行脚本a的返回内容,所以这样就解释了为什么通过src属性加载的资源,浏览器限制了JavaScript的权限,使其不能读写返回的内容,这段话并不是前后矛盾的

跨域是浏览器做出的限制

websocket的api采用了自定义的ws服务,进行前后端跨域交互

jsonp通过script标签的src不受同源策略限制,进行交互访问

cors机制通过前端设置withCredentials为true允许传输的时候携带cookie,后端设置Access-Control-Allow-Origin为允许的域。

通过iframe标签进行事件的传输,利用postMessage事件,windows.onmessage进行事件监听。

俩个页面跨域交互时可以通过设置相同的document.domain来进行跨域访问,都是利用iframe进行访问。

window.name可以进行跨域,当一个页面载入多个页面的时候,共享同一个window.name,该页面以及所有载入的页面都可以对共享的window.name进行读写,也是利用iframe进行页面互相传值。

利用location.hash进行跨域,通过window.onhashchange拿到返回值。

服务器端代理的方式,服务器访问服务器是不存在跨域问题的,服务器间通过隧道方式传输基于 TCP 的协议

进行ngix代理进行跨域,可以进行ngix正向代理来解决跨域,正向代理其实就是通过服务器进行代理。


页面跳转的差异


ngix代理

ngix正向代理

ngix代理多个客户端请求,服务器端不知道是哪个客户端对它发起的请求

ngix反向代理

ngix代理多台服务器来接收客户端的请求,客服端是明确的,但是不知道哪个服务器来处理请求。

ngix负载均衡

客户端的请求量就是负载,当采用反向代理的时候怎么才能让每台服务器都能均衡的进行对负载的处理,不会造成某一台服务器的过载。
一般可以采用硬件均衡和软件均衡,软件均衡采用轮询方式、ip_hash的方式、url_hash的方式,url_hash采用对url进行hash计算的方式,一般指向后台固定服务器。ip_hash通过ip自带的hash值进行服务器的分配。轮询就是通过顺序逐一分配到每台服务器,出现宕机情况可以将服务器从队列中剔除,可以通过配置权重值来增加服务器被分配到的概率。

单例模式

//懒汉单例模式
var CreateDiv = (function(){       
var instance;
var CreateDived = function( ){           
if ( instance ){
return instance;           
}
CreateDived.prototype.init = ()=>{
console.log(‘a—initname’);
}
   return instance = this;
};
  return CreateDived;
})();

函数柯里化

概念: 在一个函数中,首先填充几个参数, 然后返回一个新的函数的技术,被称为函数柯里化
作用: 通常用于在不侵入函数的情况下,为函数预置通用参数, 便于重复调用

function add(x) {

return function(y) {

return x + y

}}
};
const fn = add(1);

fn(1) // 2;

fn(20) // 21;

let、const与var不同

  1. 块级作用域: 块作用域由 { }包括,let和const具有块级作用域,var不存在块级作用域。

  2. 变量提升: var存在变量提升,let和const不存在变量提升但是会存在暂时性死区

  3. 重复声明: var声明变量时,可以重复声明变量,后声明的同名变量会覆盖之前声明的遍历。const和let不允许重复声明变量。

  4. 初始值设置: 在变量声明时,var 和 let 可以不用设置初始值。而const声明变量必须设置初始值。

  5. 指针指向: let和const都是ES6新增的用于创建变量的语法。 let创建的变量是可以更改指针指向(可以重新赋值)。但
    const保证的并不是变量的值不能改动,而是变量指向的那个内存地址不能改动,所以对象等引用类型可以继续添加值。

手写const

点击(此处)折叠或打开

  1. function getConst(key, val, isConst = true) {

  2. window[key] = val;

  3. let constVal = window[key];

  4. Object.defineProperty(window, key, {

  5. get: () => {

  6. return constVal;

  7. },

  8. // value: val,

  9. // writable: false,

  10. configurable: false, //配置信息是否可修改

  11. enumerable: true, //是否可枚举

  12. });

  13. }

  14. getConst(“age”, 10);

  15. age = 20;

  16. console.log(age);

手写let

点击(此处)折叠或打开

  1. function getConst(key, val, isConst = true) {

  2. global[key] = val;

  3. var constValue = val

  4. Object.defineProperty(global, key, {

  5. get: () => {

  6. return constValue;

  7. },

  8. set: (value) => {

  9. constValue = value

  10. },

  11. // value: val,

  12. // writable: false,

  13. configurable: false, //配置信息是否可修改

  14. enumerable: true, //是否可枚举

  15. });

  16. }

  17. getConst(“age”, 10);

  18. age = 20;

  19. console.log(age);

手写Promise

function Promise(fn) { // 定义状态的常量
const PENDING = 'pending’
const RESOLVE = 'resolve’
const REJECT = ‘reject’ // 为了正确的获取 this
const that = this // 初始状态应该为 pending
that.state = PENDING // 用来报错 resolve 或者 reject 中保存的值
this.value = null // 用来保存 then 中的回调
that.resolveCallBack = []
that.rejectCallBack = []
// 实现 resolve 和 reject 函数
function resolve(value) {
if(that.state === PENDING) {
that.state = RESOLVE
that.value = value
that.resolveCallBack.map(cb => cb(that.value))
}
}
function reject(value) {
if(that.state === PENDING) {
that.state = REJECT
that.value = value
that.rejectCallBack.map(cb => cb(value))
}
}
try {
fn(resolve, reject)
} catch (error) {
reject(error)
}}
Promise.prototype.then = function(onResolve, onReject) {
const that = this
// 判断用户传入的是不是 函数类型 不是的话手动改成函数类型
onResolve = typeof onResolve === ‘function’ ? onResolve : onResolve => onResolve
onReject = typeof onReject === ‘function’ ? onReject : onReject => { throw onReject }
// 如果当前的状态是 pending, 就将回调函数放入到 回调数组中
if(that.state === PENDING) {
that.resolveCallBack.push(onResolve)
that.rejectCallBack.push(onReject)
}
if(that.state === RESOLVE) {
onResolve(that.value)
}
if(that.state === REJECT) {
onReject(that.value)
}}
new MyPromise((resolve, reject) => { setTimeout(() => { resolve(1) }, 0)}).then(value => { console.log(value)})

手写call

Function.prototype.myCall = function(context) {
if(typeof context !== ‘function’) {
throw new Error(‘error’) } //1. 因为 context 不是必传的, 所以我们设置它默认为 window
context = context || window //2. 给 context 上面添加一个 fn 属性, 设置成我们需要调用的函数 也就是 array, this 在这里就是 array 方法
context.fn = this //3. 获取我们传入的参数,因为参数可以是多个, 所以我们使用 arguments 截取
const args = […arguments].slice(1) //4. 执行方法
const result = context.fn(…args) //5. 删除我们创建的临时 fn 方法 delete context.fn
return result
}

手写apply

Function.prototype.myApply = function(context) {
if(context !== ‘function’) {
throw new Error(‘error’)
}
context = context || window
context.fn = this // 参数的处理不同
let result
if(arguments[1]) {
result = context.fn(…arguments[1])
} else {
retult = context.fn()
}
delete context.fn
return result
}

手写bind

Function.prototype.myBind = function (context) {
if (typeof this !== ‘function’) {
throw new TypeError(‘Error’)
}
const _this = this
const args = […arguments].slice(1) // 返回一个函数
return function F() { // 因为返回了一个函数,我们可以 new F(),所以需要判断
if (this instanceof F) {
return new _this(…args)
}
return _this.apply(context, args)
}
}

任意一个函数里面this指向window(非严格模式)。

严格模式下函数里面this指向undefined
在普通模式下,普通函数,谁调用这个函数,this就指向谁
箭头函数this指向定义这个箭头函数时所在环境中的this,在哪个地方定义就指向那个地方的this

手动实现new

function create(Con, …arg) { //1. 创建一个空对象
let obj = {} //2. 获取构造函数
obj.proto = Con.prototype
let result = Con.apply(obj, arg) //4. 绑定 this 并执行构造函数
return result instanceof Object ? result : obj //5. 确保返回值为对象
}

Object.create

Object.create创造出的对象_proto_ === Object.prototype
Object.create(null) 创造出的是个什么属性都没有的对象,proto !== Object.prototype

this指向的优先级

new> bind > call( apply ) >obj.function() > 默认绑定

的作用

是html5标准网页声明,声明当前是html还是xhtml规范
DOCTYPE的作用和用法:作用:声明文档的解析类型,

怪异盒模型和标准盒模型

通过box-sizing:content-box(标准盒模型)/ border-box(怪异盒模型)

IE盒模型(怪异盒模型)

width = width + padding + border

标准盒模型

width = width

js的严格模式和非严格模式区别

在script标签内部写上“use strict”就可以进入严格模式,删除则进入非严格模式。

严格模式:
1、在严格模式中,所有的变量都要先声明
2、在严格模式中,调用的函数(不是方法)中的一个this值是undefined
3、当通过call()或apply()来调用函数时,其中的this值就是通过call()或apply()传入的第一个参数
4、在严格模式中,给只读属性赋值和给不可扩展的对象创建新成员都将抛出一个类型错误异常
5、在严格模式中,传入eval()的代码不能在调用程序所在的上下文中声明变量或定义函数
6、在严格模式中,函数里的arguments对象拥有传入函数值的静态副本
7、在严格模式中,在一个对象直接量中定义两个或多个同名属性将产生一个语法错误

非严格模式:
1、在非严格模式中,给只读属性赋值和给不可扩展的对象创建新成员都将简单地操作失败,不会报错
2、在非严格模式中,调用的函数(不是方法)中的一个this值是windows对象
3、在非严格模式中,传入eval()的代码能在调用程序所在的上下文中声明变量或定义函数,变量和函数的定义是在eval()创建的新作用域中,这个作用域在eval()返回时就弃用了。
4、在非严格模式中,arguments对象具有“魔术般”的行为,arguments里的数组元素和函数参数都是指向同一个值的引用。
5、在非严格模式中,在一个对象直接量中定义两个或多个同名属性不会报错。

手动实现instanceof

点击(此处)折叠或打开

  1. function instance ( left, right ){

  2. if( left === null && typeof left !== ‘Object’ ){

  3. return false

  4. }

  5. let proto = Object.getPrototypeOf(left)

  6. const prototype = right.prototype

  7. while(true){

  8. if( proto === null ){ return false }

  9. if(proto === prototype){

  10. return true

  11. }

  12. proto = Object.getPrototypeOf(proto)

  13. }

  14. return false

  15. }

函数节流和防抖

函数防抖,就是指触发事件后在 n 秒内函数只能执行一次,如果在 n 秒内又触发了事件,则会重新计算函数执行时间。
函数节流是限制一个函数在一定时间内只能执行一次。

函数节流

n秒内只执行一次
function throttle(fn, wait) {
let prev = new Date().getTime();
return function() {
const args = arguments
const now = new Date().getTime();
if(now - prev > wait*1000) {
fn.apply(this, args)
prev = new Date().getTime();
}
}
}

函数防抖

n秒之后才执行事件

function debounce(fn, wait) {
let timer;
return function() {
if (timer){
clearTimeout(timer)
}
timer = setTimeout(() => {
fn.apply(this, arguments)
}, wait)
}
}

清除浮动

四种方法:万能清除法 after伪类;clear清除浮动;给父级添加overflow: hidden;父级也同时浮动

.box:: after {
content: ‘’, clear: both;
display: block;
height: 0;
overflow: hidden;
visibility: hidden;
}
.box {
zoom: 1;
}

伪类和伪元素

伪元素主要是用来创建一些不存在原有dom结构树种的元素,例如:用::before和::after在一些存在的元素前后添加文字样式等,这些被添加的内容会以具体的UI显示出来,被用户所看到的,这些内容不会改变文档的内容,不会出现在DOM中,不可复制
伪类表示已存在的某个元素处于某种状态,但是通过dom树又无法表示这种状态,就可以通过伪类来为其添加样式

babel编译原理

ES6、ES7代码输入
babylon 进行解析
得到AST抽象语法树
plugin用babel-traverse对AST抽象语法树进行遍历转义
得到新的AST树
使用babel-generator通过AST树生成ES5代码

iterator和generator

iterator

1、迭代器需要实现一个next方法,该方法接收一个参数或者无参数。
2、next方法的返回值为一个对象,该对象中有done属性和value属性。如果返回无下一个值则done为false,否则为true。

generator

点击(此处)折叠或打开

  1. function* getName() {

  2. console.log(‘函数开始执行’);

  3. const value1 = ‘tom’

  4. console.log(value1)

  5. yield value1

  6. const value2 = ‘mary’

  7. console.log(value2);

  8. yield value2

  9. console.log(‘函数执行结束’)

  10. }

  11. getName函数运行后返回的也是一个生成器。生成器返回值都是一个生成器。

生成器是一种特殊的迭代器。所以它也有next方法。

JS的精度不够出现大小数

js中的数字分为符号位,指数位和尾数位,但是js总共只有52位字符的长度,所以对于位运算溢出的情况,会采取从最右边开始舍去进行四舍五入的近似,这样可以减少近似带来的误差,所以会出现0.1 + 0.2 != 0.3的情况出现。

js的变量提升

js中的变量提升是函数声明先被提升,之后提升变量声明,函数提升优先级比变量提升要高,且不会被变量声明覆盖,但是会被变量赋值覆盖。

面试题

[].forEach.call($$(’*’), function(a) {
a.style.outline = “1px solid #” + (~~(Math.random() * (1 << 24))).toString(16)
})

获取页面所有的元素,然后给这些元素加上 1px 的外边框,并且使用了随机颜色

解析
结果: 会给页面上所有的元素添加一个1px的边框
$$("*")是以数组的方式返回页面上的所有元素
[].forEach是一种简写, 完整的因该是Array.prototype.forEachcall是一个prototype, js 函数内置的,
call使用第一个参数替换掉上面说的这个this,也就是你要传入的数组
因此,[].forEach.call是一种快速的方式访问forEach,并将空数组的this换成想要遍历的list

数组扁平化管理

function flatten(arr) {
return arr.reduce(function(prev, item) {
return prev.concat(Array.isArray(item) ? flatten(item) : item)
},[])
}

pop方法

移除最后一个数组元素

取反操作符

~~ 就是执行两次按位取反
~是js里的按位取反操作符

实现超出范围的字符串整数相加

点击(此处)折叠或打开

  1. function add (a,b) {

  2. let lenA = a.length,

  3. lenB = b.length,

  4. len = lenA > lenB ? lenA : lenB; // 先补齐位数一致

  5. if(lenA > lenB) {

  6. for(let i = 0; i < lenA - lenB; i++) {

  7. b = ‘0’ + b;

  8. }

  9. } else {

  10. for(let i = 0; i < lenB - lenA; i++) {

  11. a = ‘0’ + a;

  12. }

  13. }

  14. let arrA = a.split(’’).reverse(),

  15. arrB = b.split(’’).reverse(),

  16. arr = [],

  17. carryAdd = 0;

  18. for(let i = 0; i < len; i++) {

  19. let temp = Number(arrA[i]) + Number(arrB[i]) + carryAdd;

  20. arr[i] = temp > 9 ? temp - 10 : temp;

  21. carryAdd = temp >= 10 ? 1 : 0;

  22. }

  23. if(carryAdd === 1) {

  24. arr[len] = 1;

  25. }

  26. return arr.reverse().join(’’);

  27. }

实现简单的parseInt函数

function str2num(str) {
var strArr = str.split(’’)
var strArrNum = strArr.map(function (str) {
return +str
})
var num = strArrNum.reduce(function (x, y) {
return x * 10 + y
})
return num
}

http连接的三个状态

http是无状态协议。无状态是指协议对于事务处理没有记忆能力。缺少状态意味着如果后续处理需要前面的信息,则它必须重传,这样可能导致每次连接传送的数据量增大。另一方面,在服务器不需要先前信息时它的应答就较快。
HTTP是媒体独立的。这意味着,只要客户端和服务器知道如何处理的数据内容,任何类型的数据都可以通过HTTP发送。客户端以及服务器指定使用适合的MIME-type内容类型。
HTTP是无连接。无连接的含义是限制每次连接只处理一个请求。服务器处理完客户的请求,并收到客户的应答后,即断开连接。采用这种方式可以节省传输时间

理解http的无状态和tcp的有状态

http是无状态协议。无状态是指协议对于事务处理没有记忆能力。缺少状态意味着如果后续处理需要前面的信息,则它必须重传,这样可能导致每次连接传送的数据量增大。另一方面,在服务器不需要先前信息时它的应答就较快。比如cookie和session
tcp 则可以把登陆信息与channel绑定,<channel, uid>,又如Netty Attribute。就是说它的实现是通过有穷自动机的原理实现的。就是说:有若干个状态,状态之间的转换通过一个个事件来触发。
tcp的TIME_WAIT在LINUX中最大为60秒

tcp是三次握手不是俩次握手

主要为了避免因为请求在某一结点逗留太久而导致连接一直存在,白白消耗服务器资源。

发布订阅系统

const EventList = {
}
const on = function(eventName,callback){
if(!EventList[eventName]){
EventList[eventName] = [];
}
EventList[eventName].push(callback);
}
const emit = function(eventName,params){
if(!EventList[eventName])return;
EventList[eventName].map((cb)=>{
cb(params)
})
}
const off = function(eventName,callback){
if(!EventList[eventName])return;
if(callback){
let index = EventList[eventName].indexOf(callback);
EventList[eventName].splice(index,1);
}else{
EventList[eventName] = [];
}
}
export default {
on:on,emit:emit,
$off:off
}

双向绑定原理


首先再vue初始化的时候,就对data数据进行了劫持监听,其中就是监听器 Observe,用来监听所有属性。
若有属性发生变化就需要告诉订阅者Watcher看是否需要更新。
因为订阅者Watcher有多个,所以需要一个消息订阅器 Dep 来专门收集这些订阅者,在监听器Observe和订阅者Watcher之间进行统一管理。
还需要有一个指令解析器 Compile ,对每个节点元素进行扫描解析,将相关的指令(如 v-model,v-on …)对应初始化成一个订阅者Watcher,并替换模板数据或者绑定相应函数
当订阅者Watcher接收到相应属性的变化通知,就会执行对应的更新函数,从而去更新视图。

vue的render函数的参数

一个createElement函数,简称h。
h接受三个参数,
一个htm字符串或组件选项对象或
解析上述任何一种的一个 async 异步函数,必要参数。

// {Object}
// 一个包含模板相关属性的数据对象
// 这样,您可以在 template 中使用这些属性。可选参数。

// {String | Array}
// 子节点 (VNodes),由 createElement() 构建而成,
// 或使用字符串来生成“文本节点”。可选参数。

[
‘先写一些文字’,
createElement(‘h1’, ‘一则头条’),
createElement(MyComponent, {
props: {
someProp: ‘foobar’
}
})
]
)

单向数据流和双向数据流

和angular和vue是实现了双向数据绑定实现了双向数据流,其中也实现了单向数据绑定实现了单向数据流,
简单的单向数据流(unidirectional data flow)是指用户访问View,View发出用户交互的Action,在Action里对state进行相应更新。state更新后会触发View更新页面的过程。这样数据总是清晰的单向进行流动,便于维护并且可以预测。
双向数据绑定,带来双向数据流。数据(state)和视图(View)之间的双向绑定。可以简单理解为value的单向数据流和change事件的监听的语法糖。

事件循环中js的微任务和宏任务

微任务

process.nextTick,MutationObserver,Promise的then catch finally
process.nextTick 永远大于 promise.then,原因其实很简单。。。在Node中,_tickCallback在每一次执行完TaskQueue中的一个任务后被调用,而这个_tickCallback中实质上干了两件事:1.nextTickQueue中所有任务执行掉(长度最大1e4,Node版本v6.9.1)2.第一步执行完后执行_runMicrotasks函数,执行microtask中的部分(promise.then注册的回调)
Mutation Observer API 用来监视 DOM 变动。DOM 的任何变动,比如节点的增减、属性的变动、文本内容的变动,事件是同步触发的,然而它是异步触发的。

宏任务

requestAnimationFrame,setImmediate,setInterval,setTimeout
microtask queue:唯一,整个事件循环当中,仅存在一个;执行为同步,同一个事件循环中的microtask会按队列顺序,串行执行完毕;
macrotask queue:不唯一,存在一定的优先级(用户I/O部分优先级更高);异步执行,同一事件循环中,只执行一个。
触发macrotask任务的操作包括:
  1. script(整体代码)

  2. setTimeout、setInterval、setImmediate

  3. I/O

  4. UI交互事件

  5. postMessage、MessageChannel

HTML5规范规定了最小延迟时间不得小于4ms,即如果x小于4,会被当做4来处理。

遇到 Promise,是主线程直接执行的。

step1:主线程读取JS代码,此时为同步环境,形成相应的堆和执行栈;
step2: 主线程遇到异步任务,指给对应的异步进程进行处理(WEB API);
step3: 异步进程处理完毕(Ajax返回、DOM事件处罚、Timer到等),将相应的异步任务推入任务队列;
step4:主线程查询任务队列,执行microtask queue,将其按序执行,全部执行完毕;
step5:主线程查询任务队列,执行macrotask queue,取队首任务执行,执行完毕;
step6:重复step4、step5。
利用microtask queue可以形成一个同步执行的环境,但如果Microtask queue太长,将导致Macrotask任务长时间执行不了,最终导致用户I/O无响应等

node的事件循环六个阶段

轮询阶段(poll)–>检查阶段(check)–>关闭事件回调阶段(close callback)–>定时器检测阶段(timer)–>I/O事件回调阶段(I/O callbacks)–>闲置阶段(idle, prepare)

node的异步队列

node常见的异步队列是宏任务队列和微任务队列
宏任务队列包括定时器,setImmediate,script,I/O操作等
微任务队列包括process.nextTick 、 new Promise().then() 、 mutationObserver等。

setImmediate和setTimeout

只有IE才支持这个api。
首先进入的是timers阶段,如果我们的机器性能一般,那么进入timers阶段,一毫秒已经过去了(setTimeout(fn, 0)等价于setTimeout(fn, 1)),那么setTimeout的回调会首先执行。如果没有到一毫秒,那么在timers阶段的时候,下限时间没到,setTimeout回调不执行,事件循环来到了poll阶段,这个时候队列为空,此时有代码被setImmediate(),于是事件循环先进入check阶段先执行了setImmediate()的回调函数,之后在下一个事件循环再执行setTimemout的回调函数。而我们在执行代码的时候,进入timers的时间延迟其实是随机的,并不是确定的,所以会出现两个函数执行顺序随机的情况。
事件循环check阶段执行回调函数输出setImmediate,之后输出nextTick。嵌套的setImmediate在下一个事件循环的check阶段执行回调输出嵌套的setImmediate。

for循环中i只等于最后一个值

var i只声明了一个空间,在每次循环后都会替代那个值,所以直接console。log会是数组最后一个值,

循环中使用axios请求每次请求的某基本类字段值都是循环最后一个值

循环中axios并没有立即对传入对象进行序列化,导致对象的基本类的字段值一直在循环中被更新,所以会导致循环中该字段永远都是最后一个值。

node事件循环与浏览器事件循环的差异

浏览器环境下,microtask的任务队列是每个macrotask执行完成之后执行。
而在node.js中,microtask会在事件循环的各个阶段之间执行,也就是说,每一个阶段执行完毕,就会去执行microtask队列中的任务。

js是解释性语言还是编译性语言

js是解释性语言,但它有自己的编译器JIT,这个编译器是js开发人员为了提高js的运行效率而加入的,对于执行可能超过一次的代码,就会对其进行标记为warm、hot、hotter等更高的等级,这样这些代码就会被放入JIT进行及时编译,下次代码再运行的时候就会运行编译后的结果。

隐式类型转换

算法流程

点击(此处)折叠或打开

  1. 1.如果x非正常值(比如x本身会抛出错误),则中断执行

  2. 2.如果y非正常值(同上),则中断执行

  3. 3.如果x的数据类型和y的数据类型相同,则返回以严格运算符执行判断的结果,即x===y的结果

  4. 4.如果x是null,y是undefined,返回true

  5. 5.如果x是undefined,y是null,返回true

  6. 6.如果x的数据类型是Number,y的数据类型是String,则将y转成Number,然后返回x==toNumber(y)的结果

  7. 7.如果x的数据类型是String,y的数据类型是Number,则将x转成Number,然后返回toNumber(x)==y的结果

  8. 8.如果x的数据类型是Boolean,则将x转成Number,然后返回toNumber(x)==y的结果

  9. 9.如果y的数据类型是Boolean,则将y转成Number,然后返回x==toNumber(y)的结果

  10. 10.如果x的数据类型是String、Number或者Symbol,y的数据类型是Object,则将y转成原始类型,然后返回x==toPrimitive(y)的结果

  11. 11.如果x的数据类型是Object,y的数据类型是String、Number或者Symbol,则将x转成原始类型,然后返回toPrimitive(x)==y的结果

  12. 12.返回false

toPrimitive

JS引擎内部转换为原始值ToPrimitive(obj,preferredType)函数接受两个参数,第一个obj为被转换的对象,第二个preferredType为希望转换成的类型(默认为空,接受的值为Number或String)

在执行ToPrimitive(obj,preferredType)时如果第二个参数为空并且obj为Date的实例时,此时preferredType会被设置为String,其他情况下preferredType都会被设置为Number

如果preferredType为Number,ToPrimitive执行过程如下:

如果obj为原始值,直接返回;
否则调用 obj.valueOf(),如果执行结果是原始值,返回之;
否则调用obj.toString(),如果执行结果是原始值,返回之;
否则抛异常。
如果preferredType为String,将上面的第2步和第3步调换,即:

如果obj为原始值,直接返回;
否则调用obj.toString(),如果执行结果是原始值,返回之;
否则调用 obj.valueOf(),如果执行结果是原始值,返回之;
否则抛异常。

toNumber

完成标志( 例如return、break、throw等) 如果参数是一个异常中断,就返回这个参数,否则就返回该参数转换成Number之后的数值
Undefined 返回Nan
Null 返回+0
Boolean 如果参数是true,返回1;如果参数是false,返回+0
Number
返回参数(不做转换)

String 看下面
Symbol 抛出一个TypeError异常
Object
采用下述的步骤:

1.利用ToPrimitive(argument,hint Number)的方式转成原始类型

2.将上述步骤的原始类型转成数值,即ToNumber(primValue),并返回该数值

string转number

点击(此处)折叠或打开

  1. 1.如果字符串中只包含数字(包括前面带加号或负号的情况),则将其转换为十进制数值,即"1"会变成1,"123"会变成123,而"011"会变成11(注意:前导的零被忽略了);

  2. 2.如果字符串中包含有效的浮点格式,如"1.1",则将其转换为对应的浮点数值(同样,也会忽略前导零);

  3. 3.如果字符串中包含有效的十六进制格式,例如"0xf",则将其转换为相同大小的十进制整数值;

  4. 4.如果字符串是空的(不包含任何字符),则将其转换为0;

  5. 5.如果字符串中包含除上述格式之外的字符,则将其转换为NaN。

mobx, redux各自的特点和区别

mobx采用引用赋值的方式,redux采用的是返回一个immutable对象的方式,因此redux支持数据回溯,通过Provider和connect来比对前后差别控制更新粒度。

发布订阅模式和观察者模式区别

观察者模式中观察者和目标直接进行交互,而发布订阅模式中统一由调度中心进行处理,订阅者和发布者互不干扰

SSR服务端渲染的优缺点

ssr有利于页面较快展示,不用等待js文件下载,编译执行,也就是首屏渲染;更利于爬虫爬取
增加了服务器端的压力;不利于SEO(搜索引擎优化);在服务端渲染中,只会执行到componentDidMount之前的生命周期钩子

webpack

webpack是什么

webpack是模块化打包工具,gulp/grunt是前端流程化开发工具。简单理解就是webpack给你提供了打包方案,gulp让你按照你的项目的流程,由你定义你要把哪些文件压缩打包在一起。

webpack的优点

专注模块化项目
可以通过plugin扩展
社区庞大,不限于web开发

webpack缺点

只能用于模块化开发的项目

webpack的热更新

配置方式:
1、配置 devServer.hot 属性为 true
2、还需要调用 module.hot.accept 接口,声明如何将模块安全地替换为最新代码
操作原理:
1、使用 webpack-dev-server (后面简称 WDS)托管静态资源,同时以 Runtime 方式注入 HMR 客户端代码
2、浏览器加载页面后,与 WDS 建立 WebSocket 连接
3、Webpack 监听到文件变化后,增量构建发生变更的模块,并通过 WebSocket 发送 hash 事件
4、浏览器接收到 hash 事件后,请求 manifest 资源文件,确认增量变更范围
5、浏览器加载发生变更的增量模块
6、Webpack 运行时触发变更模块的 module.hot.accept 回调,执行代码变更逻辑
7、done

浏览器资源的优先级

1、浏览器的一般行为:
浏览器加载的时候是自上而下的,加载和渲染为同步进行
加载不会阻塞下载,解析会阻塞下载
js解析的时候会阻塞其他的加载
一般浏览器会后面解析js文件
2、浏览器加载的一般顺序
资源分类:主资源、图片资源、css、js、font、ajax、svg
安全策略检查:同源策略、网页安全策略
资源优先级计算:xhr请求优先级最高
图片(首屏可视):优先级为高,其它优先级为低
脚本资源:添加async的脚本设置为low。脚本在第一张图片之前为high,之后为medium。

3、浏览器的提速方法
链接预取: 或

预取能够预取不同宿主的文档,只有带有关系类型为 next 或 prefetch的标签会被预拉取,a标签不会被预取。
资源预加载 通过 link的rel属性设置preload来更早的加载目标资源

分别介绍bundle,chunk,module是什么

bundle:是由webpack打包出来的文件,
chunk:代码块,一个chunk由多个模块组合而成,用于代码的合并和分割。
module:是开发中的单个模块

什么是loader,什么是plugin

loader:模块转换器,用于将模块的原内容按照需要转成你想要的内容
plugin:在webpack构建流程中的特定时机注入扩展逻辑,来改变构建结果,是用来自定义webpack打包过程的方式

loader用于对模块源码的转换,loader描述了webpack如何处理非javascript模块,并且在build中引入这些依赖。
plugin目的在于解决loader无法实现的其他事,从打包优化和压缩,到重新定义环境变量,功能强大到可以用来处理各种各样的任务。是一个webpack的扩展库。

什么是模块热更新?

模块热更新是webpack的一个功能,他可以使得代码修改过后不用刷新浏览器就可以更新,是高级版的自动刷新浏览器。

什么是Tree-shaking

Tree-shaking可以用来剔除javascript中不用的死代码,它依赖静态的es6模块化语法
commonjs和es6的module的区别
  1. 导入语法不同,es是export导出,import导入;commonjs是module.exports、exports导出,require导入。

  2. commonjs运行时加载模块,es6编译时确定模块依赖

  3. es6提升import置顶,commonjs不提升require

  4. commonjs导出是值导出,es6是引用地址导出

通过webpack处理长缓存

浏览器在用户访问页面的时候,为了加快加载速度,会对用户访问的静态资源进行存储,但是每一次代码升级或是更新,都需要浏览器去下载新的代码,最方便和简单的更新方式就是引入新的文件名称。在webpack中可以在output中输出的文件指定chunkhash,并且分离经常更新的代码和框架代码

如何提高webpack的构建速度

resolve参数合理配置,通过externals配置来提取常用库,extensions(当你引入文件不想写后缀时) 配置了很多值时,mainFiles(当你引入文件不想写文件名时,只引入一个文件夹的时候),alias(配置别名的时候)例如@为根目录时,这些配置很多值时都会影响性能。
在尽可能少的模块上应用Loader,Plugin尽可能少并确保可靠
利用DllPlugin和DllReferencePlugin预编译资源模块(就是第三方库模块)
使用Happypack 实现多线程加速编译(它对file-loader和url-loader支持不好)
控制包文件大小(通过配置tree-shaking将文件的内容中没有用到的模块但引入了的给剔除出去)
tree-shaking配置

optimization: { // 在开发环境里需要使用此配置
usedExports: true // 哪些导出的模块被使用了,再做打包
}
生产环境不用做这些配置,就默认是模块未使用不参与打包。
如果某些文件,不想让它做tree-shaking, 那么就可以通过数组的形式配置在sideEffects里边就可以了。若没有要配置的东西,此值就设置为false.

vue中的registerServiceWorker

首先说明,registerServiceWorker可以运用于主流框架,它只是为了简化缓存机制产生的js包。
它是用来做离线缓存等任务的,实际上就是为Vue项目注册了一个service worker。这样的话,如果在线上,只要访问过一次该网站,以后即使没有网络也可以访问(此时使用的是之前缓存的资源)。

vue的属性穿透

子组件绑定v-bind:attrs,attrs的功能就是将父组件的属性除了prop、class和style全部传递到子组件上。

vue的use方法

vue中use加载组件,需要组件实现了install方法。

高阶组件vue中的实现

通过使用template组件通过插槽的命名来进行实现。

vue的v-for的实现原理

DOM的diff算法,如果节点类型不同,直接干掉前面的节点,再创建并插入新的节点,不会再比较这个节点以后的子节点了。如果节点类型相同,则会重新设置该节点的属性,从而实现节点的更新。
如果有key的存在计算机会判断当前存在的情况,比如发现不同的时候会继续向后遍历如果发现相同,则进行下次遍历,经过比较计算机会知道去掉了哪些增加了哪些内容,然后再重新渲染。所以key不能是变量,一定要是每个生成器中每项的唯一的值,起到标识作用。

vue的v-if的实现原理

v-if的选择方法是实现就是控制dom元素的显示和移除和控制display的效果相同。
v-show的方法是通过transition或display:none来控制页面上dom节点的展示,优先transition。

vue中的监听对象中的属性

在data里面进行一个申明直接就是obj.a = 2
proxy里面进行代理对于对象进行拦截并处理
用watch对象里面写数据加上deep进行深度遍历
vue的set方法

set的原理

对于数组通过splice变更
对于对象

defineReactive(ob.value, key, val)
defineReactive使用object.definedProperty的第三个参数的get和set方法进行属性赋值和获取值的代理。
ob.dep.notify()

vue的双向数据流和单向数据流

v-model是双向数据流,其它是单向数据流,
单向数据流是指从view到actions到mutations到state,这样的好处是便于操作,可以追根溯源,简单容易理解。
双向数据流是可以进行双向绑定,vue中的v-model就可以进行双向绑定,可以有助于实现数据的交互,减少了不必要的操作,代码更加简洁,但是更容易发生暗箱操作。

vue中的v-for的key的实现方式

key是给每一个vnode的唯一id,也是diff的一种优化策略,可以根据key,更准确, 更快的找到对应的vnode节点
场景背后的逻辑
当我们在使用v-for时,需要给单元加上key

如果不用key,Vue会采用就地复地原则:最小化element的移动,并且会尝试尽最大程度在同适当的地方对相同类型的element,做patch或者reuse。

如果使用了key,Vue会根据keys的顺序记录element,曾经拥有了key的element如果不再出现的话,会被直接remove或者destoryed

image加载错误时触发的事件

onerror

vuex中为什么要把异步放在action中,mutation中存放同步

在 mutation 中混合异步调用会导致你的程序很难调试。例如,当你能调用了两个包含异步回调的 mutation 来改变状态,你怎么知道什么时候回调和哪个先回调呢。
actions 和 mutations 并不是为了解决竞态问题,而是为了能用 devtools 追踪状态变化。事实上在 vuex 里面 actions 只是一个架构性的概念,并不是必须的,说到底只是一个函数

vue的keep-alive

包裹动态组件时,会缓存不活动的组件实例,而不是销毁它们。和 相似, 是一个抽象组件:它自身不会渲染一个 DOM 元素,也不会出现在父组件链中。当组件在 内被切换,它的 activated 和 deactivated 这两个生命周期钩子函数将会被对应执行,会将页面缓存在内存中,当需要时可以再取出来。在activitied和deactivated生命周期中keep-alive会被激活。

vue的动态组件

动态组件是vue内部提供了一个叫做component的组件,这个组件身上可以通过绑定is属性来进行组件的切换。 keep-alive组件可以进行组件的内容缓存,将组件的内容存入浏览器缓存中,这样可以大大的节省全部切换的事件,动态组件中的is属性可以传入组件名字也可以直接传入组件的模板template,都可以在页面展示。

vue的transition组件

元素作为单个元素/组件的过渡效果。 只会把过渡效果应用到其包裹的内容上,而不会额外渲染 DOM 元素,也不会出现在检测过的组件层级中,
name - string,用于自动生成 CSS 过渡类名。例如:name: ‘fade’ 将自动拓展为.fade-enter,.fade-enter-active等。默认类名为 “v”
有四个状态,fade-enter:进入过渡的开始状态,元素被插入时生效,只应用一帧后立即删除;fade-enter-active:进入过渡的结束状态,元素被插入时就生效,在过渡过程完成之后移除;fade-leave:离开过渡的开始状态,元素被删除时触发,只应用一帧后立即删除;fade-leave-active:离开过渡的结束状态,元素被删除时生效,离开过渡完成之后被删除;

vue一些常用组件

vue-route,路由组件,用于前端路由跳转
vuex组件,状态管理器
http组件和axios组件进行网络请求
vue-echarts组件,进行图标绘制
vue-video-player组件,vue-video-player是一个视频播放解决方案
vue-photo-preview组件,就是把小图放大或者多个图片方大后轮播的情况
vue-lazyload插件,当你滚动条还没有滚动到这一片区域的时候,就不加载一片区域中的图片,会用loading中的gif图去代替
lrz插件,在图片上传的时候,我们肯定不能直接把一张大图给传上去,这里我们需要限制一下大小。然后,可能更好的方法就是,进行压缩

js实现多继承

function Parent1(name,age){
this.name = name;
this.age = age;
this.height=180;
}
Parent1.prototype.say = function(){
alert(‘hi…’);
}
function Parent2(name,age,weight){
this.name = name;
this.age = age;
this.weight = weight;
this.height = 170;
this.skin=‘yellow’;
}
Parent2.prototype.walk = function(){
alert(‘walk…’);
}

function Child(name,age,weight){
Parent1.call(this,name,age);
Parent2.call(this,name,age,weight);
}

for(var i in Parent1.prototype){Child.prototype[i] = Parent1.prototype[i]}
for(var i in Parent2.prototype){Child.prototype[i] = Parent2.prototype[i]}

可以看到子类Child的实例c1打出的结果继承了父类的所有属性和方法。当然这里存在一个问题,如果父类Parent1和Parent2都有name这个属性的时候,会以后继承的为主。可以理解javascript的多继承其实就是前面介绍的js循环拷贝继承的多次使用,,使用空函数的方式不行。

Promise和generator

generator

Generator(生成器)是一类特殊的函数,跟普通函数声明时的区别是加了一个*号,进行实例化之后,main()里的代码不会主动执行。第一个next()永远是用于启动生成器,生成器启动后要想运行到最后,其内部的每个yield都会对应一个next(),所以说next()永远都会比yield多一个了。
第一个next()仅仅是用于启动生成器用的,并不会传入任何东西,如果传入了参数也会被自动忽略掉
yield是用于在异步流程中暂停阻塞代码,当然,它阻塞的只有生成器里面的代码,生成器外部的丝毫不受影响。let settingInfo = yield getCallSettings();中,通过yield把异步的流程完全抽离出去,实现了看似顺序同步的代码

Promise

Promise使用then方法每次返回的都是一个Promise对象,并且是一个全新的Promise, 我们可以通过then去一层层的链式调用。
无法取消Promise,错误需要回调函数去捕获
all方法和race方法都会执行,一个是等所有都执行完了,如果都是resolve就走then方法否则都走catch,race是竞速的意思,就是所有的promise对象中有一个完成了返回的状态决定了race返回的promise对象的状态,如果是resolve就走then,否则走catch,总结不管是all还是race方法都会全部执行完,因为异步请求都加入了消息队列,主线程肯定会在空闲时间去执行的。

async 就是 Geneartor加上Promise的语法糖

async 的用法

async 关键字的用法是用于返回一个promise对象。用在函数前,await进行一个等待类似于generator里面的等待作用,等到运行完成返回一个promise对象再继续向下执行。所以在async函数里面进行await的等待之后,在最后返回的也是一个promise对象。

generator的用法

generator其实是声明一个生成器对象,yield可以用来阻塞函数的运行。当通过generator对象调用next方法之后可以执行yield后面的内容直到下一个yield或者return结束。

点击(此处)折叠或打开

  1. function ss (val){

  2. setTimeout((val) => {

  3. console.log(43)

  4. val.next()

  5. }, 1000,val);

  6. }

  7. function *b(){

  8. yield ss(bs);

  9. yield console.log(554)

  10. }

  11. var bs = b()

  12. bs.next()

ajax与fetch的区别

Ajax的本质是使用XMLHttpRequest对象来请求数据
fetch 是全局量 window 的一个方法,它的主要特点有:第一个参数是URL;第二个是可选参数,可以控制不同配置的 init 对象;使用了 JavaScript Promises 来处理结果/回调。
从 fetch()返回的 Promise 将不会拒绝HTTP错误状态, 即使响应是一个 HTTP 404 或 500。相反,它会正常解决 (其中ok状态设置为false), 并且仅在网络故障时或任何阻止请求完成时,它才会拒绝

slice和split和splice

slice 对数组进行部分截取,并且返回一个新的数组,不改变原来的数组
splice,会改变原数组,对原数组进行增删改查,修改原数组,返回数组里面是删除的元素。
split根据特定的字符串切分成数组
concat()方法不会改变现有的数组,而仅仅会返回被连接数组的一个副本

双击事件中不触发单击事件

启动一个定时器来进行解决。在双击开始的时候清除定时器,在单击开始的时候删除定时器,然后设置定时器。

vue中的prop的验证

type定义类型

type可以对类型进行定义,bug是如果传进来是null或者undefined与原类型不一致也均能通过。

validate进行强校验

如果出现validate不满足情况的时候直接通过返回true来完成校验,如果返回falsy则失败,falsy是null,undefined,0,’’,

vue中处理边界情况

需要对vue的规则做出小调整的情况。

访问根实例

通过$root访问根实例,拿到根组件的所有的data的值

访问父组件实例

通过$parent访问父实例,拿到父实例的data的值

依赖注入provide和inject

provide提供父组件中的值给所有后代使用,后代通过inject进行注入值来使用。

给组件附加ref属性

this.$refs,ref不是响应式的,ref是在组件渲染后产生的。可以拿到该组件的实例化

程序化事件监听器$on , $off, $once

通过进行事件的监听,对于第三方的库的引用的时候需要实例化并且要进行事件绑定的时候,可以用这个进行事件的监听。可以将需要进行在俩个生命周期内执行的方法,直接在一个生命周期中进行监听执行。

模板定义的替代品

内联模板,inline-template可以通过组件的该属性实现在父组件的子组件内写一些子组件的部分,这一部分用子组件的数据,不是以slot形式插入。

控制强制更新

this.$forceUpdate来进行强制更新。当数据驱动写的时候出现问题,找不出错误的时候就可以通过这个进行控制强制更新。

vue的插槽

分为默认插槽,具名插槽和作用域插槽

slot就是默认插槽,当slot太多时可以通过name来进行区分,这个就是具名插槽,父组件template中有个属性slot-cope就是作用域,它可以拿到使用插槽slot组件中的所有的属性名和属性值,以键值对形式存储,可以直接调用。

vue组件间通信

props/$emit

父组件A通过props的方式向子组件B传递,B to A 通过在 B 组件中 $emit, A 组件中 v-on 的方式实现

通过事件传值

子组件通过events给父组件发送消息,实际上就是子组件把自己的数据发送到父组件

emit/on

通过一个空的Vue实例作为中央事件总线(事件中心),用它来触发事件和监听事件,巧妙而轻量地实现了任何组件间的通信,包括父子、兄弟、跨级
通过写一个bus.js,或者用vue的$bus来实现总线进行事件监听和发布
这个总线机制的原理就是通过new一个新的vue实例来将所有的自定义事件全部绑定在这个上面,每次页面卸载时通过$off来进行事件的卸载操作。

vuex和localstorage

vuex不会存储数据,而是写到内存中,一刷新就重置了。

attrs/listeners

attrs与listeners 是两个对象,attrs 里存放的是父组件中绑定的非 Props 属性,listeners里存放的是父组件中绑定的非原生事件,仅仅传数据不用做中间处理时可以用。

provide/inject

通过provide/inject进行父子组件间值的传递。provide 和 inject 绑定并不是可响应的。这是刻意为之的。然而,如果你传入了一个可监听的对象,那么其对象的属性还是可响应的。所以,上面 A.vue 的 name 如果改变了,B.vue 的 this.name 是不会改变的

vue的混入

vue采用mixins进行混入,如果和组件数据冲突,则组件内数据优先。先调用混入的钩子函数,再调用自身的钩子函数。组件内的方法函数会替代钩子内冲突的函数。Vue.mixin({}),进行全局混入,!慎用。

$options拿到的是组件中存在的属性。

vue的过滤器

数据值不会改变,但是页面的展示会不一样。通过管道,过滤器默认参数是需要拿到的被过滤的值

vue的优先级

template的优先级是高于render的

vue中对于render中的内容进行渲染,采用的是JSX语法进行编写渲染。react中全部采用JSX语言进行编写。

render中的参数是一个createElement方法,看到是个h函数,函数的三个参数是html标签,要传入的标签属性,要传入的标签内容。

vue中的默认插槽和具名插槽在jsx中实现是this.slots之中,作用域插槽在this.scopedSlots之中。

vue的函数式组件

函数组件中存在的content的默认参数。这里含有对象组件的this中的大部分内容。其中attr的属性存放的是不在prop属性中的值。children里所有匿名的默认的插槽。slots有一个声明的default插槽时,只包含这一个插槽,没有声明时,包含所有匿名插槽。

vue的beforedestroy中能做什么

可以进行timer定时器的清除。
可以进行dom中自定义事件的卸载。(
俩个方法,vm.off删除事件监听器,由vm.on绑定的监听器,没有办法,解除dom上直接绑定的事件;
(官网)把当前的DOM干掉,那么你所绑定在这个DOM上的所以事件都会被删掉从而达到了移除点击事件的功能

vue中自定义指令

vue-directive(‘pin’ ,{ inserted: ()=>{ } } )
其中提供的钩子函数:

bind:只调用一次,指令第一次绑定到元素时调用。在这里可以进行一次性的初始化设置。
inserted:被绑定元素插入父节点时调用 (仅保证父节点存在,但不一定已被插入文档中)。
update:所在组件的 VNode 更新时调用,但是可能发生在其子 VNode 更新之前。指令的值可能发生了改变,也可能没有。但是你可以通过比较更新前后的值来忽略不必要的模板更新 (详细的钩子函数参数见下)。
componentUpdated:指令所在组件的 VNode 及其子 VNode 全部更新后调用。
unbind:只调用一次,指令与元素解绑时调用。

钩子函数的参数:

el:指令所绑定的元素,可以用来直接操作 DOM。
binding:一个对象,包含以下 property:
name:指令名,不包括 v- 前缀。
value:指令的绑定值,例如:v-my-directive=“1 + 1” 中,绑定值为 2。
oldValue:指令绑定的前一个值,仅在 update 和 componentUpdated 钩子中可用。无论值是否改变都可用。expression:字符串形式的指令表达式。例如 v-my-directive=“1 + 1” 中,表达式为 “1 + 1”。arg:传给指令的参数,可选。例如 v-my-directive:foo 中,参数为 “foo”。
modifiers:一个包含修饰符的对象。例如:v-my-directive.foo.bar 中,修饰符对象为 { foo: true, bar: true }。vnode:Vue 编译生成的虚拟节点。
oldVnode:上一个虚拟节点,仅在 update 和 componentUpdated 钩子中可用。

可以进行动态指令时name和value对应 , v-pin:[direction]=“200”,name对应里面的direction,value对应里面的200。

vue的修饰符

.stop 阻止事件继续传播
.prevent 阻止标签默认行为
.capture 使用事件捕获模式,即元素自身触发的事件先在此处处理,然后才交由内部元素进行处理
.self 只当在 event.target 是当前元素自身时触发处理函数
.once 事件将只会触发一次
.passive 告诉浏览器你不想阻止事件的默认行为

使用修饰符时,顺序很重要;相应的代码会以同样的顺序产生。因此,用v-on:click.prevent.self会阻止所有的点击,而 v-on:click.self.prevent 只会阻止对元素自身的点击。

vue的mixins混入

mixins一般用的比较多的就是定义一个混入对象,然后在组件里或者新建的vue对象里使用mixins
全局的mixins会影响到所有的子组件
就是把当前Vue实例的options和传入的mixin合并,再返回。真正的实现是靠mergeOptions函数实现的。
mergeOptions函数则实现了递归遍历this.options,然后执行mergeField,返回最终合并的this.options。
一般我们执行mergeField 里的key基本上就是上面strats的属性了,用的最多的可能就是data、methods、props了。所以如果我们在mixins中用到了data,其本质上就是合并当前vue实例对象里的data和我们传进去的mixin里的data,其他属性也是一样的
当组件和混入对象含有同名选项时,这些选项将以恰当的方式混合。比如,数据对象在内部会进行递归合并,在和组件的数据发生冲突时以组件数据优先。
值为对象的选项,例如 methods, components 和 directives,将被混合为同一个对象。两个对象键名冲突时,取组件对象的键值对。
局部注册组件,本质上其实是Vue.extends().而extend里面最终也会执行mergeOptions()函数

vue的异步组件

Vue.component(‘async-webpack-example’, function (resolve) {

// 这个特殊的 require 语法将会告诉 webpack
// 自动将你的构建代码切割成多个包,这些包
// 会通过 Ajax 请求加载
require([’./my-async-component’], resolve)
})

缓存中拿取,编译出来是一个包,link标签的属性rel= prefetch。
app.js和window.js是打包后出现的俩个文件,app.js中存放了自己的内容,window.js中存放的是第三方库的内容,这俩个的加载方式是link标签的属性rel= preload。

Vue的父子组件的生命周期函数顺序

created与beforeMount之间,主要做了两步工作:1、判断实例在dom中有没有挂载的元素(el:‘#app’),只有挂载了才能够继续。挂载好后,实例即与挂载dom元素进行了绑定(占坑),实例中也可以进行引用;2、渲染dom模板。渲染dom模板只是在内存中,并非是在HTML中的DOM结构中渲染,所以前台在这个阶段时,组件对应的元素是没有显示的。(在调用 this.$el.outerHTML 后,控制台输出
)—— 可以看到fathe组件的beforeMount时,child子组件的vue创建生命周期已经完成到mounted阶段。说明father在执行dom模板渲染的时候,会监测模板中是否有自定义的vue子组件。如果有,就进入子组件的生命周期的创建阶段,等到所有子组件的完成创建并挂载(mounted)到父组件的模板当中后。才能表明父组件在内存中的模板渲染完成。—— 子组件的mounted阶段虽然完成,但父组件仍在beforeMounte阶段时。前台也看不见子组件渲染的效果,子组件只是完成了挂载到父组件的模板中了(控制台可以看到dom树中的元素并未变化)。因此此刻在子组件的mounted阶段直接调用一些方法(dom操作方法)可能会造成异常错误。为保险起见可在子组件中通过 $nextTick() 回调,等下一次DOM更新后再进行dom的操作。

执行顺序为:
父beforeCreate -> 父created -> 父beforeMount -> 子beforeCreate -> 子created -> 子beforeMount -> 子mounted -> 父mounted

更新阶段
执行顺序为:
父beforeUpdate -> 子beforeUpdate -> 子updated -> 父updated

销毁阶段
执行顺序为:
父beforeDestroy -> 子beforeDestroy -> 子destroyed -> 父destroyed

vue的data属性

data属性使用return返回

data中如果不使用return返回,会造成全局污染,
我们希望组件中的data只在该组件中生效,当一个组件被定义,data必须为其声明返回一个初始数据对象的函数,因为组件可能被用来创建多个实例,这样每次声明这个组件都可以初始化一个数据对象

computed和watch的区别

computed
  1. 支持缓存,只有依赖数据发生改变,才会重新进行计算

  2. 不支持异步,当computed内有异步操作时无效,无法监听数据的变化
    3.computed 属性值会默认走缓存,计算属性是基于它们的响应式依赖进行缓存的,也就是基于data中声明过或者父组件传递的props中的数据通过计算得到的值

  3. 如果一个属性是由其他属性计算而来的,这个属性依赖其他属性,是一个多对一或者一对一,一般用computed
    5.如果computed属性属性值是函数,那么默认会走get方法;函数的返回值就是属性的属性值;在computed中的,属性都有一个get和一个set方法,当数据变化时,调用set方法。

watch
  1. 不支持缓存,数据变,直接会触发相应的操作;
    2.watch支持异步;
    3.监听的函数接收两个参数,第一个参数是最新的值;第二个参数是输入之前的值;

  2. 当一个属性发生变化时,需要执行对应的操作;一对多;

  3. 监听数据必须是data中声明过或者父组件传递过来的props中的数据,当数据变化时,触发其他操作,函数有两个参数,
      immediate:组件加载立即触发回调函数执行,
      deep: 深度监听,为了发现对象内部值的变化,复杂类型的数据时使用,例如数组中的对象内容的改变,注意监听数组的变动不需要这么做。注意:deep无法监听到数组的变动和对象的新增,参考vue数组变异,只有以响应式的方式触发才会被监听到。
    无法监听时例如:利用索引直接设置一个数组项时,例如:arr[indexOfItem] = newValue;
    修改数组的长度时,例如:arr.length = newLength;

Object.definedproperty()的缺点

经过Object.defineProperty方法建立的响应式对象来说,只能追踪对象已有数据是否被修改,无法追踪新增属性和删除属性,这就需要另外处理
不能监听数组的变化

解决方法

var proxy = new Proxy(obj, handler)
Proxy的代理针对的是整个对象,而不是像Object.defineProperty针对某个属性。只需做一层代理就可以监听同级结构下的所有属性变化,包括新增属性和删除属性
Proxy也可以监听数组的变化

vue的生命周期函数和hook函数

beforecreate : 举个栗子:可以在这加个loading事件
created :在这结束loading,还做一些初始化,实现函数自执行,在此之前dom元素都没有被加载。
beforeMount: 挂载之前,在此及之后dom才被加载出来。
mounted : 在这发起后端请求,拿回数据,配合路由钩子做一些事情,这里才可以进行ajax请求。
beforeUpdate: 更新前状态
updated:更新后完成状态
beforeDestory: 你确认删除XX吗?
destoryed :当前组件已被删除,清空相关内容
vue通过computed属性将vuex里面的状态渲染到组件页面中去。组件页面的状态通过Object.defineproperty()来将数据渲染到页面中去。
get方式和post方式的幂等性,幂等性是数学上的概念,只的是我输入相同的值,输出一定是什么,get也是幂等的,post是非幂等的,但是这里不是说输入什么输出一定相等,如果用get方式去请求时间,输入为空,每次返回也都不一样。这里的幂等性主要是说明用get方式进行传值,前后俩次的请求互不相关,举个例子,post请求进行身份注册的时候,第一次post请求成功了,第二次再注册就会失败,但是用get方式进行请求的第一次和第二次是独立的是不想关的。如果出现相关性,这在进行设计的时候就是不合理的。
get和post方式的区别,对于 GET 和 POST 的区别,总结来说就是:它们的本质都是 TCP 链接,并无区别。但是由于 HTTP 的规定以及浏览器/服务器的限制,导致它们在应用过程中可能会有所不同。

浏览器缓存

强缓存

expires需要设置一个固定时间
Cache-Control设置的是相对时间,max-age = 30

协商缓存

Last-Modified 表示文件的最后修改日期
ETag类似于文件指纹,if-None-Match会将当前的Etag发送给文件服务器,询问该资源ETag是否有变动,有变动的话就将新的资源返回回来,并且ETag的优先级比Last-Modified高
Last-Modified / If-Modified-Since

If-Modified-Since则是客户端再次发起该请求时,携带上次请求返回的Last-Modified值,通过此字段值告诉服务器该资源上次请求返回的最后被修改时间。服务器收到该请求,发现请求头含有If-Modified-Since字段,则会根据If-Modified-Since的字段值与该资源在服务器的最后被修改时间做对比,若服务器的资源最后被修改时间大于If-Modified-Since的字段值,则重新返回资源,状态码为200;否则则返回304,代表资源无更新,可继续使用缓存文件

Etag / If-None-Match

If-None-Match是客户端再次发起该请求时,携带上次请求返回的唯一标识Etag值,通过此字段值告诉服务器该资源上次请求返回的唯一标识值。服务器收到该请求后,发现该请求头中含有If-None-Match,则会根据If-None-Match的字段值与该资源在服务器的Etag值做对比,一致则返回304,代表资源无更新,继续使用缓存文件;不一致则重新返回资源文件,状态码为200,

缓存的请求头字段

弱缓存
If-Modified-Since

对应 Last-modified。服务器返回资源时,会携带 Last-modified,表示该资源最后修改的时间。客户端如果缓存数据,就需要把这个时间保存起来,在下一次请求的时候用 If-Modified-Since 带上,让服务器判断资源的最后修改时间是否一致。如果一致,就返回304,让客户端直接使用本地缓存。否则说明资源被修改,返回新资源和新的 Last-modified。

If-Modified-Since:告诉服务器如果时间一致,返回状态码304
If-Unmodified-Since:告诉服务器如果时间不一致,返回状态码412

Last-modified

资源最后修改时间,配合 If-Modified-Since 使用。属于响应头字段

ETag

资源的特定版本标识符,可以类比软件的版本号,需要配合 If-None-Match 使用。另外,这个 E 是 Entity(实体)的意思。

ETag 的比较只对同一个 URL 有意义 —— 不同 URL 上资源的 ETag 值可能相同也可能不同。

ETag生成

没有明确指定生成 ETag 值的方法。通常是使用内容的散列、最后修改时间戳的哈希值或简单地使用版本号

If-None-Match

If-None-Match效果类似 If-Modified-Since,客户端第一次请求资源时会拿到响应头里的 ETag 字段,将其保存起来。下次请求时,就作为 If-None-Match 头字段的值进行请求。ETag 通常使用的是弱比较算法,即如果两个资源语义一致,可看作为匹配成功。如果匹配成功,返回 304,否则返回新资源和新的 ETag。另外,ETag 通常会用 W/ 开头,表示使用了弱匹配算法。ETag 可以用于跟踪用户,某种程度上,可以作为 cookie 的替代品。该头字段的优先度比 If-Modified-Since 高。

If-None-Match:告诉服务器如果一致,返回状态码304,不一致则返回资源
If-Match:告诉服务器如果不一致,返回状态码412


强缓存
Cache-Control

通用消息头字段,在请求头和响应头中有不同的语义,用于实现缓存控制。

请求头中
  1. no-cache 告知代理服务器不直接使用缓存,向原服务器发起请求

  2. no-store 所有内容不会被缓存或internet临时文件中

  3. max-age 告知代理服务器客户端希望接收一个存在时间不大于多少秒的资源

  4. max-stale 告知代理服务器客户端愿意接收一个超过(多少秒的资源/没有则不限时间)缓存时间的资源

  5. min-fresh 告知代理服务器客户端希望接收一个在小于多少秒内被更新过的资源

  6. no-transform 告知代理服务器客户端希望获得实体数据没有被转换过的资源

  7. only-if-cached 告知代理服务器客户端希望获取缓存的内容而不用向原服务发送请求

  8. cache-extension 自定义扩展值,若服务器不识别,该值将被忽略掉

响应头中
  1. public 表明任何情况下都要缓存该资源,即使是需要http认证的

  2. private [=“field-name”] 表明返回报文中全部或部分(有field-name则为field-name的字段数据)仅开放给某些用户(服务器指定的share-user),其它用户不能缓存这些数据。

  3. no-cache 不直接使用缓存,要求向服务器发起新请求

  4. no-store 所有内容都不会被保存到缓存或internet临时文件中

  5. no-transform 告知客户端缓存文件时不得对实体数据进行任何处理

  6. only-if-cached 告知代理服务器客户端希望获取获取缓存的内容

  7. must-revalidate 当前资源一定是向原服务器发起请求的,如果请求失败会返回504,不会使用代理服务器缓存。

  8. proxy-revalidate 与must-revalidate类似,但仅能应用于共享缓存(例如代理)

  9. max-age 告知客户端该资源在多少秒内是新的,无需向服务器发起请求

  10. s-maxage 同max-age,但是仅应用于共享缓存(如代理)

  11. cache-extension 自定义扩展值,若服务器不识别该值将会被忽略掉

http1.0的缓存标识

在 http1.0 时代,给客户端设定缓存方式可通过两个字段——Pragma和Expires来规范。虽然这两个字段早可抛弃,但为了做http协议的向下兼容

pragma: no-cache 时禁用缓存
Pragma的优先级是高于Cache-Control

Expire

资源无效的时间节点。优先级比 Cache-Control 低。格式例子: Expires: Thu, 05 Dec 2019 16:27:43 GMT

Expires是HTTP/1.0的字段,但是现在浏览器的默认使用的是HTTP/1.1,那么在HTTP/1.1中网页缓存还是否由Expires控制?

到了HTTP/1.1,Expires已经被Cache-Control替代,原因在于Expires控制缓存的原理是使用客户端的时间与服务端返回的时间做对比,如果客户端与服务端的时间由于某些原因(时区不同;客户端和服务端有一方的时间不准确)发生误差,那么强制缓存直接失效,那么强制缓存存在的意义就毫无意义。

Via

通用字段,记录经过的代理节点的信息。常用于代理服务器和源服务器上,假设客户端发送的请求依次经过了代理服务器A和代理服务器B,最终达到源服务器,则源服务器收到的字段为 Via: A, B。该头字段的作用是追踪消息的转发情况,防止代理服务器之间形成环导致消息死循环。

X-Forwarded-For

Via 没有记录真正的发送者客户端的 IP,所以出现了这个没有纳入 HTTP 标准但已经是事实上的标准。该字段记录的是当前节点的请求方的 IP 地址,即记录的内容最终为 X-Forwarded-For: client, A, B。另外,为了解决代理转发时,为了添加信息必须解析 HTTP 报文修改数据造成的成本,出现了 代理协议(The PROXY protocol),原理是将一些信息放到了HTTP报文的前面。

X-Real-IP

只记录客户端的 IP 地址。

不同缓存位置

内存缓存(from memory cache):内存缓存具有两个特点,分别是快速读取和时效性:

1、快速读取:内存缓存会将编译解析后的文件,直接存入该进程的内存中,占据该进程一定的内存资源,以方便下次运行使用时的快速读取。

2、时效性:一旦该进程关闭,则该进程的内存则会清空。

(2)硬盘缓存(from disk cache):硬盘缓存则是直接将缓存写入硬盘文件中,读取缓存需要对该缓存存放的硬盘文件进行I/O操作,然后重新解析该缓存内容,读取复杂,速度比内存缓存慢。

在浏览器中,浏览器会在js和图片等文件解析执行后直接存入内存缓存中,那么当刷新页面时只需直接从内存缓存中读取(from memory cache);而css文件则会存入硬盘文件中,所以每次渲染页面都需要从硬盘读取缓存(from disk cache)。

什么时候走强缓存走协商缓存

强缓存就是给资源设置个过期时间,客户端每次请求资源时都会看是否过期;只有在过期才会去询问服务器。所以,强缓存就是为了给客户端自给自足用的。而当某天,客户端请求该资源时发现其过期了,这是就会去请求服务器了,而这时候去请求服务器的这过程就可以设置协商缓存。这时候,协商缓存就是需要客户端和服务器两端进行交互的。
强缓存是浏览器自己检查有没有超时可不可用,协商缓存是服务器查有没有超时,没有超时返回304,浏览器自己用缓存的数据。

BOM浏览器原理

BOM组成

主要组件包括:

  1. 用户界面 - 包括地址栏、后退/前进按钮、书签目录等,也就是所看到的除了用来显示所请求页面的主窗口之外的其他部分

  2. 浏览器引擎 - 用来查询及操作渲染引擎的接口

  3. 渲染引擎 - 用来显示请求的内容,例如,如果请求内容为html,它负责解析html及css,并将解析后的结果显示出来。  4. 网络 - 用来完成网络调用,例如http请求,它具有平台无关的接口,可以在不同平台上工作。

  4. UI后端 - 用来绘制类似组合选择框及对话框等基本组件,具有不特定于某个平台的通用接口,底层使用操作系统的用户接口。

  5. JS解释器 - 用来解释执行JS代码。

  6. 数据存储 - 属于持久层,浏览器需要在硬盘中保存类似cookie的各种数据,HTML5定义了web database技术,这是一种轻量级完整的客户端存储技术

浏览器内核分成两部分:渲染引擎和js引擎

【渲染引擎】  
opera:blink
IE :trident
google:webkit
firefox:gecko
safari:webkit

【js引擎】
IE9之后使用Chakra引擎,edge浏览器仍然使用Chakra引擎  
firefox使用monkey系列引擎  
safari使用的SquirrelFish系列引擎  
Opera使用Carakan引擎  
chrome使用V8引擎。
nodeJs其实就是封装了V8引擎

渲染过程是原始输入为URL地址,最终输出为页面Bitmap,中间依次经过了Loader、Parser、Layout和Paint模块

Loader模块负责处理所有的HTTP请求以及网络资源的缓存,负责各种外链资源的下载
Parser模块主要负责解析HTML页面,完成从HTML文本到HTML语法树再到文档对象树;css解析,页面中所有的CSS由样式表CSSStyleSheet集合构成,而CSSStyleSheet是一系列CSSRule的集合,每一条CSSRule则由选择器CSSStyleSelector部分和声明CSSStyleDeclaration部分构成,而CSSStyleDeclaration是CSS属性和值的Key-Value集合。总结:内核把页面文本转换成了一棵节点带CSS Style、会响应自定义事件的Styled DOM树。
Layout模块并非所有DOM节点都可视,也就是并非所有DOM树节点都会对应生成一个Render树节点,DOM树可视节点的CSS Style就是其对应Render树节点的Style。总结:经过了Layout阶段的处理,把带Style的DOM树变换成包含布局信息和绘制信息的Render树,接下来的显示工作就交由Paint模块进行操作了
Paint模块负责将Render树映射成可视的图形,它会遍历Render树调用每个Render节点的绘制方法将其内容显示在一块画布或者位图上,并最终呈现在浏览器应用窗口中成为用户看到的实际页面。

dom树的生成

1、浏览器获取到HTML文件,对文件进行解析,形成DOM树
2、通过<!DOCTYPE>告知浏览器当前的html是个什么版本的页面,
3、在头部标签中的标签会有一个编码格式,和视口大小,是否能够缩放,
4、向下继续解析就会有一个标签,line一般引入的是css样式,然后进入这个链接进行一个解析,生成styleRules,在解析过程中,并不影响DOM的渲染
5、DOM继续向下进行渲染,当遇到

css阻塞js运行

css加载不会阻止dom解析,但是css的加载会阻止后面的dom的渲染。
js的操作会影响dom树和css树,css加载会阻塞后面的js运行
js的解析影响dom的渲染
css的解析和dom解析是两条并行的线,所以这两个互不影响

onLoad和DOMContentLoaded

当 onload 事件触发时,页面上所有的DOM,样式表,脚本,图片,flash都已经加载完成了。

当 DOMContentLoaded 事件触发时,仅当DOM加载完成,不包括样式表,图片,flash。

区别:
1、onload事件是DOM事件,onDOMContentLoaded是HTML5事件。
2、onload事件会被样式表、图像和子框架阻塞,而onDOMContentLoaded不会。
3、当加载的脚本内容并不包含立即执行DOM操作时,使用onDOMContentLoaded事件是个更好的选择,会比onload事件执行时间更早。

浏览器缓存

Page Cache:是将浏览的页面状态临时保存在缓存中,以加速页面返回等操作
Memory Cache:浏览器内部的缓存机制,对于相同url的资源直接从缓存中获取,不需重新下载
Disk Cache:资源加载缓存和服务器进行交互,服务器端可以通过HTTP头信息设置网页要不要缓存。

Memory Cache,顾名思义内存缓存,其主要作用为缓存页面使用各种派生资源
而Page Cache只会在用户点击前进或后退按钮时才会被查询,如果页面符合缓存条件并被缓存了,则直接从Page Cache中加载。即使某个需要被加载的页面在Page Cache中有备份,但若触发加载的原因是用户在地址栏输入url或点击链接,则页面仍然是通过网络加载。也就是说Page Cache并不是主资源的通用缓存
磁盘缓存即我们常说的Web缓存,分为强缓存和协商缓存,它们的区别在于强缓存不发请求到服务器,协商缓存会发请求到服务器。
对于大文件来说,大概率是不存储在内存中的,反之优先
当前系统内存使用率高的话,文件优先存储进硬盘

less和sass的区别

Less和Sass的主要不同就是他们的实现方式。 Less是基于JavaScript,是在客户端处理的。 Sass是基于Ruby的,是在服务器端处理的。 关于变量在Less和Sass中的唯一区别就是Less用@,Sass用$。
LESS

①//less中的注释,但这种不会被编译
②/* * 这也是less中的注释,但是会被编译 */

      • / 可带、可不带单位

@变量名:变量值; 使用变量: @变量名

SASS

使用 变量名:变量值,声明变量;如果变量需要在字符串中嵌套,则需使用#加大括号包裹;border-#{left}:10px solid blue;
sass中的运算会将单位也进行运算,使用时需注意最终单位例:10px_10px=100px_px

选择器嵌套,属性嵌套,伪类嵌套
选择器嵌套:ul{ li{} } 后代 ul{ >li{} } 子代&:表示上一层 div{ ul{ li{ &==“div ul li” } } }
属性嵌套:属性名与大括号之间必须有: 例如:border:{color:red;}
伪类嵌套:ul{li{ &:hover{ “ul li:hover” } } }

①混合宏:声明:@mixin name($param:value){}调用:@include name(value);

声明时,可以有参,可以无参;可带默认值,也可不带;但是,调用时,必须符合声明规范。同less

优点:可以传参,不会生成同名class;
缺点:会将混合宏中的代码,copy到对应的选择器中,产生冗余代码!
②继承:声明:.class{}调用:@extend .class;

优点:继承的相同代码,会提取到并集选择器中,减少冗余代码
缺点:无法进行传参,会在css中,生成一个同名class
③占位符:声明:&class{}调用:@extend %class;

优点:继承相同代码,会提前到并集选择器;不会生成同名的class选择器
缺点:无法进行传参综上所述:当需要传递参数时,用混合宏;当有现成的class时用继承;当不需要参数,也不需要class时,用占位符

css

1px像素

.area{

position:relative;
}
.area:after{
content:"";
width:200%;
height:200%;
border-radius:4px;
border:1px solid #000;
transform:scale( 0.5, 0.5);
transform-origin: 0 0 ;
position:absolute;
left:0;
right:0;
}

重排与重绘

display:none
会触发重排,不会被子元素继承,无法触发绑定事件,translation无效。
页面上不展示并消失但是dom树中存在节点

visibility:hidden
会触发重绘,会被继承,可以设置子元素visibility使其显示,无法触发绑定事件,translate无效
页面看不到,但是会占位dom树存在节点

opacity:0
会触发重绘,会被子元素继承,不能设置子元素重新展示。可以触发绑定事件,translate有效。
页面看不到,但是会占位dom树存在节点

magin和translate在动画效果上的性能损耗

用magin时,父元素往往是被子元素撑起来的,那你设置父元素的margin时,往往会感染到子元素,做轮播图的时候会出现大面积留白。margin会导致重排,translate会导致重绘。
css选择器优先级

important声明 1,0,0,0
ID选择器 0,1,0,0
类选择器 0,0,1,0
伪类选择器 0,0,1,0
属性选择器 0,0,1,0
标签选择器 0,0,0,1
伪元素选择器 0,0,0,1
通配符选择器 0,0,0,0

实现栅格布局

一、使用float:
左一个使用float:left
右一个使用float:right
中间的不设置width和height这样中间的宽度就随内容的撑开而扩大。
二、 使用display:flex
设置父元素为flex-wrap:wrap;justify-content: center;

order是有序,unorder是无序,所以有序列表是ol,ul是无序列表。

块级作用域

用let命令新增了块级作用域,外层作用域无法获取到内层作用域,非常安全明了。即使外层和内层都使用相同变量名,也都互不干扰。

var溢出

for循环是同步的,var只有函数作用域,所以当for循环内部为异步事件时,for本身执行是同步的,var全局公用一个内存空间,所以var声明的变量最后一直是循环的最大值。

块级上下文(BFC)

inline-block元素可以将对象呈递为内联对象,但是对象的内容作为块对象呈递。
inline-block元素,如果内部没有inline内联元素,或者overflow不是visible,则该元素的基线就是它margin的底边缘,否则就是元素内部最后一行内联元素的基线。
生成BFC

根元素
float的值不为none
overflow的值不为visible
display的值为inline-block、table-cell、table-caption
position的值为absolute或fixed

BFC的约束规则

内部的Box会在垂直方向上一个接一个的放置垂直方向上的距离由margin决定。(完整的说法是:属于同一个BFC的两个相邻Box的margin会发生重叠,与方向无关。)
每个元素的左外边距与包含块的左边界相接触(从左向右),即使浮动元素也是如此。(这说明BFC中子元素不会超出他的包含块,而position为absolute的元素可以超出他的包含块边界)
BFC的区域不会与float的元素区域重叠,计算BFC的高度时,浮动子元素也参与计算,BFC就是页面上的一个隔离的独立容器,容器里面的子元素不会影响到外面元素

BFC在布局中的应用

同一个BFC中的两个相邻Box才会发生重叠与方向无关(外边距合并)

margin塌陷

问题

父子嵌套元素在垂直方向的margin,父子元素是结合在一起的,他们两个的margin会取其中最大的值

解决

给父级设置边框或内边距(不建议使用)
触发bfc(块级格式上下文),改变父级的渲染规则

深克隆实现的几种方法

递归的方法
json.parse(json.stringify())
jquery.extend( true, {} , obj)

前端性能优化

减少请求资源大小或者次数

尽量和并和压缩css和js文件
尽量所使用的字体图标或者SVG图标
能用css做的效果,不要用js做
使用雪碧图或者是说图片精灵
减少对cookie的使用
前端与后端协商,合理使用keep-alive
避免使用iframe

代码优化相关

在js中尽量减少闭包的使用
减少对DOM操作,主要是减少DOM的重绘与重排
把css放在body上,把js放在body下面
在js中避免嵌套循环和"死循环"
css选择器解析规则所示从右往左解析的。减少元素标签作为对后一个选择对象
尽量将一个动画元素单独设置为一个图层
css导入的时候尽量减少@import导入式,因为@import是同步操作,只有把对应的样式导入后,才会继续向下加兹安,而link是异步的操作
使用window.requestAnimationFrame(js的帧动画)代替传统的定时器动画

存储

结合后端,利用浏览器的缓存技术,做一些缓存
利用h5的新特性(localStorage、sessionStorage)做一些简单数据的存储,

localStorage和SessionStorage和cookie的区别

cookie的内容主要包括:名字、值、过期时间、路径和域。路径与域一起构成cookie的作用范围。若不设置时间,则表示这个cookie的生命期为浏览器会话期间,关闭浏览器窗口,cookie就会消失。这种生命期为浏览器会话期的cookie被称为会话cookie。
会话cookie一般不存储在硬盘而是保存在内存里,当然这个行为并不是规范规定的。若设置了过期时间,浏览器就会把cookie保存到硬盘上,关闭后再打开浏览器这些cookie仍然有效直到超过设定的过期时间。对于保存在内存里的cookie,不同的浏览器有不同的处理方式session机制。
Web Storage的概念和cookie相似,区别是它是为了更大容量存储设计的,cookie的大小是受限的,并且每次请求一个新的页面的时候cookie都会被发送过去,这样无形中浪费了带宽,另外cookie还需要指定作用域,不可跨域调用。

存储大小限制也不同,cookie数据不能超过4K,同时因为每次http请求都会携带cookie、所以cookie只适合保存很小的数据,如会话标识。sessionStorage和localStorage虽然也有存储大小的限制,但比cookie大得多,可以达到5M或更大

数据有效期不同,sessionStorage:仅在当前浏览器窗口关闭之前有效;localStorage:始终有效,窗口或浏览器关闭也一直保存,因此用作持久数据;cookie:只在设置的cookie过期时间之前有效,即使窗口关闭或浏览器关闭
作用域不同,sessionStorage不在不同的浏览器窗口中共享,即使是同一个页面;localstorage在所有同源窗口中都是共享的;cookie也是在所有同源窗口中都是共享的
web Storage支持事件通知机制,可以将数据更新的通知发送给监听者
页面中一般的js对象的生存期仅在当前页面有效,因此刷新页面或转到另一页面这样的重新加载页面的情况,数据就不存在了 而sessionStorage只要同源的同窗口中,刷新页面或进入同源的不同页面,数据始终存在,也就是说只要浏览器不关闭,数据仍然存在

其它优化

页面中出现音视频标签,我们不让页面加载的时候去加载这些资源
尽量将一个动画元素单独设置为一个图层,可以避免重绘和重排
减少作用域链查找

为什么构造函数的原型是Object对象

因为构造函数本质上也是对象,所以他的原型是Object.prototype。

new的作用

创建一个空对象,作为将要返回的对象实例,this指向空对象,将传递进来的参数和属性绑定this,将this对象返回出去。
将这个空对象的原型指向构造函数的prototype属性

new 构造函数之后的proto指向哪个?

构造函数的proto指向构造函数的prototype。

Object.assign()

这个是浅拷贝,只拷贝其中一层。

Object.create()

Object.create()方法创建一个新对象,使用现有的对象来提供新创建的对象的__proto__。

const person = {
isHuman: false,
printIntroduction: function() {
console.log(My name is ${this.name}. Am I human? ${this.isHuman});
}
};

const me = Object.create(person);

me.name = ‘Matthew’; // “name” is a property set on “me”, but not on "person"
me.isHuman = true; // inherited properties can be overwritten

me.printIntroduction();
// expected output: “My name is Matthew. Am I human? true”

浏览器点击屏幕事件触发顺序

mousedown
mouseup
click
mousedown
mouseup
click
dblclick

button的属性值

DOM的button属性有3个值:0表示鼠标左键,1表示鼠标滚轮按钮,2表示鼠标右键

不同绑定事件的方式的触发顺序

下面只有handler3会被添加执行,所以我们使用另外一种方法添加事件
element.onclick=handler1;
element.onclick=handler2;
element.onclick=handler3;

下面三个方法执行顺序:3-2-1;
elment.attachEvent(“onclick”,handler1);
elment.attachEvent(“onclick”,handler2);
elment.attachEvent(“onclick”,handler3);

下面三个方法执行顺序:1-2-3;
elment.addEvenListener(“click”,handler1,false);
elment.addEvenListener(“click”,handler2,false);
elment.addEvenListener(“click”,handler3,false);

异步编程有哪几种方法

1.回调函数
2.事件监听
3.Promises对象
4.发布/订阅(观察者模式)

闭包

闭包就是可以创建一个独立的环境,每个闭包里面的环境都是独立的,互不干扰。闭包会发生内存泄漏,每次外部函数执行的时候,外部函数的引用地址不同,都会重新创建一个新的地址。但凡是当前活动对象中有被内部子集引用的数据,那么这个时候,这个数据不删除,保留一根指针给内部活动对象。闭包内存泄漏为: key = value,key 被删除了 value 常驻内存中。存在内存泄漏,避免使用全局变量,外部函数使用内部变量的桥梁。变量常驻内存。
闭包产生条件,函数内返回另一个函数,而且还要对上级作用域的变量有所依赖。

线程和进程的区别

1、 进程是资源分配的最小单位,线程是程序执行的最小单位,cpu最小调度单位是线程,操作系统最小调度单位是进程。
2、进程有自己的独立地址空间,(每启动一个进程,系统就会为它分配地址空间),3、建立数据表来维护代码段、堆栈段和数据段,这种操作非常昂贵。4、而线程是共享进程中的数据的,使用相同的地址空间(因此CPU切换一个线程的花费远比进程要小很多,同时创建一个线程的开销也比进程要小很多)
5、线程之间的通信更方便,同一进程下的线程共享全局变量、静态变量等数据,而进程之间的通信需要以通信的方式(IPC)进行。(不过如何处理好同步与互斥是编写多线程程序的难点)
6、但是多进程程序更健壮,多线程程序只要有一个线程死掉,整个进程也死掉了,而一个进程死掉并不会对另外一个进程造成影响,因为进程有自己独立的地址空间。
7、当一个线程死了(非正常退出、死循环等)就会导致线程该占有的资源永远无法释放,从而影响其他线程的正常工作

单页面的优缺点

优点:用户体验好,快,内容的改变不需要重新加载整个页面,基于这一点spa对服务器压力较小。前后端分离。页面效果会比较炫酷(比如切换页面内容时的专场动画)。
缺点:不利于seo。导航不可用,如果一定要导航需要自行实现前进、后退。(由于是单页面不能用浏览器的前进后退功能,所以需要自己建立堆栈管理)。初次加载时耗时多。页面复杂度提高很多。
SEO是搜索引擎优化,不利于seo是因为单页面的情况下的页面中的很多内容都是根据匹配到的路由动态生成并展示出来的,而且很多页面内容是通过ajax异步获取的,网络抓取工具并不会等待异步请求完成后再行抓取页面内容,对于网络抓取工来说去准确模拟相关的行为获取复合数据是很困难的,它们更擅长对静态资源的抓取和分析.解决策略是目前市面上用的比较多的就是以下两种策略: 预渲染和SSR(服务端渲染).

预渲染

预渲染指的是打包生成一些主要路由对应的静态html文件,这样有了更多的静态资源,网络爬虫可以抓取到更多的网站信息,提升网站的搜索排名.

SSR渲染

服务端渲染就是先向后端服务器请求数据,然后生成完整首屏html返回给浏览器, 服务端渲染返回给客户端的是已经获取了异步数据并执行JavaScript脚本的最终HTML,网络爬虫可以抓取到完整的页面信息,SSR另一个很大的作用是加速首屏渲染,因为无需等待所有的JavaScript都完成下载并执行,才显示服务端渲染的标记,所以用户会更快地看到完整渲染的页面.

服务端渲染和客户端渲染的区别

本质上两种渲染都是一样的,都是进行的字符串拼接生成html,两者的差别最终体现在时间消耗以及性能消耗上。客户端在不同网络环境下进行数据请求,客户端需要经历从js加载完成到数据请求再到页面渲染这个时间段。导致了大量时间的消耗以及浏览器性能的消耗。而服务端在内网请求,数据响应快,不需要等待js代码加载,可以先请求数据再渲染可视部分然后返回给客户端,客户端再做二次渲染,这样大部分消耗的是服务端的性能。客户端页面响应时间也更快。

进程间的通信

进程间通信(IPC,InterProcess Communication)是指在不同进程之间传播或交换信息。
IPC的方式通常有管道(包括无名管道和命名管道)、消息队列、信号量、共享存储、Socket、Streams等。其中 Socket和Streams支持不同主机上的两个进程IPC。

网络通信,url传输到拿到值得过程。

客户端获取URL - > DNS解析(客户端拿到IP和端口) - > TCP连接 - >发送HTTP请求 - >服务器处理请求 -> 找到对应的软件 -> 软件找到对应的相对路径的文件 - >返回报文 - >浏览器解析渲染页面 - > TCP断开连接
客户端:(应用层开始)获取URL,通过负责域名解析的DNS服务获取网址的IP地址,根据HTT协议生成HTTP请求报文(应用层结束)(传输层开始)根据TCP协议连接从客户端到服务端(通过三次握手)客户端给服务端发送一个带SYN(同步)标志的数据包给客户端,然后客户端接收到信息再给客户端回传一个带有SYN / ACK(确认)标志的数据包以示传达确认信息,客户求最后端的再传送一个带ACK标志的数据包,代表“握手”结束,连接成功.TCP协议在把请求报文按序号分割成多个报文段(传输层结束)(网络层开始)根据IP协议(传输数据),ARP协议(获取MAC地址),OSPF协议(选择最优路径),搜索服务器地址,一边中转一边传输数据(网络层结束)(数据链路层开始)到达后通过数据链路层,物理层负责0,1比特流与物理设备电压高低,光的闪灭之间的互换。数据链路层负责将0,1序列划分为数据帧从一个节点传输到临近的另一个节点,这些节点是通过MAC来唯一标识的(MAC,物理地址,一个中主机会有一个MAC地址)。 (数据链路层结束)
客户端通过数据链路层 - >网络层 - >传输层(根据TCP协议接收响应报文并重组) - >应用层(HTTP协议对响应进行处理) - >浏览器渲染页面 - >断开连接协议四次挥手)
渲染页面的过程解析

1、获取html文档,解析成dom树
2、处理css标记,构成层叠样式模型(css树)
3、dom和css合并形成渲染树
4、计算渲染树的每个元素的内容
5、绘制渲染树的每个节点到屏幕

一、构建dom

  1. DOM 树的构建可能会被 CSS 和 JS 的加载而阻塞;

  2. display: none 的元素也会在 DOM 树中;

  3. 注释也会在 DOM 树中;

  4. script 标签也会在 DOM 树中;

二、解析css

  1. CSS 的解析可以与 DOM 的解析同步进行

  2. CSS 的解析与 script 的执行互斥

  3. 在 Webkit 内核中进行了 script 执行优化,只有在 JS 访问 CSS 时才会发生互斥

三、构建渲染树

  1. 渲染树和 DOM 树不完全对对应;

  2. display: none 的元素不在渲染树中;

  3. visiblity: none 的元素在渲染树中;

四、布局

  1. float 元素、absolute 元素、fixed 元素会发生位置偏移;

  2. 脱离标准文档流就是脱离渲染树(render tree);

五、绘制

绘制阶段,浏览器遍历所有的渲染树节点,调用 paint() 方法,将其渲染在屏幕上。渲染树的绘制工作是由浏览器的UI后端组件完成的。

  1. CSS 被默认为阻塞渲染的资源,所以在 CSSOM 构建完成之前不会处理任何已处理的内容;

  2. JavaScript 可以读取和修改 DOM 属性,还可读取和修改 CSSOM 属性,所以 CSS 解析和 script 的执行互斥

服务端通过数据链路层 - >通过网络层 - >再通过传输层(根据TCP协议接收请求报文并重组报文段) - >再通过应用层(通过HTTP协议对请求的内容进行处理) - >再通过应用层 - >传输层 - >网络层 - >数据链路层 - >到达客户端

web安全

xss攻击,通过在input等表单中输入script标签,在内部进行自定义操作,从而实现从页面获取cookie,storage等操作。解决方法通过将标签转化成中文字符来避免攻击
csrf攻击,CSRF全称为跨站请求伪造(Cross-site request forgery),是一种网络攻击方式。通过cookie进行攻击。解决方法是通过验证 HTTP 头中有一个字段叫 Referer,它记录了该 HTTP 请求的来源地址,用json的api来传输避免跨域。可以在 HTTP 请求中以参数的形式加入一个随机产生的 token
sql注入,通过在url后面拼接sql语句达到欺骗服务器去操作sql数据库的作用。解决方法是对进入数据库的特殊字符,使用了Java JDBC中的PreparedStatement预编译预防SQL注入
DDOS:分布式拒绝服务攻击(Distributed Denial of Service),简单说就是发送大量请求是使服务器瘫痪。解决方法是DDoS 防御产品的核心是检测技术和清洗技术。检测技术就是检测网站是否正在遭受 DDoS 攻击,而清洗技术就是清洗掉异常流量

c#写的net程序采用的服务器是什么

IIS(Internet Information Services)是微软公司的产品,它借助Windows NT/2000/2003/Vista Server操作系统,在PC界处于绝对优势,也是当今使用最广泛的Web服务器之一。由于它具有与操作系统的亲和性,并继承了微软产品一贯的用户界面,使其成为功能强大、使用方便、广泛使用的Web服务器之一

垃圾回收机制

V8 将内存分为两类:新生代内存空间和老生代内存空间,新生代内存空间主要用来存放存活时间较短的对象,老生代内存空间主要用来存放存活时间较长的对象。
新生代内存中的垃圾回收主要通过 Scavenge 算法进行,具体实现时主要采用了 Cheney 算法。Cheney 将内存空间一分为二,每部分都叫做一个 Semispace,这两个 Semispace 一个处于使用,一个处于闲置。处于使用中的 Semispace 也叫作 From,处于闲置中的 Semispace 也叫作 To。在垃圾回收运行时时,会检查 From 中的对象,当某个对象需要被回收时,将其留在 From 空间,剩下的对象移动到 To 空间,然后进行反转,将 From 空间和 To 空间互换。进行垃圾回收时,会将 To 空间的内存进行释放
简而言之,就是 From 空间中存放不需要被回收的对象,To 空间中存放需要被回收的对象,当垃圾回收运行时,将 To 空间中的对象全部进行回收。
在垃圾回收的过程中,如果发现某个对象之前被清理过,那么会将其晋升到老生代内存空间中在 From 空间和 To 空间进行反转的过程中,如果 To 空间中的使用量已经超过了 25%,那么就将 From 中的对象直接晋升到老生代内存空间中
老生代内存空间中的垃圾回收有标记清除(Mark Sweep)和标记合并(Mark Compact)两种方式。
使用 Mark Sweep 进行垃圾回收会产生一个问题,就是垃圾回收后内存会出现不连续的情况,为了解决这个问题,出现了 Mark Compact 方案
Mark Sweep 是将需要被回收的对象进行标记,在垃圾回收运行时直接释放相应的地址空间,如下图所示(红色的内存区域表示需要被回收的区域)
新生代包括一个New Space,老生代包括: Old Space, Code Space和Map Space,Large Object Space。

v8分代内存

64位环境下的V8引擎的新生代内存大小32MB、老生代内存大小为1400MB,而32位则减半,分别为16MB和700MB。
对于新生代的对象,采用空间换取时间的Scavenge算法, 尽可能快的回收内存。如果对象经历了2次GC还依然坚挺,就会在第二次回收时晋升为老生代(准确的说是保存在Old Space中)。
而老生代的GC采取Mark-Sweep的算法,并使用Mark-Sweep解决内存碎片的问题。

V8的内存限制

V8限制了所能使用的内存极限(64位系统下约为1.4GB,32位系统下约为0.7GB)

V8内存使用策略

可以使用process.memoryUsage()方法查看
heapTotal:V8已申请到的堆内存
heapUsed:当前内存使用量
rss:官网解释:驻留集大小, 是给这个进程分配了多少物理内存(占总分配内存的一部分) 这些物理内存中包含堆,栈,和代码段。简单说:进程的常驻内存(node所占的内存)
V8并不是一开始就申请到其内存上限的大小的,而是在当前堆内存使用已满时再申请更多的堆内存,直至V8的堆内存使用上限,当达到上限之后内存溢出。
扩大内存上限的选项

–max-old-space-size=2048 // 设置老生代内存最大限制,单位:MB
–max-new-space-size=1024 // 设置新生代内存最大上限,单位:MB

判断回收内容

如何确定哪些内存需要回收,哪些内存不需要回收,这是垃圾回收期需要解决的最基本问题。我们可以这样假定,一个对象为活对象当且仅当它被一个根对象或另一个活对象指向。根对象永远是活对象,它是被浏览器或V8所引用的对象。被局部变量所指向的对象也属于根对象,因为它们所在的作用域对象被视为根对象。全局对象(Node中为global,浏览器中为window)自然是根对象。浏览器中的DOM元素也属于根对象

如何识别指针和数据

垃圾回收器需要面临一个问题,它需要判断哪些是数据,哪些是指针。由于很多垃圾回收算法会将对象在内存中移动(紧凑,减少内存碎片),所以经常需要进行指针的改写目前主要有三种方法来识别指针:
  1. 编译器提示法:如果是静态语言,编译器能够告诉我们每个类当中指针的具体位置,而一旦我们知道对象时哪个类实例化得到的,就能知道对象中所有指针。这是JVM实现垃圾回收的方式,但这种方式并不适合JS这样的动态语言

  2. 标记指针法:这种方法需要在每个字末位预留一位来标记这个字段是指针还是数据。这种方法需要编译器支持,但实现简单,而且性能不错。V8采用的是这种方式。V8将所有数据以32bit字宽来存储,其中最低一位保持为0,而指针的最低两位为01

v8垃圾回收机制

  1. 新生代内存区:存储存活时间较短的对象,这个区域很小但是垃圾回收特别频繁

  2. 老生代指针区:属于老生代,这里包含了大多数可能存在指向其他对象的指针的对象,大多数从新生代晋升的对象会被移动到这里

  3. 老生代数据区:属于老生代,这里只保存原始数据对象,这些对象没有指向其他对象的指针

  4. 大对象区:这里存放体积超越其他区大小的对象,每个对象有自己的内存,垃圾回收其不会移动大对象

  5. 代码区:代码对象,也就是包含JIT之后指令的对象,会被分配在这里。唯一拥有执行权限的内存区

  6. Cell区、属性Cell区、Map区:存放Cell、属性Cell和Map,每个区域都是存放相同大小的元素,结构简单

垃圾回收器只会针对新生代内存区、老生代指针区以及老生代数据区进行垃圾回收。

新生代的特点

大多数的对象被分配在这里,这个区域很小但是垃圾回收特别频繁。在新生代分配内存非常容易,我们只需要保存一个指向内存区的指针,不断根据新对象的大小进行递增即可。当该指针到达了新生代内存区的末尾,就会有一次清理(仅仅是清理新生代)

新生代的垃圾回收算法Scavenge算法

新生代使用Scavenge算法进行回收。在Scavenge算法的实现中,主要采用了Cheney算法。Cheney算法算法是一种采用复制的方式实现的垃圾回收算法。它将内存一分为二,每一部分空间称为semispace。在这两个semispace中,一个处于使用状态,另一个处于闲置状态。处于使用状态的semispace空间称为From空间,处于闲置状态的空间称为To空间,当我们分配对象时,先是在From空间中进行分配。当开始进行垃圾回收算法时,会检查From空间中的存活对象,这些存活对象将会被复制到To空间中(复制完成后会进行紧缩),而非活跃对象占用的空间将会被释放。完成复制后,From空间和To空间的角色发生对换。也就是说,在垃圾回收的过程中,就是通过将存活对象在两个semispace之间进行复制。可以很容易看出来,使用Cheney算法时,总有一半的内存是空的。但是由于新生代很小,所以浪费的内存空间并不大。而且由于新生代中的对象绝大部分都是非活跃对象,需要复制的活跃对象比例很小,所以其时间效率十分理想。复制的过程采用的是BFS(广度优先遍历)的思想,从根对象出发,广度优先遍历所有能到达的对象

写屏障(用于新生代中from空间里判断对象是否被老生代所指向)

如果新生代中的一个对象只有一个指向它的指针,而这个指针在老生代中,我们如何判断这个新生代的对象是否存活?为了解决这个问题,需要建立一个列表用来记录所有老生代对象指向新生代对象的情况。每当有老生代对象指向新生代对象的时候,我们就记录下来

对象的晋升

当一个对象经过多次新生代的清理依旧幸存,这说明它的生存周期较长,也就会被移动到老生代,这称为对象的晋升。具体移动的标准有两种:
1. 对象从From空间复制到To空间时,会检查它的内存地址来判断这个对象是否已经经历过一个新生代的清理,如果是,则复制到老生代中,否则复制到To空间中
2. 对象从From空间复制到To空间时,如果To空间已经被使用了超过25%,那么这个对象直接被复制到老生代

老生代的特点

老生代所保存的对象大多数是生存周期很长的甚至是常驻内存的对象,而且老生代占用的内存较多

老生代的垃圾回收算法Mark-Sweep和Mark-Compact相结合

Mark-Sweep(标记清除)标记清除分为标记和清除两个阶段。在标记阶段需要遍历堆中的所有对象,并标记那些活着的对象,然后进入清除阶段。在清除阶段中,只清除没有被标记的对象。由于标记清除只清除死亡对象,而死亡对象在老生代中占用的比例很小,所以效率较高标记清除有一个问题就是进行一次标记清楚后,内存空间往往是不连续的,会出现很多的内存碎片。如果后续需要分配一个需要内存空间较多的对象时,如果所有的内存碎片都不够用,将会使得V8无法完成这次分配,提前触发垃圾回收。Mark-Compact(标记整理)标记整理正是为了解决标记清除所带来的内存碎片的问题。标记整理在标记清除的基础上进行修改,将标记清除的清除阶段变为紧缩极端。在整理的过程中,将活着的对象向内存区的一段移动,移动完成后直接清理掉边界外的内存。紧缩过程涉及对象的移动,所以效率并不是太好,但是能保证不会生成内存碎片
标记阶段

在标记阶段,所有堆上的活跃对象都会被标记。每个内存页有一个用来标记对象的位图,位图中的每一位对应内存页中的一个字。这个位图需要占据一定的空间(32位下为3.1%,64位为1.6%)。另外有两位用来标记对象的状态,这个状态一共有三种(所以要两位)——白,灰,黑:

在初始时,位图为空,所有的对象也都是白对象。从根对象到达的对象会背染色为灰色,放入一个单独的双端队列中。标记阶段的每次循环,垃圾回收器都会从双端队列中取出一个对象并将其转变为黑对象,并将其邻接的对象转变为灰,然后把其邻接对象放入双端队列。如果双端队列为空或所有对象都变成黑对象,则结束

清除和紧缩阶段

清除和紧缩阶段都是以内存页为单位回收内存。

清除时垃圾回收器会扫描连续存放的死对象,将其变成空闲空间,并保存到一个空闲空间的链表中。这个链表常被scavenge算法用于分配被晋升对象的内存,但也被紧缩算法用于移动对象

V8的老生代使用标记清除和标记整理结合的方式,主要采用标记清除算法,如果空间不足以分配从新生代晋升过来的对象时,才使用标记整理

V8的优化Incremental Marking(增量标记)

由于全停顿会造成了浏览器一段时间无响应,所以V8使用了一种增量标记的方式,将完整的标记拆分成很多部分,每做完一部分就停下来,让JS的应用逻辑执行一会,这样垃圾回收与应用逻辑交替完成。经过增量标记的改进后,垃圾回收的最大停顿时间可以减少到原来的1/6左右

V8的优化的惰性清理

由于标记完成后,所有的对象都已经被标记,不是死对象就是活对象,堆上多少空间格局已经确定。我们可以不必着急释放那些死对象所占用的空间,而延迟清理过程的执行。垃圾回收器可以根据需要逐一清理死对象所占用的内存页
V8后续还引入了增量式整理(incremental compaction),以及并行标记和并行清理,通过并行利用多核CPU来提升垃圾回收的性能

git的一些指令和面试题

git框架介绍

Workspace:开发者工作区
Index / Stage:暂存区/缓存区
Repository:仓库区(或本地仓库)
Remote:远程仓库

git的执行步骤流程

创建分支: Git branch name 创建名字为name的branch
创建分支的时候:git branch --set-upstream-to=origin/远程分支名称 本地分支名 (先建立远程分支与本地分支的连接,再pull)
拉取合并远程分支的操作:git fetch/git merge或者git pull
新增文件的命令 git add file或者git add
提交文件的命令:git commit –m或者git commit –a
推送文件的操作:Git push origin main_furture_xxx 执行推送的操作,完成本地分支向远程分支的同步

git合并的方式与区别

Git代码合并有两种:Git Merge 和 Git ReBase

Git Merge:这种合并方式是将两个分支的历史合并到一起,现在的分支不会被更改,它会比对双方不同的文件缓存下来,生成一个commit,去push。

Git ReBase:这种合并方法通常被称为“衍合”。他是提交修改历史,比对双方的commit,然后找出不同的去缓存,然后去push,修改commit历史。

git和svn的区别

  1. Git是分布式的,而SVN不是分布式的。
    Git和svn最大的区别就是集中式和分布式,集中式是指只有一个远程版本库,而分布式有本地和远程版本库。

  2. Git把内容按元数据方式存储,而SVN是按文件。

  3. Git没有一个全局版本号,Git 对于每一次提交,通过对文件的内容或目录的结构计算出一个SHA-1 哈希值,得到一个40位的十六进制字符串,Git将此字符串作为版本号。SVN的版本号实际是任何一个相应时间的源代码快照,每一个事物处理(即一次提交)都具有整个版本库全局唯一的版本号。目前为止这是SVN相比Git缺少的最大的一个特征

  4. GIT的内容完整性要优于SVN。GIT的内容存储使用的是哈希算法。这能确保代码内容的完整性,确保在遇到磁盘故障和网络问题时降低对版本库的破坏

通信原理和计算机网络的关系

计算机通信:属于通信领域的理论知识,以及各个层次、TCP/IP协议,等有关计算机辅助通信的东西,计算机通信属于更细一层的划分,但也比较抽象。
知道了这些原理了你才知道通信是怎么回事,通过协议 ,互联网才是全球最大网络,通信必须需要协议

测试模型

单元测试

在单元测试中,一般的API请求用Mock处理,因为关注点只是这个函数能不能得到正确的结果,API返回的数据这种side effect,显然不在函数是不是能正确运行的范畴中,根据控制变量的原则,也是用Mock更好

E2E测试

E2E集成测试(端到端测试(E2E Testing))中,测试的目的是验证业务功能。

TDD和BDD

BDD(Bebavior Driven Developement,行为驱动测试)和TDD(Testing Driven Developement,测试驱动开发)

0人推荐
随时随地看视频
慕课网APP