000 (H5*) 知识点总结
https://note.youdao.com/ynoteshare1/index.html?id=ff02e616917fba868f39241c8383d7c7&type=note
目录
1:闭包
2:游览器渲染原理
3:原型链
4:this
5:数据类型
6:继承
7:数组方法
8:call、apply、bind
9:promise 异步
10:深浅拷贝
11:函数节流
12:事件代理
13:let const var
14:ES6新特性
15:CSS布局
16:rem、px、em单位
17:定位
18:rem适配
19:http协议
20:http缓存
21:从游览器到一个页面展示经历了什么?
22:3次握手、4次挥手
23:Vue组件通信、Vue生命周期
24:Vue双向绑定原理
25:游览器缓存
26:本地存储
27:webpackage
28:冒泡 、快排
29:数组去重
30:跨域3中方式
31:post和get区别
32:同步任务、微任务、宏任务
正文
1:闭包
1:闭包的定义:
函数A中,有一个函数B,函数B中可以访问函数A中定义的变量或者数据,此时就行程了闭包。
<body> <script> /* 闭包 * 闭包的概念:函数A中,有一个函数B,函数B中可以访问函数A中定义的变量或者数据,此时就行程了闭包。 * 闭包的模式:函数闭包和对象闭包 * 闭包的作用:缓存数据,延长作用域链 * 闭包的优点和缺点:缓存数据 * 闭包的应用: * ****/ // 1:函数式闭包 function f4() { var num4 = 40; return function () { console.log(num4); // 40 return num4; }; } var ff1 = f4(); var result = ff1(); console.log(result); // 40 // 2:对象是闭包 function f5() { var num5 = 50; var obj = { age : num5, } return obj; } var resultObj = f5(); var result5 = resultObj.age; console.log(result5); // 50 </script> <script> function fun(n,o) { console.log(o); return { fun:function(m) { return fun(m,n); } }; } console.log("***** 111111 *****") // 经典案例一: undefined 0 0 0 // fun(0,undefined).fun(1);==>>fun(1,0) var a = fun(0); // ---- undefined a.fun(1); // fun(1,0); 对象中的方法 ---- 0 a.fun(2); // fun(2,0); 对象中的方法 ---- 0 a.fun(3); // fun(3,0); 对象中的方法 ---- 0 console.log("***** 222222 *****") // 经典案例二: undefined 0 1 2 var b = fun(0) // fun(0,undefined) ---- undefined .fun(1) // fun(0,undefined).fun(1);==>>fun(1,0) ---- 0 .fun(2) // fun(1,0).fun(2);==>>fun(2,1) ---- 1 .fun(3); // fun(2,1).fun(3);==>>fun(3,2) ---- 2 console.log("***** 3333333 *****") // 经典案例三: undefined 0 1 1 var c = fun(0).fun(1); // fun(0, undefined).f(1)==>>fun(1,0) ----undefined ----0 c.fun(2); // fun(1, 0).f(2)==>>fun(2,1) ----1 c.fun(3); // fun(1, 0).f(3)==>>fun(3,1) ----1 </script> <script> // 经典案例四 console.log("***** 444444 *****") var name = "The Window"; var object = { name : "My Object", getNameFunc : function(){ console.log(this); // {name: "My Object", getNameFunc: ƒ} console.log(this.name); // My Object return function(){ console.log(this); // Window {parent: Window, postMessage: ƒ, blur: ƒ, focus: ƒ, close: ƒ, …} console.log(this.name); // The Window return this.name; }; } }; console.log(object.getNameFunc()());//The Window // 经典案例五 console.log("***** 555555 *****") var name1 = "The Window"; var object = { name1 : "My Object", getNameFunc : function(){ var that = this; console.log(this); // {name: "My Object", getNameFunc: ƒ} console.log(this.name1); // My Object return function(){ console.log(this); // Window {parent: Window, postMessage: ƒ, blur: ƒ, focus: ƒ, close: ƒ, …} console.log(this.name1); // The Window console.log(that); // {name: "My Object", getNameFunc: ƒ} console.log(that.name1); // My Object return that.name1; }; } }; console.log(object.getNameFunc()());//My Object </script> </body>
2:游览器渲染原理
1:渲染引擎
但是常见的浏览器内核可以分这四种:
Trident:(IE游览器) [ˈtraidnt] 三叉戟(旧时武器);
Gecko:(火狐游览器) [ˈɡekoʊ] 壁虎
Blink: (google) [bliŋk] v.眨眼睛; 闪烁;
Webkit:(safari)
2:渲染主流程
渲染引擎首先通过网络获得所请求文档的内容,通常以8K分块的方式完成。下面是渲染引擎在取得内容之后的基本流程:
解析html以构建dom树 -> 构建render树 -> 布局render树 -> 绘制render树
这里先解释一下几个概念,方便大家理解:
1:DOM Tree:浏览器将HTML解析成树形的数据结构。
2:CSS Rule Tree:浏览器将CSS解析成树形的数据结构。
3:Render Tree: DOM和CSSDOM合并后生成Render Tree。
4:layout: 有了Render Tree,浏览器已经能知道网页中有哪些节点、各个节点的CSS定义以及他们的从属关系,从而去计算出每个节点在屏幕中的位置。
5:painting: 按照算出来的规则,通过显卡,把内容画到屏幕上。
reflow(回流):当浏览器发现某个部分发生了点变化影响了布局,需要倒回去重新渲染,内行称这个回退的过程叫 reflow。reflow 会从 <html> 这个 root frame 开始递归往下,依次计算所有的结点几何尺寸和位置。reflow 几乎是无法避免的。现在界面上流行的一些效果,比如树状目录的折叠、展开(实质上是元素的显 示与隐藏)等,都将引起浏览器的 reflow。鼠标滑过、点击……只要这些行为引起了页面上某些元素的占位面积、定位方式、边距等属性的变化,都会引起它内部、周围甚至整个页面的重新渲 染。通常我们都无法预估浏览器到底会 reflow 哪一部分的代码,它们都彼此相互影响着。
repaint(重绘):改变某个元素的背景色、文字颜色、边框颜色等等不影响它周围或内部布局的属性时,屏幕的一部分要重画,但是元素的几何尺寸没有变。
注意:(1)display:none 的节点不会被加入Render Tree,而visibility: hidden 则会,所以,如果某个节点最开始是不显示的,设为display:none是更优的。
(2)display:none 会触发 reflow,而 visibility:hidden 只会触发 repaint,因为没有发现位置变化。
(3)有些情况下,比如修改了元素的样式,浏览器并不会立刻reflow 或 repaint 一次,而是会把这样的操作积攒一批,然后做一次 reflow,这又叫异步 reflow 或增量异步 reflow。但是在有些情况下,比如resize 窗口,改变了页面默认的字体等。对于这些操作,浏览器会马上进行 reflow。
来看看webkit的主要流程:
再来看看Geoko的主要流程:
Gecko 里把格式化好的可视元素称做“帧树”(Frame tree)。每个元素就是一个帧(frame)。 webkit 则使用”渲染树”这个术语,渲染树由”渲染对象”组成。webkit 里使用”layout”表示元素的布局,Gecko则称为”reflow”。Webkit使用”Attachment”来连接DOM节点与可视化信息以构建渲染树。一个非语义上的小差别是Gecko在HTML与DOM树之间有一个附加的层 ,称作”content sink”,是创建DOM对象的工厂。
尽管Webkit与Gecko使用略微不同的术语,这个过程还是基本相同的,如下:
1. 浏览器会将HTML解析成一个DOM树,DOM 树的构建过程是一个深度遍历过程:当前节点的所有子节点都构建好后才会去构建当前节点的下一个兄弟节点。
2. 将CSS解析成 CSS Rule Tree 。
3. 根据DOM树和CSSOM来构造 Rendering Tree。注意:Rendering Tree 渲染树并不等同于 DOM 树,因为一些像Header或display:none的东西就没必要放在渲染树中了。
4. 有了Render Tree,浏览器已经能知道网页中有哪些节点、各个节点的CSS定义以及他们的从属关系。下一步操作称之为layout,顾名思义就是计算出每个节点在屏幕中的位置。
5. 再下一步就是绘制painting,即遍历render树,并使用UI后端层绘制每个节点。
注意:上述这个过程是逐步完成的,为了更好的用户体验,渲染引擎将会尽可能早的将内容呈现到屏幕上,并不会等到所有的html都解析完成之后再去构建和布局render树。它是解析完一部分内容就显示一部分内容,同时,可能还在通过网络下载其余内容。
3: 原型链
在JavaScript中万物都是对象,对象和对象之间也有关系,并不是孤立存在的。
对象之间的继承关系,在JavaScript中是通过构造函数prototype原型对象对象指向父类实例对象,直到指向Object对象为止,这样就形成了一个原型指向的链条
当我们访问对象的一个属性或方法时,它会先在对象自身中寻找,如果有则直接使用,如果没有则会去原型对象中寻找,如果找到则直接使用。如果没有则去原型的原
型中寻找,直到找到Object对象的原型,Object对象的原型没有原型,如果在Object原型中依然没有找到,则返回undefined。
原型链是实例对象之间的关系,实例对象中__proto__之间的关系。
继承是通过修改 构造函数的原型对象指向父类的实例对象。父类实例对象中的__proto__依次往上找、
4:this
1:普通函数中的this是谁?-----window
2:定时器方法中的this是谁?----window
3:对象.方法中的this是谁?----当前的实例对象
4:构造函数中的this是谁?-----实例对象
5:原型对象方法中的this是谁?---实例对象
<script> /* * * 函数中的this的指向 * * * 普通函数中的this是谁?-----window * 对象.方法中的this是谁?----当前的实例对象 * 定时器方法中的this是谁?----window * 构造函数中的this是谁?-----实例对象 * 原型对象方法中的this是谁?---实例对象 * * * */ //严格模式: // "use strict";//严格模式 // function f1() { // console.log(this);//window // } // f1(); //普通函数 // function f1() { // console.log(this); // } // f1(); //定时器中的this // setInterval(function () { // console.log(this); // },1000); //构造函数 // function Person() { // console.log(this); //对象的方法 // this.sayHi=function () { // console.log(this); // }; // } //原型中的方法 // Person.prototype.eat=function () { // console.log(this); // }; // var per=new Person(); // console.log(per); // per.sayHi(); // per.eat(); //BOM:中顶级对象是window,浏览器中所有的东西都是window的 </script>
5:js中的数据类型
1:js中的原始数据类型: number,string,boolean,undefined,null,object null和undefined数据是没有太大意义的,null是很有意义的---对象的位置讲 NaN----不是一个数字,不是一个数字和一个数字计算--->结果就是NaN isNaN()--->判断这个变量或者这个值是不是 不是一个数字---如果不是一个数字结果是true,如果是一个数字结果false 2:基础类型
number数据类型----无论是整数还是小数都是number数据类型的 string数据类型----字符串,获取字符串的长度:变量名.length boolean数据类型---两个值,true,false null----只有一个,null undefined----只有一个,undefined,一个变量声明了,没有赋值 object---对象-----面向对象的时候讲解
3:是使用typeof 获取变量的类型
console.log(typeof nll);//不是null, object
console.log(typeof undef);//undefined
var num = 10; var str = "小白"; var flag = true; var nll = null; var undef; var obj = new Object(); //是使用typeof 获取变量的类型 console.log(typeof num);//number console.log(typeof str);//string console.log(typeof flag);//boolean console.log(String(nll));//是null console.log(typeof nll);//不是null, object
console.log(typeof undef);//undefined console.log(typeof obj);//object console.log(typeof(num)); //
6:继承
1:原型链继承:一是字面量重写原型会中断关系,使用引用类型的原型,并且子类型还无法给超类型传递参数。
2:借用构造函数:无法继承属性
3:组合继承:其背后的思路是 使用原型链实现对原型属性和方法的继承,而通过借用构造函数来实现对实例属性的继承。这样,既通过在原型上定义方法实现了函数复用,又保证每个实例都有它自己的属性。
4:拷贝继承:把一个对象中的属性或者方法直接复制到另一个对象中
7:数组的方法:
<script> /* * * Array.isArray(对象)---->判断这个对象是不是数组 * instanceof关键字 * .concat(数组,数组,数组,...) 组合一个新的数组 * .every(函数)--返回值是布尔类型,函数作为参数使用,函数中有三个参数,第一个参数是元素的值,第二个参数是索引值,第三个参数是原来的数组(没用) * 如果这个数组中的每个元素的值都符合条件,最后才返回的是true * * .filter(函数);返回的是数组中每一个元素都复合条件的元素,组成了一个新的数组 * * .push(值);--->把值追加到数组中,加到最后了---返回值也是追加数据之后的数组长度 * .pop();--->删除数组中最后一个元素,返回值就是删除的这个值 * .shift();--->删除数组中第一个元素,返回值就是删除的这个值 shift: 挪动,转移, * .unshift();--->向数组的第一个元素前面插入一个新的元素,----返回值是插入后的程度 * .forEach(函数)方法---遍历数组用---相当于for循环 * .indexOf(元素值);返回的是索引,没有则是-1 * .join("字符串");----返回的是一个字符串 * .map(函数);--->数组中的每个元素都要执行这个函数,把执行后的结果重新的全部的放在一个新的数组中 * .reverse();----->反转数组 * .sort();---排序的,可能不稳定,如果不稳定,请写MDN中的那个固定的代码 * .arr.slice(开始的索引,结束的索引);把截取的数组的值放在一个新的数组中,但是不包含结束的索引对应的元素值 * .splice(开始的位置,要删除的个数,替换的元素的值);一般是用于删除数组中的元素,或者是替换元素,或者是插入元素 * * * */ //构造函数 // var arr1=new Array(); // //字面量的方式 // var arr2=[]; //对象是不是数组类型:两种 //1 instanceof // var obj=[]; // console.log(obj instanceof Array);//false // // //2 使用数组的 // console.log(Array.isArray(obj));// // var arr=["a","b","c"]; // var newArr=Array.from(arr); // console.log(newArr); // var arr1=[10,20,30]; // var arr2=[40,50,60]; // console.log(arr1.concat(arr2)); // var arr=[1000,2000,3000]; // //a----: 元素的值 // //b----: 索引的值 // //c----:谁调用了这个方法,那么c就是谁---->arr // var flag= arr.every(function (a,b) { // //console.log(a+"==="+b+"===="+c); // return a>2000;//数组中的每个元素的值都要大于2000的情况,最后才返回true // }); // var arr=["小明明lkko","小曹操674","小白白bd","笑眯眯a"]; // var flag=arr.every(function (ele,index) { // //数组中的每个元素的长度是不是大于4 // return ele.length>4; // }); //console.log(flag); // var arr=[10,20,30,40,50,60,70,80]; // var newArr=arr.filter(function (ele) {//ele---每个元素 // return ele>40; // }); // console.log(newArr); // var arr=[10,0,20,0,40,0,60,100]; // var newArr=arr.filter(function (ele) { // return ele!=0; // }); // console.log(newArr); // var arr=[10,20,30,40,50]; // var result=arr.unshift(100); // console.log(result); // console.log(arr); // // var arr = [10, 20, 30, 40]; // arr.forEach(function (ele,index) { // console.log(ele+'======'+index); // }); // var arr=[10,20,30,40]; // var index=arr.indexOf(300); // console.log(index); // var arr=["小白","小黑","小红","小芳","小绿","小苏"]; // var str=arr.join("|"); // console.log(str); // var numbers = [1, 4, 9]; // var roots = numbers.map(Math.sqrt); // console.log(roots); // var arr=[10,20,30,40,50]; // arr.reverse();//反转 // console.log(arr); // var arr=[1,40,20,10,100]; // //a---arr[j] // //b---arr[j+1] // arr.sort(function (a,b) { // if(a>b){ // return 1; // }else if(a==b){ // return 0; // }else{ // return -1; // } // }); // console.log(arr); // // var arr=[10,20,30,40,50,60,70,80,90,100]; // var newArr= arr.slice(3,7); // console.log(newArr); var myFish = ['angel', 'clown', 'mandarin', 'sturgeon']; // myFish.splice(2, 0, 'drum'); // 在索引为2的位置插入'drum' // myFish 变为 ["angel", "clown", "drum", "mandarin", "sturgeon"] myFish.splice(2, 1); // 从索引为2的位置删除一项(也就是'drum'这一项) console.log(myFish); // myFish 变为 ["angel", "clown", "mandarin", "sturgeon"] </script>
8: apply call bind
1: apply和call都可以让函数或者方法来调用,传入参数和函数自己调用的写法不一样,但是效果是一样的,
apply传入的是数组, call,传入的是参数
2: apply和call可以改变this的指向,
3: apply和call方法实际上并不在函数这个实例对象中,而是在Function的prototype,原型对象中。
4: bind方法是复制的意思,可以在复制的时候传递参数,也可以在复制之后传递参数
5: apply 和 call 是调用的时候改变this的指向
bind是 复制的时候修改this的指向,bind的时候没有调用
9: promise
1:什么是Promise
Promise 是异步编程的一种解决方案,其实是一个构造函数,
自己身上有all、reject、resolve这几个方法,
原型上有then、catch等方法。
2: Promise对象有以下两个特点。
(1)对象的状态不受外界影响。Promise对象代表一个异步操作,有三种状态:pending(进行中)、fulfilled(已成功,完成)和rejected(已失败)。只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态。这也是Promise这个名字的由来,它的英语意思就是“承诺”,表示其他手段无法改变。
(2)一旦状态改变,就不会再变,任何时候都可以得到这个结果。Promise对象的状态改变,只有两种可能:从pending变为fulfilled和从pending变为rejected。只要这两种情况发生,状态就凝固了,不会再变了,会一直保持这个结果,这时就称为 resolved(已定型)。如果改变已经发生了,你再对Promise对象添加回调函数,也会立即得到这个结果。这与事件(Event)完全不同,事件的特点是,如果你错过了它,再去监听,是得不到结果的。
3:注意!我只是new了一个对象,并没有调用它,我们传进去的函数就已经执行了,这是需要注意的一个细节。
// 1. 创建promise实例,在实例中执行异步操作(比如发送网络请求) // 2. 异步操作成功时,调用reslove函数传递数据 // 3. 异步操作失败时,调用reject函数传递错误信息 const promise = new Promise(function(resolve, reject) { // 异步操作 if (/* 异步操作成功 */){ resolve(value); } else { reject(error); } }); // 使用promise实例then方法接收reslove或reject返回的数据 promise.then(function(value) { // 此处数据即为reslove回来的数据 // success }, function(error) { // 此处数据即为reject回来的数据 // failure });
4: .then,方法里面有两个回调,一个处理成功的回调,一个处理失败的回调。
5:.catch用法和.then中的rejected一样
6:all的用法
与then同级的另一个方法,all方法,该方法提供了并行执行异步操作的能力,并且在所有异步操作执行完后并且执行结果都是成功的时候才执行回调。
7:race的用法
all是等所有的异步操作都执行完了再执行then方法,那么race方法就是相反的,谁先执行完成就先执行回调。
10:深浅拷贝
1:js 数据类型
javaScritp的数据类型有:数值类型、字符串类型、布尔类型、null、undefined、对象(数组、正则表达式、日期、函数),大致分成两种:基本数据类型和引用数据类型,
其中:
(1)基本数据类型:数值、字符串、布尔、null、undefined (值类型)
(2)复杂(复合)数据类型:对象 (引用类型)
基本数据类型保存在栈内存,引用类型保存在堆内存中。根本原因在于保存在栈内存的必须是大小固定的数据,引用类型的大小不固定,只能保存在堆内存中,但是可以把它的地址写在栈内存中以供我们访问
如果是基本数据类型,则按值访问,操作的就是变量保存的值;如果是引用类型的值,我们只是通过保存在变量中的引用类型的地址来操作实际对象。
2:基本类型的赋值
<script> // 1:基本类型的赋值 // 赋值的时候,在栈内存中重新开辟内存,存放变量b,所以在栈内存中分别存放着变量a、b各自的值,修改时互不影响 console.log("****** 111111 ******"); var a = 1; var b = a; //赋值 console.log(b)//1 a = 2; // 改变a的值 console.log(b)//1 console.log(a)//2 </script>
3:引用类型的复制
<script> // 2:引用类型的复制 // 因此,对于引用类型的复制,简单赋值无用,需要拷贝。拷贝存在两种类型:深拷贝与浅拷贝 console.log("****** 222222 ******"); var color1 = ['red','green']; var color2 = color1;//赋值 console.log(color2)//['red','green']; color1.push('black') ;//改变color1的值 console.log(color2)//['red','green','black'] console.log(color1)//['red','green','black'] </script>
4、深浅拷贝
浅拷贝只复制指向某个对象的指针,而不复制对象本身,新旧对象还是共享同一块内存。但深拷贝会另外创造一个一模一样的对象,新对象跟原对象不共享内存,修改新对象不会改到原对象
1、浅拷贝
浅拷贝只是拷贝基本类型的数据,如果父对象的属性等于数组或另一个对象,那么实际上,子对象获得的只是一个内存地址,因此存在父对象被篡改的可能,浅拷贝只复制指向某个对象的指针,而不复制对象本身,新旧对象还是共享同一块内存
<body> <script> // 1:基本类型的赋值 // 赋值的时候,在栈内存中重新开辟内存,存放变量b,所以在栈内存中分别存放着变量a、b各自的值,修改时互不影响 console.log("****** 111111 ******"); var a = 1; var b = a; //赋值 console.log(b)//1 a = 2; // 改变a的值 console.log(b)//1 console.log(a)//2 </script> <script> // 2:引用类型的复制 // 因此,对于引用类型的复制,简单赋值无用,需要拷贝。拷贝存在两种类型:深拷贝与浅拷贝 console.log("****** 222222 ******"); var color1 = ['red','green']; var color2 = color1;//赋值 console.log(color2)//['red','green']; color1.push('black') ;//改变color1的值 console.log(color2)//['red','green','black'] console.log(color1)//['red','green','black'] </script> <script> console.log("****** 333333 ******"); var Nation = { nation: '中国' }; function extendCopy(p) { var c = {}; for (var i in p) { c[i] = p[i]; } return c; } var Doctor = extendCopy(Nation); Doctor.career = '医生'; Doctor.nation = '美国'; console.log(Doctor.nation); // 美国 console.log(Nation.nation); // 中国 console.log(Doctor.career); // 医生 console.log(Doctor.__proto_ === Nation.__proto_) // true // 这里涉及到使用拷贝父对象的属性实现继承 console.log("****** 444444 浅拷贝 ******"); function simpleClone(initalObj) { var obj = {}; for ( var i in initalObj) { obj[i] = initalObj[i]; } return obj; } var obj = { a: "hello", b:{ a: "world", b: 21 }, c:["Bob", "Tom", "Jenny"], d:function() { alert("hello world"); } } var obj1 = simpleClone(obj); console.log('obj1=>>>',obj1); // 1、 obj1.c = ['mm', "Tom", "Jenny"]; // 一层,作为整体,重写,全改变;改变属性值,不改变原对象 console.log('obj=>>>',obj); //obj.c => ["Bob", "Tom", "Jenny"] console.log('obj1=>>>',obj1); //obj1.c => ['mm', "Tom", "Jenny"] // 2、 obj1.c[0] = 'mm'; // 浅拷贝时,改变属性的属性值,改变原对象 console.log('obj=>>>',obj); //obj.c => ["Bob", "Tom", "Jenny"] console.log('obj1=>>>',obj1); //obj.c => ["mm", "Tom", "Jenny"] </script> </body>
2: 深拷贝
// 只要不是基本类型,比如数组和对象的话,就需要重新开始开辟一块内存,来存储。之后把地址给这个属性。
<script> //深拷贝:拷贝还是复制,深:把一个对象中所有的属性或者方法,一个一个的找到.并且在另一个对象中开辟相应的空间,一个一个的存储到另一个对象中 var obj1={ age:10, sex:"男", car:["奔驰","宝马","特斯拉","奥拓"], dog:{ name:"大黄", age:5, color:"黑白色" } }; var obj2={};//空对象 //通过函数实现,把对象a中的所有的数据深拷贝到对象b中 function extend(a,b) { for(var key in a){ //先获取a对象中每个属性的值 var item=a[key]; //判断这个属性的值是不是数组 if(item instanceof Array){ //如果是数组,那么在b对象中添加一个新的属性,并且这个属性值也是数组 b[key]=[]; //调用这个方法,把a对象中这个数组的属性值一个一个的复制到b对象的这个数组属性中 extend(item,b[key]); }else if(item instanceof Object){//判断这个值是不是对象类型的 //如果是对象类型的,那么在b对象中添加一个属性,是一个空对象 b[key]={}; //再次调用这个函数,把a对象中的属性对象的值一个一个的复制到b对象的这个属性对象中 extend(item,b[key]); }else{ //如果值是普通的数据,直接复制到b对象的这个属性中 b[key]=item; } } } extend(obj1,obj2); console.dir(obj1); console.dir(obj2); </script>
深拷贝示意图
11:函数节流
函数节流: 指定时间间隔内只会执行一次任务;( 函数调用n秒后才会执行,如果函数在n秒内再被调用的话则上次调用的函数不执行且重新计算执行时间 )
函数防抖: 任务频繁触发的情况下,只有任务触发的间隔超过指定间隔的时候,任务才会执行。
函数节流和函数防抖都是通过减少实际逻辑处理过程的执行来提高事件处理函数运行性能的手段,并没有实质上减少事件的触发次数。
// 函数节流 var closure = true // 初始化执行状态 document.getElementById("two").onscroll = () => { if (!closure) return // 如果开关关闭,说明上次执行没有完成,则return closure = false // 关闭开关,开始执行 setTimeout(() => { console.log('节流执行函数') closure = true // 打开开关 }, 1000) // 延迟1秒执行函数,并打开开关,可以进行下一次执行 } // 函数防抖 var shake // 声明变量接受 document.getElementById("three").onscroll = () => { clearTimeout(shake) // 每执行一下清除延时器,回归初始状态 shake = setTimeout(() => { console.log('函数防抖') // 执行函数 }, 1000) }
12:事件代理
1:DOM事件流的三个阶段:
事件捕获
处于目标阶段
事件冒泡
这里再详细介绍一下浏览器处理DOM事件的过程。对于事件的捕获和处理,不同的浏览器厂商有不同的处理机制,这里我们主要介绍W3C对DOM2.0定义的标准事件。
DOM2.0模型将事件处理流程分为三个阶段:一、事件捕获阶段,二、事件目标阶段,三、事件起泡阶段。如图:
事件捕获:当某个元素触发某个事件(如onclick),顶层对象document就会发出一个事件流,随着DOM树的节点向目标元素节点流去,直到到达事件真正发生的目标元素。在这个过程中,事件相应的监听函数是不会被触发的。
事件目标:当到达目标元素之后,执行目标元素该事件相应的处理函数。如果没有绑定监听函数,那就不执行。
事件起泡:从目标元素开始,往顶层元素传播。途中如果有节点绑定了相应的事件处理函数,这些函数都会被一次触发。如果想阻止事件起泡,可以使用e.stopPropagation()(Firefox)或者e.cancelBubble=true(IE)来组织事件的冒泡传播。
2:事件委托:
事件委托是利用了事件的冒泡原理实现的,子元素的事件通过冒泡形式委托父元素执行
3:优点:
第一个好处是效率高,比如,不用for循环为子元素添加事件了
第二个好处是,js新生成的子元素也不用新为其添加事件了,程序逻辑上比较方便
1.管理的函数变少了。不需要为每个元素都添加监听函数。对于同一个父节点下面类似的子元素,可以通过委托给父元素的监听函数来处理事件。
2.可以方便地动态添加和修改元素,不需要因为元素的改动而修改事件绑定。
3.JavaScript和DOM节点之间的关联变少了,这样也就减少了因循环引用而带来的内存泄漏发生的概率。
缺点:
潜在的问题也许并不那么明显,但是一旦你注意到这些问题,你就可以轻松地避免它们:
你的事件管理代码有成为性能瓶颈的风险,所以尽量使它能够短小精悍。
不是所有的事件都能冒泡的。blur、focus、load和unload不能像其它事件一样冒泡。事实上blur和focus可以用事件捕获而非事件冒泡的方法获得(在IE之外的其它浏览器中)。
在管理鼠标事件的时候有些需要注意的地方。如果你的代码处理mousemove事件的话你遇上性能瓶颈的风险可就大了,因为mousemove事件触发非常频繁。而mouseout则因为其怪异的表现而变得很难用事件代理来管理。
13:let const var
JS中作用域有:全局作用域、函数作用域。没有块作用域的概念。ECMAScript 6(简称ES6)中新增了块级作用域。块作用域由 { } 包括,if语句和for语句里面的{ }也属于块作用域。
es5:var es6:let 、const
1.var定义变量,没有块的概念,可以跨块访问,不能跨函数访问,不初始化出现undefined,不会报错。
2.let定义变量,只能在块作用域里访问,也不能跨函数访问,对函数外部无影响。
3.const定义常量,只能在块作用域里访问,也不能跨函数访问,使用时必须初始化(即必须赋值),而且不能修改。
<script type="text/javascript"> // 块作用域 { var a = 1; let b = 2; const c = 3; // c = 4; // 报错,const不能修改 var aa; let bb; // const cc; // 报错,必须初始化(即必须赋值) console.log(a); // 1 console.log(b); // 2 console.log(c); // 3 } console.log(a); // 1 // console.log(b); // 报错,只能在块作用域里访问 // console.log(c); // 报错,只能在块作用域里访问 // 函数作用域 (function A() { var d = 5; let e = 6; const f = 7; console.log(d); // 5 console.log(e); // 6 (在同一个{ }中,也属于同一个块,可以正常访问到) console.log(f); // 7 (在同一个{ }中,也属于同一个块,可以正常访问到) })(); // console.log(d); // 报错,不能跨函数访问 // console.log(e); // 报错,不能跨函数访问 // console.log(f); // 报错,不能跨函数访问 </script>
14:ES6新特性
1:默认参数
var link = function (height, color, url) { var height = height || 50; var color = color || 'red'; var url = url || 'http://azat.co'; ... }
一切工作都是正常的,直到参数值是0后,就有问题了,因为在JavaScript中,0表示fasly,它是默认被hard-coded的值,而不能变成参数本身的值。当然,如果你非要用0作为值,我们可以忽略这一缺陷并且使用逻辑OR就行了!但在ES6,我们可以直接把默认值放在函数申明里:
var link = function(height = 50, color = 'red', url = 'http://azat.co') { ... }
2:模板对象
在其它语言中,使用模板和插入值是在字符串里面输出变量的一种方式。因此,在ES5,我们可以这样组合一个字符串:
var name = 'Your name is ' + first + ' ' + last + '.'; var url = 'http://localhost:3000/api/messages/' + id;
幸运的是,在ES6中,我们可以使用新的语法$ {NAME},并把它放在反引号里:
var name = `Your name is ${first} ${last}. `; var url = `http://localhost:3000/api/messages/${id}`;
在ES5中,我们不得不使用以下方法来表示多行字符串:
var roadPoem = 'Then took the other, as just as fair,nt' + 'And having perhaps the better claimnt' + 'Because it was grassy and wanted wear,nt' + 'Though as for that the passing therent' + 'Had worn them really about the same,nt'; var fourAgreements = 'You have the right to be you.n You can only be you when you do your best.';
然而在ES6中,仅仅用反引号就可以解决了:
var roadPoem = `Then took the other, as just as fair,
And having perhaps the better claim
Because it was grassy and wanted wear,
Though as for that the passing there
Had worn them really about the same,`;
var fourAgreements = `You have the right to be you.
You can only be you when you do your best.`;
4: 解构赋值
var data = $('body').data(), // data has properties house and mouse house = data.house, mouse = data.mouse;
以及在node.js中用ES5是这样:
var jsonMiddleware = require('body-parser').jsonMiddleware ; var body = req.body, // body has username and password username = body.username, password = body.password;
在ES6,我们可以使用这些语句代替上面的ES5代码:
var { house, mouse} = $('body').data(); // we'll get house and mouse variables var {jsonMiddleware} = require('body-parser'); var {username, password} = req.body;
5:增强的对象字面量
6:箭头函数<script> // 箭头函数相当于匿名函数,箭头是标识。没有语义,箭头前面是参数,后面是函数体 // 1: 箭头函数 没有参数 () => {函数声明} console.log("****** 1: 箭头函数 没有参数 *******"); var f10 = () => { return 10; } var result10 = f10(); console.log(result10); // 2: 箭头函数 一个参数 // 方式1:(单一参数) => {函数声明} console.log("****** 2: 箭头函数 一个参数 方式1: *******"); var f20 = (a) => { return a; } var result20 = f20(20); console.log(result20); // 方式2:单一参数 => {函数声明} console.log("****** 2: 箭头函数 一个参数 方式2: *******"); var f21 = a => { return a; } var result21 = f21(21); console.log(result21); // 3. 多个参数需要用到小括号,参数间逗号间隔 (参数1, 参数2, …, 参数N) => { 函数声明 } console.log("****** 3. 多个参数需要用到小括号,参数间逗号间隔 *******"); var f30 = (a, b, c) => { return a + b + c; } var result30 = f30(10, 20, 30); console.log(result30); // 4. 函数体一条语句 // (参数1, 参数2, …, 参数N) => 表达式(单一) // //相当于:(参数1, 参数2, …, 参数N) =>{ return 表达式; } console.log("****** 4. 函数体一条语句 方式1: *******"); var f40 = (a, b, c) => a + b + c; var result40 = f40(10, 20, 30); console.log(result40); // 5:多条语句 (参数1, 参数2, …, 参数N) => { 函数声明 } console.log("****** 5:多条语句 *******"); var add = (a, b) => { if (typeof a == 'number' && typeof b == 'number') { return a + b } else { return 0 } } var result50 = add(10, 20); console.log(result50); // 6:只有一个return // 当箭头函数的函数体只有一个 `return` 语句时,可以省略 `return` 关键字和方法体的花括号 console.log("****** 6:只有一个返回值 *******"); var f60 = (a, b, c) => { a + b + c; } var result60 = f60(10, 20, 30); console.log(result60); // undefined var f61 = (a, b, c) => a + b + c; var result61 = f61(10, 20, 30); console.log(result61); // 60 var f62 = (a, b, c) => { return a + b + c; } var result62 = f62(10, 20, 30); console.log(result62); // 60 // 7. 返回对象时需要用小括号包起来,因为大括号被占用解释为代码块了 console.log("****** 7. 返回对象时需要用小括号包起来 *******"); var f70 = (a, b) => { var c = a + b; return ({ name : "张三", age : 10, }); } var obj = f70(); console.log(obj); // 8: this console.log("****** 8: this *******"); var obj = { birth: 1990, getAge: function () { console.log(this); var b = this.birth; // 1990 var fn = function () { console.log(this); return new Date().getFullYear() - this.birth; // this指向window或undefined }; return fn(); } }; console.log(obj.getAge()) ; // NaN var obj1 = { birth: 1990, getAge: function () { var b = this.birth; // 1990 console.log(this); var fn = () => { console.log(this); return new Date().getFullYear() - this.birth; // this指向obj对象 } return fn(); } }; console.log(obj1.getAge()) ; // 29 // 9:通过 call 或 apply 调用 由于 箭头函数没有自己的this指针,通过 call() 或 apply() 方法调用一个函数时,只能传递参数 // 注意 // 10: typeof运算符和普通的function一样 console.log("****** 10: typeof运算符和普通的function一样 *******"); var func = a => a console.log(typeof func); // "function" // instanceof也返回true,表明也是Function的实例 console.log(func instanceof Function); // true // 11. 不能使用argument console.log("****** 11. 不能使用argument *******"); var func = () => { console.log(arguments) } func(55) // Uncaught ReferenceError: arguments is not defined // 12: 箭头函数不能用作构造器 箭头函数也没有原型 </script>
7、Promises
在ES6中,我们可以用promise重写:
var wait1000 = new Promise(function(resolve, reject) { setTimeout(resolve, 1000); }).then(function() { console.log('Yay!'); });
用ES6的箭头函数:
var wait1000 = new Promise((resolve, reject)=> { setTimeout(resolve, 1000); }).then(()=> { console.log('Yay!'); });
8:块作用域
JS中作用域有:全局作用域、函数作用域。没有块作用域的概念。ECMAScript 6(简称ES6)中新增了块级作用域。块作用域由 { } 包括,if语句和for语句里面的{ }也属于块作用域。
es5:var es6:let 、const
1.var定义变量,没有块的概念,可以跨块访问,不能跨函数访问,不初始化出现undefined,不会报错。
2.let定义变量,只能在块作用域里访问,也不能跨函数访问,对函数外部无影响。
3.const定义常量,只能在块作用域里访问,也不能跨函数访问,使用时必须初始化(即必须赋值),而且不能修改。
9:类
1、ES6提供了更接近传统语言的写法,引入了Class(类)这个概念,作为对象的模板。通过class
关键字,可以定义类。
//定义类 class Point { constructor(x, y) { this.x = x; this.y = y; } toString() { return '(' + this.x + ', ' + this.y + ')'; } }
2、构造函数的prototype
属性,在ES6的“类”上面继续存在。事实上,类的所有方法都定义在类的prototype
属性上面。
15:CSS布局
1:浮动布局
2:绝对定位布局
3:flex布局
16:rem、px、em单位
px、em、rem都是计量单位,都能表示尺寸,但是有有所不同,而且其各有各的优缺点。
Px表示“绝对尺寸”(并非真正的绝对),实际上就是css中定义的像素(此像素与设备的物理像素有一定的区别,后续详细说明见文末说明1),利用px设置字体大小及元素宽高等比较稳定和精确。Px的缺点是其不能适应浏览器缩放时产生的变化,因此一般不用于响应式网站。
em表示相对尺寸,其相对于当前对象内文本的font-size(如果当前对象内文本的font-size计量单位也是em,则当前对象内文本的font-size的参考对象为父元素文本font-size)。使用em可以较好的相应设备屏幕尺寸的变化,但是在进行元素设置时都需要知道父元素文本的font-size及当前对象内文本的font-size,如有遗漏可能会导致错误。
rem也表示相对尺寸,其参考对象为根元素<html>的font-size,因此只需要确定这一个font-size。
17:定位
1:元素的定位属性主要包括定位模式和边偏移两部分。
1.: 边偏移
| 边偏移属性 | 描述 |
| ------ | ----------------------- |
| top | 顶端偏移量,定义元素相对于其父元素上边线的距离 |
| bottom | 底部偏移量,定义元素相对于其父元素下边线的距离 |
| left | 左侧偏移量,定义元素相对于其父元素左边线的距离 |
| right | 右侧偏移量,定义元素相对于其父元素右边线的距离 |
2:position属性的常用值
| 值 | 描述 |
| -------- | ------------------------ |
| static | 自动定位(默认定位方式) |
| relative | 相对定位,相对于其原文档流的位置进行定位 |
| absolute | 绝对定位,相对于其上一个已经定位的父元素进行定位 |
| fixed | 固定定位,相对于浏览器窗口进行定位 |
3:静态定位(static)
静态定位是所有元素的默认定位方式,当position属性的取值为static时,可以将元素定位于静态位置。 所谓静态位置就是各个元素在HTML文档流中默认的位置。
上面的话翻译成白话: 就是网页中所有元素都默认的是静态定位哦! 其实就是标准流的特性。
在静态定位状态下,无法通过边偏移属性(top、bottom、left或right)来改变元素的位置。
PS: 静态定位其实没啥可说的。
静态定位唯一的用处: 就是 取消定位。 position: static;
4: 相对定位relative(自恋型):脱标、占有位置
相对定位是将元素相对于它在标准流中的位置进行定位,当position属性的取值为relative时,可以将元素定位于相对位置。
对元素设置相对定位后,可以通过边偏移属性改变元素的位置,但是它在文档流中的位置仍然保留。如下图所示,即是一个相对定位的效果展示:
注意:
1. 相对定位最重要的一点是,脱离标准流。它可以通过边偏移移动位置,但是原来的所占的位置,继续占有。
2. 其次,每次移动的位置,是以自己的左上角为基点移动(相对于自己来移动位置)
就是说,相对定位的盒子仍在标准流中,它后面的盒子仍以标准流方式对待它。(相对定位不脱标)
如果说浮动的主要目的是 让多个块级元素一行显示,
那么定位的主要价值就是 移动位置, 让盒子到我们想要的位置上去。
5: 绝对定位absolute (拼爹型):脱标(浮动)、不占位置
[注意] 如果文档可滚动,绝对定位元素会随着它滚动,因为元素最终会相对于正常流的某一部分定位。
当position属性的取值为absolute时,可以将元素的定位模式设置为绝对定位。
注意:
绝对定位最重要的一点是,它可以通过边偏移移动位置,但是它完全脱标,完全不占位置。
6: 父级没有定位
若所有父元素都没有定位,以浏览器当前屏幕为准对齐(document文档)。
7: 父级有定位
绝对定位是将元素依据最近的已经定位(绝对、固定或相对定位)的父元素(祖先)进行定位。
8:固定定位fixed(认死理型):脱标、不占位置
固定定位是绝对定位的一种特殊形式,类似于 正方形是一个特殊的 矩形。它以浏览器窗口作为参照物来定义网页元素。当position属性的取值为fixed时,即可将元素的定位模式设置为固定定位。
当对元素设置固定定位后,它将脱离标准文档流的控制,始终依据浏览器窗口来定义自己的显示位置。不管浏览器滚动条如何滚动也不管浏览器窗口的大小如何变化,该元素都会始终显示在浏览器窗口的固定位置。
固定定位有两点:
1. 固定定位的元素跟父亲没有任何关系,只认浏览器。
2. 固定定位完全脱标,不占有位置,不随着滚动条滚动。
记忆法: 就类似于孙猴子, 无父无母,好不容易找到一个可靠的师傅(浏览器),就听的师傅的,别的都不听。
ie6等低版本浏览器不支持固定定位。
9:定位模式转换
跟 浮动 一样, 元素添加了 绝对定位和固定定位之后, 元素模式也会发生转换, 都转换为 行内块模式
行内块 的宽度和高度 跟内容有关系
** 因此 比如 行内元素 如果添加了 绝对定位或者 固定定位后 浮动后,可以不用转换模式,直接给高度和宽度就可以了。**
10:四种定位总结
| 定位模式 | 是否脱标占有位置 | 是否可以使用边偏移 | 移动位置基准 |
| ------------ | ---------- | --------- | ---------------- |
| 静态static | 不脱标,正常模式 | 不可以 | 正常模式 |
| 相对定位relative | 脱标,占有位置 | 可以 | 相对自身位置移动(自恋型) |
| 绝对定位absolute | 完全脱标,不占有位置 | 可以 | 相对于定位父级移动位置(拼爹型) |
| 固定定位fixed | 完全脱标,不占有位置 | 可以 | 相对于浏览器移动位置(认死理型) |
18:rem适配
rem适配
1:页面内的所有有关尺寸大小的设定都可以用rem来代替,如用rem代替px的使用,可以做到屏幕大内容按比例放大,
2:任何一个设备 1rem= 屏幕尺寸/750*32px。 这是任意设备一个字符的高度。
19:http协议
HTTP协议通常承载于TCP协议之上,在HTTP和TCP之间添加一个安全协议层(SSL或TSL),这个时候,就成了我们常说的HTTPS。
默认HTTP的端口号为80,HTTPS的端口号为443。
20:http缓存
1.Cache-Control
请求/响应头,缓存控制字段,可以说是控制http缓存的最高指令,要不要缓存也是它说了算。
它有以下常用值
1.1 no-store:所有内容都不缓存
1.2 no-cache:缓存,但是浏览器使用缓存前,都会请求服务器判断缓存资源是否是最新,它是个比较高贵的存在,因为它只用不过期的缓存。
1.3 缓存过期机制
max-age=x(单位秒) 请求缓存后的X秒不再发起请求,属于http1.1属性,与下方Expires(http1.0属性)类似,但优先级要比Expires高。
1.4 s-maxage=x(单位秒) 代理服务器请求源站缓存后的X秒不再发起请求,只对CDN缓存有效(这个在后面会细说)
1.5 public 客户端和代理服务器(CDN)都可缓存
1.6 private 只有客户端可以缓存
2.Expires
响应头,代表资源过期时间,由服务器返回提供,GMT格式日期,是http1.0的属性,在与max-age(http1.1)共存的情况下,优先级要低。
3.Last-Modified
响应头,资源最新修改时间,由服务器告诉浏览器。
4.if-Modified-Since
请求头,资源最新修改时间,由浏览器告诉服务器(其实就是上次服务器给的Last-Modified,请求又还给服务器对比),和Last-Modified是一对,它两会进行对比。
过期时间,最后更新时间。
2、http缓存方案
1.md5/hash缓存
通过不缓存html,为静态文件添加MD5或者hash标识,解决浏览器无法跳过缓存过期时间主动感知文件变化的问题。
21:从游览器到一个页面展示经历了什么?
1、清楚一些最重要的术语:
客户端: 简言之就是浏览器,如Chrome或Firefox。其主要作用是进行用户交互,并将其转换为对另一台称为Web服务器的计算机的请求。虽然我们通常使用浏览器访问网络,但您可以将整个计算机视为客户端 - 服务器模型的“客户端”。每个客户端计算机都有一个唯一的地址,称为IP地址,其他计算机可以用来识别它。
服务器: 连接到互联网且具有IP地址的机器。服务器等待来自其他机器(例如客户机)的请求并对其进行响应。不同于您的计算机(即客户端),服务器也具有IP地址并安装运行特殊的服务器软件,确定如何响应来自浏览器的请求。 Web服务器的主要功能是将网页存储,处理和传送给客户端。有许多类型的服务器,包括Web服务器(在这篇文章中,我们在谈论Web服务器),数据库服务器,文件服务器,应用程序服务器等。
IP地址: 互联网协议地址。 TCP / IP网络上的设备(计算机,服务器,打印机,路由器等)的数字标识符。互联网上的每台计算机都有一个IP地址,用于识别和与其他计算机通信。 IP地址有四组数字,以小数点分隔(例如244.155.65.2)。这被称为“逻辑地址”。为了在网络中定位设备,通过TCP / IP协议软件将逻辑IP地址转换为物理地址。这个物理地址(即MAC地址)内置在您的硬件中。
TCP / IP: 传输控制协议/互联网协议。最广泛使用的通信协议。 “协议”是一些标准的规则。TCP / IP被用作通过网络传输数据的标准。
ISP: 互联网服务提供商。 ISP是客户端和服务器之间的中间人。典型的ISP通常是“有线电视公司”。当您的浏览器收请求www.itheima.com, 时,它不会知道在哪里寻找www.itheima.com, 因此,ISP的工作是进行DNS(域名系统)查找,以询问查找的网站的IP地址。
DNS: 域名系统。跟踪计算机的域名及其在互联网上相应IP地址的分布式数据库。不要担心“分布式数据库”如何工作:只需要知道输入www.github.com, 而不是IP地址就行了。
域名: 用于标识一个或多个IP地址。用户使用域名(例如www.github.com, )访问互联网上的网站。当您在浏览器中键入域名时,DNS使用它来查找该给定网站的IP地址。
端口号: 一个16位整数,用于标识服务器上的特定端口,并始终与IP地址相关联。它可以用来识别服务器上可以转发网络请求的特定进程。
主机: 连接到网络的计算机 - 它可以是客户端,服务器或任何其他类型的设备。每个主机都有唯一的IP地址。对于www.google.com, 等网站,主机可以是为该网站的网页提供服务的网络服务器。主机和服务器概念经常混合,但是它们是两个不同的东西。服务器是一种主机,但另一方面,提供托管服务来维护多个Web服务器的机器可以称作主机。在这个意义上,您可以从主机运行服务器。
HTTP: 超文本传输协议。 Web浏览器和Web服务器用于通过互联网进行通信的协议。
URL: 统一资源定位符。 URL识别特定的Web资源。一个简单的例子是https://itheima.com/someone. URL指定协议(“https”),主机名(itheima.com)和文件名(某人的个人资料页面)。用户可以从域名为itheima.com的网络主机通过HTTP获取该URL所标识的Web资源。(很绕口吗?)
简写版:
- 输入url
- 域名解析
- 发起TCP的3次握手
- 建立TCP连接后发起http请求 --> -服务器响应http请求,浏览器得到html代码 --> 浏览器解析html代码,并请求html代码中的资源(如js、css、图片等)
- 浏览器对页面进行渲染呈现给用户
- 传输完成,四次挥手结束。
详细版:
1,在浏览器输入URL
URL:统一资源定位符,用于定位互联网上的资源。
url对应有协议、域名、端口号。
协议是从该计算机获取资源的方式,常见的协议有:http,https,ftp,file;不同协议有不同的通讯内容格式;
2,域名解析
首先,浏览器会解析输入URL的域名去查找对应的IP地址
域名解析流程:
2.1 浏览器缓存
首先搜索浏览器自身的DNS缓存(缓存时间比较短,大概只有1分钟,且只能容纳1000条缓存),看自身的缓存中是否有与输入域名对应的条目,而且没有过期,如果有且没有过期则解析到此结束。
2.2 DNS缓存
如果浏览器自身的缓存里面没有找到对应的条目,那么浏览器会搜索操作系统自身的DNS缓存,如果找到且没有过期则停止搜索解析到此结束.
2.3 路由器缓存
如果在系统DNS缓存也没有找到,那么尝试读取系统hosts文件(位于C:\Windows\System32\drivers\etc),看看这里面有没有该域名对应的IP地址,如果有则解析成功。
2.4 ISP DNS缓存
如果在hosts文件中也没有找到对应的条目,浏览器就会发起一个DNS的系统调用,就会向
本地配置的首选DNS服务器(一般是电信运营商提供的)发起域名解析请求,(通过的是UDP协议向DNS的53端口发起请求,这个请求是递归的请求,也就是运营商的DNS服务器必须得提供给我们该域名的IP地址),运营商的DNS服务器首先查找自身的缓存,找到对应的条目,且没有过期,则解析成功。
2.5 如果仍没有找到对应的条目,则由运营商的DNS代浏览器发起迭代DNS解析请求,直到找到对对应IP地址
1.首先 是找到根域的DNS的IP地址,向其发起请求:(请问www.xxxxxx.com这个域名的IP地址是多少啊?),根域发现这是一个顶级域com域的一个域名,于是就告诉运营商的DNS我不知道这个域名的IP地址,但是我知道com域的IP地址,你去找它去,于是运营商的DNS就得到了com域的IP地址;
2.然后 com域的IP地址发起了请求:(请问www.xxxxxx.com这个域名的IP地址是多少啊?),com域这台服务器告诉运营商的DNS我不知道www.xxxxxx.com这个域名的IP地址,但是我知道xxxxxx.com这个域的DNS地址,你去找它要IP地址去;
3.于是运营商的DNS又向xxxxxx.com这个域名的DNS地址(这个一般就是由域名注册商提供的,像万网,新网等)发起请求:(请问www.xxxxxx.com这个域名的IP地址是多少啊?),这个时候xxxxxx.com域的DNS服务器一查,诶,果真在我这里,于是就把找到的结果发送给运营商的DNS服务器;
这个时候运营商的DNS服务器就拿到了www.xxxxxx.com这个域名对应的IP地址,并返回给操作系统内核,内核又把结果返回给浏览器,终于浏览器拿到了www.xxxxxx.com对应的IP地址,进行下一步操作。
3.浏览器向web服务器发起TCP 连接请求
拿到域名对应的IP地址之后,User-Agent(一般是指浏览器)向WEB服务器发起TCP的连接请求:发起TCP的3次握手。
4.建立TCP连接后发起http请求
经过TCP 3次握手之后,浏览器会通过tcp连接向远程服务器发送 HTTP 的 GET请求。
5.服务器端响应http请求,浏览器得到html代码
服务器端WEB程序接收到http请求以后,就开始处理该请求,处理之后就返回给浏览器html文件。
6.浏览器解析html代码,并请求html代码中的资源
浏览器拿到index.html文件后,就开始解析其中的html代码,遇到js/css/image等静态资源时,就向服务器端去请求下载(会使用多线程下载,每个浏览器的线程数不一样),这个时候就用上keep-alive特性了,建立一次HTTP连接,可以请求多个资源,下载资源的顺序就是按照代码里的顺序。
HTML字符串被浏览器接受后,就会被一句句读取解析
解析到link标签后,重新发送请求获取css
解析到script标签后发送请求获取js
解析到img标签后发送请求获取图片资源等
7.浏览器对页面进行渲染呈现给用户
浏览器根据html和css计算得到渲染树,绘制到屏幕上,js会被执行。
8.传输完成,断开连接
4次挥手断开
其中,步骤2的具体过程是:
浏览器缓存:浏览器会记录DNS一段时间,因此,只是第一个地方解析DNS请求;
操作系统缓存:如果在浏览器缓存中不包含这个记录,则会使系统调用操作系统,获取操作系统的记录(保存最近的DNS查询缓存);
路由器缓存:如果上述两个步骤均不能成功获取DNS记录,继续搜索路由器缓存;
ISP缓存:若上述均失败,继续向ISP搜索。
22:3次握手、4次挥手
TCP/IP连接三次握手,断开4次握手
- 1: 发送方向接收方发送一个带syn标志的数据包。
- 2: 接收方接收后,向发送方发送一个带syn/ack标志的数据包,表示已经接收
- 3: 发送方发送一个ack标志的数据包,表示结束。
- 1: 主动关闭方发送一个fin,表示关闭数据传送。
- 2: 被动关闭方收到后发送一个ack表示接收到。
- 3: 被动关闭方发送一个fin,关闭数据传送。
- 4: 主动关闭放收到后发送一个ack表示收到。
-
1.为什么建立连接协议是三次握手,而关闭连接却是四次握手呢?
这是因为服务端的LISTEN状态下的SOCKET当收到SYN报文的建连请求后,它可以把ACK和SYN(ACK起应答作用,而SYN起同步作用)放在一个报文里来发送。但关闭连接时,当收到对方的FIN报文通知时,它仅仅表示对方没有数据发送给你了;但未必你所有的数据都全部发送给对方了,所以你不可以马上关闭SOCKET,也即你可能还需要发送一些数据给对方之后,再发送FIN报文给对方来表示你同意现在可以关闭连接了,所以它这里的ACK报文和FIN报文多数情况下都是分开发送的。
位码即tcp标志位,有6种标示:
SYN(synchronous建立联机)
ACK(acknowledgement 确认)
PSH(push传送)
FIN(finish结束)
RST(reset重置)
URG(urgent紧急)
Sequence number(顺序号码)
Acknowledge number(确认号码)
23:Vue组件通信、Vue生命周期
1:父组件向子组件传值
1.1:父组件声明数据
1.2:子组件声明属性
1.3:子组件引用的使用绑定属性
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>Document</title> <script src="./lib/vue-2.4.0.js"></script> </head> <body> <div id="app"> <!-- 3:父组件,可以在引用子组件的时候, 通过 属性绑定(v-bind:) 的形式, 把 需要传递给 子组件的数据,以属性绑定的形式,传递到子组件内部,供子组件使用 --> <com1 v-bind:parentmsg="msg"></com1> </div> <script> // 创建 Vue 实例,得到 ViewModel var vm = new Vue({ el: '#app', data: { // 1:父组件声明属性 msg msg: '123 啊-父组件中的数据' }, methods: {}, components: { // 结论:经过演示,发现,子组件中,默认无法访问到 父组件中的 data 上的数据 和 methods 中的方法 com1: { data() { // 注意: 子组件中的 data 数据,并不是通过 父组件传递过来的,而是子组件自身私有的,比如: 子组件通过 Ajax ,请求回来的数据,都可以放到 data 身上; // data 上的数据,都是可读可写的; return { title: '123', content: 'qqq' } }, template: '<h1 @click="change">这是子组件 --- {{ parentmsg }}</h1>', // 2:子组件声明传过来的属性 parentmsg // 注意: 组件中的 所有 props 中的数据,都是通过 父组件传递给子组件的 // props 中的数据,都是只读的,无法重新赋值 props: ['parentmsg'], // 把父组件传递过来的 parentmsg 属性,先在 props 数组中,定义一下,这样,才能使用这个数据 directives: {}, filters: {}, components: {}, methods: { change() { this.parentmsg = '被修改了' } } } } }); </script> </body> </html>
2:父组件向子组件传递事件,通过传递参数,向父组件传值。
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>Document</title> <script src="./lib/vue-2.4.0.js"></script> </head> <body> <div id="app"> <!-- 2:父组件向子组件 传递 方法,使用的是 事件绑定机制; v-on, 当我们自定义了 一个 事件属性之后,那么,子组件就能够,通过某些方式,来调用 传递进去的 这个 方法了 --> <com2 @func="show"></com2> </div> <template id="tmpl"> <div> <h1>这是 子组件</h1> <input type="button" value="这是子组件中的按钮 - 点击它,触发 父组件传递过来的 func 方法" @click="myclick"> </div> </template> <script> // 定义了一个字面量类型的 组件模板对象 var com2 = { template: '#tmpl', // 通过指定了一个 Id, 表示 说,要去加载 这个指定Id的 template 元素中的内容,当作 组件的HTML结构 data() { return { sonmsg: { name: '小头儿子', age: 6 } } }, methods: { myclick() { //3: 当点击子组件的按钮的时候,如何 拿到 父组件传递过来的 func 方法,并调用这个方法??? // emit 英文原意: 是触发,调用、发射的意思 // this.$emit('func123', 123, 456) this.$emit('func', this.sonmsg) } } } // 创建 Vue 实例,得到 ViewModel var vm = new Vue({ el: '#app', data: { datamsgFormSon: null }, methods: { // 1:声明方法 show(data) { // console.log('调用了父组件身上的 show 方法: --- ' + data) // console.log(data); this.datamsgFormSon = data; } }, components: { com2 // com2: com2 } }); </script> </body> </html>
6:Vue的生命周期
24:Vue双向数据绑定和原理
1:v-model指令、数据视图同步更新、使用的是ES5提供的Object.defineProperty()这个方法实现数据劫持
2:数据如何从模型同步到视图?当模型中数据发生变化时会触发Object.defineProperty的set方法,在这个方法内部能够劫持到数据的改变,然后就可以在该方法内部通知视图更新
3:视图中的数据如何同步到模型中?(v-model指令是怎么实现改变了元素中的数据同步到模型中)监听表单元素的change事件,在change事件中可以拿到用户输入的数据,然后
给模型中的数据赋值
25:游览器缓存
一、前言
缓存可以说是性能优化中简单高效的一种优化方式了。一个优秀的缓存策略可以缩短网页请求资源的距离,减少延迟,并且由于缓存文件可以重复利用,还可以减少带宽,降低网络负荷。
对于一个数据请求来说,可以分为发起网络请求、后端处理、浏览器响应三个步骤。浏览器缓存可以帮助我们在第一和第三步骤中优化性能。比如说直接使用缓存而不发起请求,或者发起了请求但后端存储的数据和前端一致,那么就没有必要再将数据回传回来,这样就减少了响应数据。
接下来的内容中我们将通过缓存位置、缓存策略以及实际场景应用缓存策略来探讨浏览器缓存机制。
如需获取思维导图或想阅读更多优质文章请猛戳GitHub博客
从缓存位置上来说分为四种,并且各自有优先级,当依次查找缓存且都没有命中的时候,才会去请求网络。
- Service Worker
- Memory Cache
- Disk Cache
- Push Cache
1.Service Worker
Service Worker 是运行在浏览器背后的独立线程,一般可以用来实现缓存功能。使用 Service Worker的话,传输协议必须为 HTTPS。因为 Service Worker 中涉及到请求拦截,所以必须使用 HTTPS 协议来保障安全。Service Worker 的缓存与浏览器其他内建的缓存机制不同,它可以让我们自由控制缓存哪些文件、如何匹配缓存、如何读取缓存,并且缓存是持续性的。
Service Worker 实现缓存功能一般分为三个步骤:首先需要先注册 Service Worker,然后监听到 install 事件以后就可以缓存需要的文件,那么在下次用户访问的时候就可以通过拦截请求的方式查询是否存在缓存,存在缓存的话就可以直接读取缓存文件,否则就去请求数据。
当 Service Worker 没有命中缓存的时候,我们需要去调用 fetch 函数获取数据。也就是说,如果我们没有在 Service Worker 命中缓存的话,会根据缓存查找优先级去查找数据。但是不管我们是从 Memory Cache 中还是从网络请求中获取的数据,浏览器都会显示我们是从 Service Worker 中获取的内容。
2.Memory Cache
Memory Cache 也就是内存中的缓存,主要包含的是当前中页面中已经抓取到的资源,例如页面上已经下载的样式、脚本、图片等。读取内存中的数据肯定比磁盘快,内存缓存虽然读取高效,可是缓存持续性很短,会随着进程的释放而释放。 一旦我们关闭 Tab 页面,内存中的缓存也就被释放了。
那么既然内存缓存这么高效,我们是不是能让数据都存放在内存中呢?
这是不可能的。计算机中的内存一定比硬盘容量小得多,操作系统需要精打细算内存的使用,所以能让我们使用的内存必然不多。
当我们访问过页面以后,再次刷新页面,可以发现很多数据都来自于内存缓存
内存缓存中有一块重要的缓存资源是preloader相关指令(例如<link rel="prefetch">
)下载的资源。总所周知preloader的相关指令已经是页面优化的常见手段之一,它可以一边解析js/css文件,一边网络请求下一个资源。
需要注意的事情是,内存缓存在缓存资源时并不关心返回资源的HTTP缓存头Cache-Control是什么值,同时资源的匹配也并非仅仅是对URL做匹配,还可能会对Content-Type,CORS等其他特征做校验。
3.Disk Cache
Disk Cache 也就是存储在硬盘中的缓存,读取速度慢点,但是什么都能存储到磁盘中,比之 Memory Cache 胜在容量和存储时效性上。
在所有浏览器缓存中,Disk Cache 覆盖面基本是最大的。它会根据 HTTP Herder 中的字段判断哪些资源需要缓存,哪些资源可以不请求直接使用,哪些资源已经过期需要重新请求。并且即使在跨站点的情况下,相同地址的资源一旦被硬盘缓存下来,就不会再次去请求数据。绝大部分的缓存都来自 Disk Cache,关于 HTTP 的协议头中的缓存字段,我们会在下文进行详细介绍。
浏览器会把哪些文件丢进内存中?哪些丢进硬盘中?
关于这点,网上说法不一,不过以下观点比较靠得住:
- 对于大文件来说,大概率是不存储在内存中的,反之优先
- 当前系统内存使用率高的话,文件优先存储进硬盘
4.Push Cache
Push Cache(推送缓存)是 HTTP/2 中的内容,当以上三种缓存都没有命中时,它才会被使用。它只在会话(Session)中存在,一旦会话结束就被释放,并且缓存时间也很短暂,在Chrome浏览器中只有5分钟左右,同时它也并非严格执行HTTP头中的缓存指令。
Push Cache 在国内能够查到的资料很少,也是因为 HTTP/2 在国内不够普及。这里推荐阅读Jake Archibald
的 HTTP/2 push is tougher than I thought 这篇文章,文章中的几个结论:
- 所有的资源都能被推送,并且能够被缓存,但是 Edge 和 Safari 浏览器支持相对比较差
- 可以推送 no-cache 和 no-store 的资源
- 一旦连接被关闭,Push Cache 就被释放
- 多个页面可以使用同一个HTTP/2的连接,也就可以使用同一个Push Cache。这主要还是依赖浏览器的实现而定,出于对性能的考虑,有的浏览器会对相同域名但不同的tab标签使用同一个HTTP连接。
- Push Cache 中的缓存只能被使用一次
- 浏览器可以拒绝接受已经存在的资源推送
- 你可以给其他域名推送资源
如果以上四种缓存都没有命中的话,那么只能发起请求来获取资源了。
那么为了性能上的考虑,大部分的接口都应该选择好缓存策略,通常浏览器缓存策略分为两种:强缓存和协商缓存,并且缓存策略都是通过设置 HTTP Header 来实现的。
三、缓存过程分析
浏览器与服务器通信的方式为应答模式,即是:浏览器发起HTTP请求 – 服务器响应该请求,那么浏览器怎么确定一个资源该不该缓存,如何去缓存呢?浏览器第一次向服务器发起该请求后拿到请求结果后,将请求结果和缓存标识存入浏览器缓存,浏览器对于缓存的处理是根据第一次请求资源时返回的响应头来确定的。具体过程如下图:
由上图我们可以知道:
-
浏览器每次发起请求,都会先在浏览器缓存中查找该请求的结果以及缓存标识
-
浏览器每次拿到返回的请求结果都会将该结果和缓存标识存入浏览器缓存中
以上两点结论就是浏览器缓存机制的关键,它确保了每个请求的缓存存入与读取,只要我们再理解浏览器缓存的使用规则,那么所有的问题就迎刃而解了,本文也将围绕着这点进行详细分析。为了方便大家理解,这里我们根据是否需要向服务器重新发起HTTP请求将缓存过程分为两个部分,分别是强缓存和协商缓存。
四、强缓存
强缓存:不会向服务器发送请求,直接从缓存中读取资源,在chrome控制台的Network选项中可以看到该请求返回200的状态码,并且Size显示from disk cache或from memory cache。强缓存可以通过设置两种 HTTP Header 实现:Expires 和 Cache-Control。
1.Expires
缓存过期时间,用来指定资源到期的时间,是服务器端的具体的时间点。也就是说,Expires=max-age + 请求时间,需要和Last-modified结合使用。Expires是Web服务器响应消息头字段,在响应http请求时告诉浏览器在过期时间前浏览器可以直接从浏览器缓存取数据,而无需再次请求。
Expires 是 HTTP/1 的产物,受限于本地时间,如果修改了本地时间,可能会造成缓存失效。Expires: Wed, 22 Oct 2018 08:41:00 GMT
表示资源会在 Wed, 22 Oct 2018 08:41:00 GMT 后过期,需要再次请求。
2.Cache-Control
在HTTP/1.1中,Cache-Control是最重要的规则,主要用于控制网页缓存。比如当Cache-Control:max-age=300
时,则代表在这个请求正确返回时间(浏览器也会记录下来)的5分钟内再次加载资源,就会命中强缓存。
Cache-Control 可以在请求头或者响应头中设置,并且可以组合使用多种指令:
public:所有内容都将被缓存(客户端和代理服务器都可缓存)。具体来说响应可被任何中间节点缓存,如 Browser <-- proxy1 <-- proxy2 <-- Server,中间的proxy可以缓存资源,比如下次再请求同一资源proxy1直接把自己缓存的东西给 Browser 而不再向proxy2要。
private:所有内容只有客户端可以缓存,Cache-Control的默认取值。具体来说,表示中间节点不允许缓存,对于Browser <-- proxy1 <-- proxy2 <-- Server,proxy 会老老实实把Server 返回的数据发送给proxy1,自己不缓存任何数据。当下次Browser再次请求时proxy会做好请求转发而不是自作主张给自己缓存的数据。
no-cache:客户端缓存内容,是否使用缓存则需要经过协商缓存来验证决定。表示不使用 Cache-Control的缓存控制方式做前置验证,而是使用 Etag 或者Last-Modified字段来控制缓存。需要注意的是,no-cache这个名字有一点误导。设置了no-cache之后,并不是说浏览器就不再缓存数据,只是浏览器在使用缓存数据时,需要先确认一下数据是否还跟服务器保持一致。
no-store:所有内容都不会被缓存,即不使用强制缓存,也不使用协商缓存
max-age:max-age=xxx (xxx is numeric)表示缓存内容将在xxx秒后失效
s-maxage(单位为s):同max-age作用一样,只在代理服务器中生效(比如CDN缓存)。比如当s-maxage=60时,在这60秒中,即使更新了CDN的内容,浏览器也不会进行请求。max-age用于普通缓存,而s-maxage用于代理缓存。s-maxage的优先级高于max-age。如果存在s-maxage,则会覆盖掉max-age和Expires header。
max-stale:能容忍的最大过期时间。max-stale指令标示了客户端愿意接收一个已经过期了的响应。如果指定了max-stale的值,则最大容忍时间为对应的秒数。如果没有指定,那么说明浏览器愿意接收任何age的响应(age表示响应由源站生成或确认的时间与当前时间的差值)。
min-fresh:能够容忍的最小新鲜度。min-fresh标示了客户端不愿意接受新鲜度不多于当前的age加上min-fresh设定的时间之和的响应。
从图中我们可以看到,我们可以将多个指令配合起来一起使用,达到多个目的。比如说我们希望资源能被缓存下来,并且是客户端和代理服务器都能缓存,还能设置缓存失效时间等等。
3.Expires和Cache-Control两者对比
其实这两者差别不大,区别就在于 Expires 是http1.0的产物,Cache-Control是http1.1的产物,两者同时存在的话,Cache-Control优先级高于Expires;在某些不支持HTTP1.1的环境下,Expires就会发挥用处。所以Expires其实是过时的产物,现阶段它的存在只是一种兼容性的写法。
强缓存判断是否缓存的依据来自于是否超出某个时间或者某个时间段,而不关心服务器端文件是否已经更新,这可能会导致加载文件不是服务器端最新的内容,那我们如何获知服务器端内容是否已经发生了更新呢?此时我们需要用到协商缓存策略。
五、协商缓存
协商缓存就是强制缓存失效后,浏览器携带缓存标识向服务器发起请求,由服务器根据缓存标识决定是否使用缓存的过程,主要有以下两种情况:
-
协商缓存生效,返回304和Not Modified
-
协商缓存失效,返回200和请求结果
协商缓存可以通过设置两种 HTTP Header 实现:Last-Modified 和 ETag 。
1.Last-Modified和If-Modified-Since
浏览器在第一次访问资源时,服务器返回资源的同时,在response header中添加 Last-Modified的header,值是这个资源在服务器上的最后修改时间,浏览器接收后缓存文件和header;
Last-Modified: Fri, 22 Jul 2016 01:47:00 GMT
浏览器下一次请求这个资源,浏览器检测到有 Last-Modified这个header,于是添加If-Modified-Since这个header,值就是Last-Modified中的值;服务器再次收到这个资源请求,会根据 If-Modified-Since 中的值与服务器中这个资源的最后修改时间对比,如果没有变化,返回304和空的响应体,直接从缓存读取,如果If-Modified-Since的时间小于服务器中这个资源的最后修改时间,说明文件有更新,于是返回新的资源文件和200
但是 Last-Modified 存在一些弊端:
- 如果本地打开缓存文件,即使没有对文件进行修改,但还是会造成 Last-Modified 被修改,服务端不能命中缓存导致发送相同的资源
- 因为 Last-Modified 只能以秒计时,如果在不可感知的时间内修改完成文件,那么服务端会认为资源还是命中了,不会返回正确的资源
既然根据文件修改时间来决定是否缓存尚有不足,能否可以直接根据文件内容是否修改来决定缓存策略?所以在 HTTP / 1.1 出现了 ETag
和If-None-Match
2.ETag和If-None-Match
Etag是服务器响应请求时,返回当前资源文件的一个唯一标识(由服务器生成),只要资源有变化,Etag就会重新生成。浏览器在下一次加载资源向服务器发送请求时,会将上一次返回的Etag值放到request header里的If-None-Match里,服务器只需要比较客户端传来的If-None-Match跟自己服务器上该资源的ETag是否一致,就能很好地判断资源相对客户端而言是否被修改过了。如果服务器发现ETag匹配不上,那么直接以常规GET 200回包形式将新的资源(当然也包括了新的ETag)发给客户端;如果ETag是一致的,则直接返回304知会客户端直接使用本地缓存即可。
3.两者之间对比:
- 首先在精确度上,Etag要优于Last-Modified。
Last-Modified的时间单位是秒,如果某个文件在1秒内改变了多次,那么他们的Last-Modified其实并没有体现出来修改,但是Etag每次都会改变确保了精度;如果是负载均衡的服务器,各个服务器生成的Last-Modified也有可能不一致。
- 第二在性能上,Etag要逊于Last-Modified,毕竟Last-Modified只需要记录时间,而Etag需要服务器通过算法来计算出一个hash值。
- 第三在优先级上,服务器校验优先考虑Etag
六、缓存机制
强制缓存优先于协商缓存进行,若强制缓存(Expires和Cache-Control)生效则直接使用缓存,若不生效则进行协商缓存(Last-Modified / If-Modified-Since和Etag / If-None-Match),协商缓存由服务器决定是否使用缓存,若协商缓存失效,那么代表该请求的缓存失效,返回200,重新返回资源和缓存标识,再存入浏览器缓存中;生效则返回304,继续使用缓存。具体流程图如下:
看到这里,不知道你是否存在这样一个疑问:如果什么缓存策略都没设置,那么浏览器会怎么处理?
对于这种情况,浏览器会采用一个启发式的算法,通常会取响应头中的 Date 减去 Last-Modified 值的 10% 作为缓存时间。
七、实际场景应用缓存策略
1.频繁变动的资源
Cache-Control: no-cache
对于频繁变动的资源,首先需要使用Cache-Control: no-cache
使浏览器每次都请求服务器,然后配合 ETag 或者 Last-Modified 来验证资源是否有效。这样的做法虽然不能节省请求数量,但是能显著减少响应数据大小。
2.不常变化的资源
Cache-Control: max-age=31536000
通常在处理这类资源时,给它们的 Cache-Control 配置一个很大的 max-age=31536000
(一年),这样浏览器之后请求相同的 URL 会命中强制缓存。而为了解决更新的问题,就需要在文件名(或者路径)中添加 hash, 版本号等动态字符,之后更改动态字符,从而达到更改引用 URL 的目的,让之前的强制缓存失效 (其实并未立即失效,只是不再使用了而已)。
在线提供的类库 (如 jquery-3.3.1.min.js
, lodash.min.js
等) 均采用这个模式。
八、用户行为对浏览器缓存的影响
所谓用户行为对浏览器缓存的影响,指的就是用户在浏览器如何操作时,会触发怎样的缓存策略。主要有 3 种:
- 打开网页,地址栏输入地址: 查找 disk cache 中是否有匹配。如有则使用;如没有则发送网络请求。
- 普通刷新 (F5):因为 TAB 并没有关闭,因此 memory cache 是可用的,会被优先使用(如果匹配的话)。其次才是 disk cache。
- 强制刷新 (Ctrl + F5):浏览器不使用缓存,因此发送的请求头部均带有
Cache-control: no-cache
(为了兼容,还带了Pragma: no-cache
),服务器直接返回 200 和最新内容。
26:本地存储
对浏览器来说,使用 Web Storage 存储键值对比存储 Cookie 方式更直观,而且容量更大,它包含两种:localStorage 和 sessionStorage
sessionStorage(临时存储) :为每一个数据源维持一个存储区域,在浏览器打开期间存在,包括页面重新加载
localStorage(长期存储) :与 sessionStorage 一样,但是浏览器关闭后,数据依然会一直存在
<script> /* 1: 对浏览器来说,使用 Web Storage 存储键值对比存储 Cookie 方式更直观,而且容量更大,它包含两种:localStorage 和 sessionStorage sessionStorage(临时存储) :为每一个数据源维持一个存储区域,在浏览器打开期间存在,包括页面重新加载 localStorage(长期存储) :与 sessionStorage 一样,但是浏览器关闭后,数据依然会一直存在 */ /* 2. 保存数据到本地 sessionStorage.setItem('key', JSON.stringify(info)) */ const info = { name: 'Lee', age: 20, id: '001' }; localStorage.setItem("key1", "11"); localStorage.setItem("key1", "22"); sessionStorage.setItem('key', JSON.stringify(info)); localStorage.setItem('key', JSON.stringify(info)); /* 3: 从本地存储获取数据 */ var data1 = JSON.parse(sessionStorage.getItem('key')); var data2 = JSON.parse(localStorage.getItem('key')); console.log(data1); console.log(data2); /* 4: 本地存储中删除某个保存的数据 */ // sessionStorage.removeItem('key'); // localStorage.removeItem('key'); /* 5: 删除所有保存的数据 */ // sessionStorage.clear(); // localStorage.clear(); window.addEventListener('storage', function (e) { console.log('key', e.key); console.log('oldValue', e.oldValue); console.log('newValue', e.newValue); console.log('url', e.url); }); </script>
27:webpackage
常见的Vue命令
全局安装
保存到项目中 -S
开发是用到 -D
安装babel插件
1: 运行 cnpm i @babel/core babel-loader @babel/plugin-transform-runtime -D 转换工具
2: 运行 cnpm i @babel/preset-env @babel/preset-stage-0 -D 语法
3: 安装能够识别转换jsx语法的包 babel-preset-react
运行 cnpm i @babel/preset-react -D
4: 执行命令:cnpm i @babel/plugin-proposal-class-properties -D
5: 执行命令:cnpm i @babel/runtime -D
9: Vue组件
npm i vue-loader vue-template-compiler -D
10: vue-router
npm i vue-router -S
其他命令
28:冒泡 、快排
排序算法说明:
(1)对于评述算法优劣术语的说明
稳定:如果a原本在b前面,而a=b,排序之后a仍然在b的前面;
不稳定:如果a原本在b的前面,而a=b,排序之后a可能会出现在b的后面;
内排序:所有排序操作都在内存中完成;
外排序:由于数据太大,因此把数据放在磁盘中,而排序通过磁盘和内存的数据传输才能进行;
时间复杂度: 一个算法执行所耗费的时间。
空间复杂度: 运行完一个程序所需内存的大小。
(2)排序算法图片总结:
1: 冒泡排序
1.比较相邻的两个元素,如果前一个比后一个大,则交换位置, 一直到最后一个元素位置。 2.第一轮的时候最后一个元素应该是最大的一个。 3.按照步骤一的方法进行相邻两个元素的比较,这个时候由于倒数第二个一个元素已经是最大的了, 所以最后一个元素不用比较。
<script> // 1.冒泡排序: /* 1.冒泡排序: 1.比较相邻的两个元素,如果前一个比后一个大,则交换位置, 一直到最后一个元素位置。 2.第一轮的时候最后一个元素应该是最大的一个。 3.按照步骤一的方法进行相邻两个元素的比较,这个时候由于倒数第二个一个元素已经是最大的了, 所以最后一个元素不用比较。 */ console.log("****** 1:冒泡排序 ******") function sort(elements) { // 1: 外循环是轮数 for (var i = 0; i < elements.length - 1; i++) { // 2:内循环是第一步骤 for (var j = 0; j < elements.length - 1 - i; j ++){ if (elements[j] > elements[j + 1]){ var temp = elements[j]; elements[j] = elements[j + 1]; elements[j + 1] = temp; } } } } var elements = [3, 1, 5, 7, 2, 4, 9, 6, 10, 8]; console.log('before-- ' + elements); sort(elements); console.log('after-- ' + elements); </script>
2:快速排序
<script> // 2:快速排序 /* 1.以一个数为基准(中间的数),比基准小的放到左边,比基准大的放到右边 2.再按此方法对这两部分数据分别进行快速排序(递归进行) 3.不能再分后退出递归,并重新将数组合并 */ console.log("****** 2:快速排序 ******") function quickSort(elements) { if(elements.length <= 1){ return elements; } // pivot [ˈpɪvət] 中心点 // floor 向下取整 var pivotIndex = Math.floor(elements.length/2); // 删除元素:array.splice(pivotIndex, 1); // 添加元素: array.splice(2, 0, "three"); 拼接函数(索引位置, 要删除元素的数量, 元素) // splice() 方法可删除从 index 处开始的零个或多个元素,并且用参数列表中声明的一个或多个值来替换那些被删除的元素。 如果从 arrayObject 中删除了元素,则返回的是含有被删除的元素的数组。 var arr = elements.splice(pivotIndex, 1); var pivot = arr[0]; var left = []; var right = []; for (var i = 0; i < elements.length; i ++ ){ if(elements[i] < pivot){ left.push(elements[i]); }else{ right.push(elements[i]); } } return quickSort(left).concat([pivot], quickSort(right)); } var arr = [49, 38, 65, 97, 13, 27, 49]; console.log(quickSort(arr)); </script>
29:数组去重
<script> // indexof去重 function unique(arr) { if (!Array.isArray(arr)) { console.log('type error!') return } var array = []; for (var i = 0; i < arr.length; i++) { if (array .indexOf(arr[i]) === -1) { array .push(arr[i]) } } return array; } var arr = [1,1,'true','true',true,true,15,15,false,false, undefined,undefined, null,null, NaN, NaN,'NaN', 0, 0, 'a', 'a',{},{}]; console.log(unique(arr)); // 2: filter去重 function unique1(arr) { return arr.filter(function(item, index, arr) { //当前元素,在原始数组中的第一个索引==当前索引值,否则返回当前元素 return arr.indexOf(item, 0) === index; }); } var arr1 = [1,1,'true','true',true,true,15,15,false,false, undefined,undefined, null,null, NaN, NaN,'NaN', 0, 0, 'a', 'a',{},{}]; console.log(unique1(arr1)) //[1, "true", true, 15, false, undefined, null, "NaN", 0, "a", {…}, {…}] </script>
JavaScript数组去重(12种方法,史上最全)
30:跨域
一、什么是跨域
JavaScript出于安全方面的考虑,不允许跨域调用其他页面的对象。那什么是跨域呢,简单地理解就是因为JavaScript同源策略的限制,a.com域名下的js无法操作b.com或是c.a.com域名下的对象。
当协议、子域名、主域名、端口号中任意一个不相同时,都算作不同域。不同域之间相互请求资源,就算作“跨域”。
例如:http://www.abc.com/index.html 请求 http://www.efg.com/service.php。
有一点必须要注意:跨域并不是请求发不出去,请求能发出去,服务端能收到请求并正常返回结果,只是结果被浏览器拦截了。之所以会跨域,是因为受到了同源策略的限制,同源策略要求源相同才能正常进行通信,即协议、域名、端口号都完全一致。
大家可以参照下图,有助于深入理解跨域。
特别说明两点:
第一:如果是协议和端口造成的跨域问题“前台”是无能为力的。
第二:在跨域问题上,域仅仅是通过“URL的首部”来识别而不会根据域名对应的IP地址是否相同来判断。“URL的首部”可以理解为“协议, 域名和端口必须匹配”。
二、什么是同源策略及其限制
同源策略限制从一个源加载的文档或脚本如何与来自另一个源的资源进行交互。这是一个用于隔离潜在恶意文件的关键的安全机制。它的存在可以保护用户隐私信息,防止身份伪造等(读取Cookie)。
同源策略限制内容有:
-
Cookie、LocalStorage、IndexedDB 等存储性内容
-
DOM 节点
-
AJAX 请求不能发送
但是有三个标签是允许跨域加载资源:
img
link
script
三、处理跨域方法一——JSONP
1.JSONP原理
利用 <script>
元素的这个开放策略,网页可以得到从其他来源动态产生的 JSON 数据。JSONP请求一定需要对方的服务器做支持才可以。
2.JSONP和AJAX对比
JSONP和AJAX相同,都是客户端向服务器端发送请求,从服务器端获取数据的方式。但AJAX属于同源策略,JSONP属于非同源策略(跨域请求)
3.JSONP优缺点
JSONP优点是兼容性好,可用于解决主流浏览器的跨域数据访问的问题。缺点是仅支持get方法具有局限性。
4.JSONP的流程(以第三方API地址为例,不必考虑后台程序)
-
声明一个回调函数,其函数名(如fn)当做参数值,要传递给跨域请求数据的服务器,函数形参为要获取目标数据(服务器返回的data)。
-
创建一个
<script>
标签,把那个跨域的API数据接口地址,赋值给script的src,还要在这个地址中向服务器传递该函数名(可以通过问号传参:?callback=fn)。 -
服务器接收到请求后,需要进行特殊的处理:把传递进来的函数名和它需要给你的数据拼接成一个字符串,例如:传递进去的函数名是fn,它准备好的数据是fn([{"name":"jianshu"}])。
-
最后服务器把准备的数据通过HTTP协议返回给客户端,客户端再调用执行之前声明的回调函数(fn),对返回的数据进行操作。
四、处理跨域方法二——CORS
1.CORS原理
整个CORS通信过程,都是浏览器自动完成,不需要用户参与。对于开发者来说,CORS通信与同源的AJAX通信没有差别,代码完全一样。浏览器一旦发现AJAX请求跨源,就会自动添加一些附加的头信息,有时还会多出一次附加的请求,但用户不会有感觉。因此,实现CORS通信的关键是服务器。只要服务器实现了CORS接口,就可以跨源通信。
JSONP的优缺点
JSONP的优点是:它不像XMLHttpRequest
对象实现的Ajax请求那样受到同源策略的限制;它的兼容性更好,在更加古老的浏览器中都可以运行,不需要XMLHttpRequest或ActiveX的支持;并且在请求完毕后可以通过调用callback的方式回传结果。
JSONP的缺点则是:它只支持GET请求而不支持POST等其它类型的HTTP请求;它只支持跨域HTTP请求这种情况,不能解决不同域的两个页面之间如何进行JavaScript
调用的问题。
CORS和JSONP对比
CORS与JSONP相比,无疑更为先进、方便和可靠。
1、 JSONP只能实现GET请求,而CORS支持所有类型的HTTP请求。
2、 使用CORS,开发者可以使用普通的XMLHttpRequest发起请求和获得数据,比起JSONP有更好的错误处理。
3、 JSONP主要被老的浏览器支持,它们往往不支持CORS,而绝大多数现代浏览器都已经支持了CORS)。
方向代理跨域
通过访问第三方服务器,让第三方服务器帮我们发送请求.
31:post和get区别
request获取请求参数
最为常见的客户端传递参数方式有两种:
浏览器地址栏直接输入:一定是GET请求;
超链接:一定是GET请求;
表单:可以是GET,也可以是POST,这取决与<form>的method属性值;
GET请求和POST请求的区别:
1.效率
- GET的意思是『得』,从服务器获取数据(也可以上传数据,参数就是),效率较高
- POST的意思是『给』,但可以向服务器发送数据和下载数据,效率不如GET
2.缓存
- GET 请求能够被缓存,默认的请求方式也是有缓存的
- POST请求默认不会缓存
- 缓存是针对URL来进行缓存的,GET请求由于其参数是直接加在URL上-的,一种参数组合就有一种URL的缓存,可以根据参数来进行一一对应,重复请求是幂等的(不论请求多少次,结果都一样);
- 而POST请求的URL没有参数,每次请求的URL都相同,数据体(HTTPBody)可能不同,无法一一对应,所以缓存没有意义
3.安全性
- GET的所有参数全部包装在URL中,明文显示,且服务器的访问日志会记录,非常不安全
- POST的URL中只有资源路径,不包含参数,参数封装在二进制的数据体中,服务器也不会记录参数,相对安全。所有涉及用户隐私的数据都要用POST传输
POST的安全是相对的,对于普通用户来说他们看不到明文,数据封装对他们来说就是屏障。但是对于专业人士,它们会抓包会分析,没有加密的数据包对他们来说也是小case。所以POST仅仅是相对安全,唯有对数据进行加密才会更安全。当然加密也有被破解的可能性,理论上所有的加密方式都可以破解,只是时间长短的问题。而加密算法要做的就是使得破解需要的时间尽量长,越长越安全。由于我们也需要解密,加密算法太过复杂也并非好事,这就要结合使用情况进行折中或者足够实际使用即可。绕的有点远,具体的话,我将在后续的文章之中介提及,并介绍一些常用的加密算法。
4.数据量
HTTP协议中均没有对GET和POST请求的数据大小进行限制,但是实际应用中它们通常受限于软硬件平台的设计和性能。
- GET:不同的浏览器和服务器不同,一般限制在2~8K之间,更加常见的是1k以内
- POST方法提交的数据比较大,大小靠服务器的设定值限制,PHP默认是2M(具体的话大家以后看后端给的开发文档就行了)
32:同步任务、微任务、宏任务
- macro-task(宏任务):包括整体代码script,setTimeout,setInterval
- micro-task(微任务):Promise,process.nextTick