文章目录
JS
值类型 vs 引用类型
队列 FIFO,堆栈 FILO
内存地址分布如下:
栈在上,堆在下,一般不会重合。
let a = 100;
let b = a;
a = 200;
console.log(b); // 100
let a = { age: 20 };
let b = a;
b.age = 21;
console.log(a.age); // 21
常见值类型:
let a; //undefined,若定义const a;会报错
const s = 'abc';
const n = 100;
const b = ture;
const s = Symbol('s'); //Symbol独一无二的类型
常见引用类型:
const obj = { x: 100 };
const arr = [1, 2, 3];
const n = null; //特殊引用类型,指针所指地址为空地址
//特殊引用类型,但不用于存储数据,所以没有“拷贝、复制函数”这一说
function fn() {}
typeof可以判断哪些类型
- 能识别所有的值类型
- 识别函数
- 判断是否是引用类型(不可再细分)
let a = 'abc';
typeof a; // 'string'
深拷贝
//深拷贝
const obj = {
age: 20;
name: 'xxx',
address: {
city: 'beijing';
},
arr: ['a', 'b', 'c']
}
function deepclone(obj = {}){
if(typeof obj !== 'object' || obj == null){
return obj;
}
let result;
if(obj instanceof Array){
result = [];
} else {
result = {}
}
for(let key in obj){
//保证不是原型的key
if(obj.hasOwnProperty(key)){
//递归调用
result[key] = deepclone(obj[key]);
}
}
//返回结果
return result;
}
强制类型转换
字符串拼接
const c = true = '10'; // 'true10'
== 运算符
0 == ''; // true
0 == false; // true
false == ''; // true
null == undefined; // true
综上,除了 ==
null之外,其他地方一律用 ===
const obj = { x: 100 }
if(obj.a == null) {}
//相当于:
//if(obj.a === null || obj.a === undefined) {}
trully变量!!a === true
falsely变量 !!a === false
!!0 === false
!!NaN === false
!!'' === false
!!null === false
!!undefined === false
!!false === false
原型和继承
继承:
- extends
- super
- 扩展或重写方法
// 父类
class People {
constructor(name){
this.name = name;
}
eat(){
console.log(`${this.name} eat something`);
}
}
// 子类
class Student extends Prople{
constructor(name, number){
super(name); //super会把name交给父类来处理(赋值)
this.number = number;
}
sayHi(){
console.log(`姓名${this.name},学号${this.number}`);
}
}
const v= new Student('小明', 100);
console.log(xiaoming);
xiaoming.eat();
xiaoming.sayHi(); // 姓名小明,学号100
//注意:
//如果调用方式为:
xiaoming.__proto__.sayHi(); // 姓名undefined,学号undefined
//是因为隐式原型中没有name、number
xiaoming.__proto__.name; // undefined
xiaoming.__proto__.number; // undefined
//可以这么写:
xiaoming.__proto__.sayHi.call(xiaoming);
instanceof判断属于哪个构造函数/class
xiaoming instanceof Student; // true
xiaoming instanceof Prople; // true
xiaoming instanceof Object; // true Object是所有的父类
[] instanceof Array; // true
[] instanceof Object; // true
{} instanceof Object; // true
原型:
- 每个class都有显式原型
prototype
- 每个实例都有隐式原型
__proto__
- 实例的
__proto__
指向对应的class的prototype
原型链:
访问当前属性,若没有则通过隐式原型__proto__
向上查找,
console.log(People.prototype === Student.prototype.__proto__);
hasOwnproperty:
instanceof:
顺着实例的原型链网上找显示原型,如果在原型链上,返回true。
例如上图,
xialuo instanceof Array // false 因为不在原型链上
手写简易的jQuery考虑插件和扩展性
jQuery做DOM查询的
class jQuery {
constructor(selector) {
const result = document.querySelectorAll(selector);
const length = result.length;
for (let i = 0; i < length; i++) {
this[i] = result[i];
}
this.length = length;
this.selector = selector;
}
get(index) {
return this[index];
}
each(fn) {
for (let i = 0; i < this.length; i++) {
const elem = this[i];
fn(elem);
}
}
on(type, fn) {
return this.each((elem) => {
elem.addEventListener(type, fn, false);
});
}
}
//考虑插件:往jQuery原型中添加
jQuery.prototype.dialog = function (info) {
alert(info);
}
//复写机制: 造轮子
class myJQuery extends jQuery {
constructor(selector) {
super(selector);
}
//扩展自己的方法
addClass(className) {
//...
}
style(data) {
//...
}
}
插件:用的还是jQuery本身,是在jQuery上扩展的
轮子:是继承了jQuery,上面添加包裹一些更好的方法,然后提供的是封好的新类
使用例子:
let $q = new jQuery('p');
$q.get(1); // <p></p>
$q.each((i) => { console.log(i.nodeName) }); // p 三次
$q.on('click', ()=>{ alert('click') });
$q.dialog(3);
作用域和闭包
作用域举例:
// 场景一
let i, a;
for ( i = 0; i < 10; i++) {
a = document.createElement("a");
a.innerHTML = i + "</br>";
document.body.appendChild(a);
a.addEventListener("click", function () {
alert(i); //点击所有的全部是10
});
}
// 场景二
let i, a;
for ( let i = 0; i < 10; i++) { //这里定义局部变量
a = document.createElement("a");
a.innerHTML = i + "</br>";
document.body.appendChild(a);
a.addEventListener("click", function () {
alert(i); //点击所有的一次是 0 ~ 9
});
}
闭包两种场景:
- 函数作为返回值
- 函数作为参数
闭包:自由变量的查找,是在函数定义的地方,向上级作用域查找,而不是在执行的地方!!!
this几种场景:
- 作为普通函数
- 使用call apply bind
- 作为对象方法被调用
- 在class方法中被调用
- 箭头函数
** this的取值是在函数执行的时候确定的,不加粗样式是函数定义的时候确定的!!**
// 作为普通函数
function fn1() {
console.log(this);
}
fn1(); // window
// 使用call apply bind
fn1.call({ x: 100 }); // { x: 100 }
const fn2 = fn1.bind({ x: 200 });
fn2(); // { x: 200 }
// call 直接执行函数
// bind 返回一个函数
// 作为对象方法被调用,setTimeout为普通函数
const zhangsan = {
name: "张三",
sayHi() {
// this 为当前对象
console.log(this);
},
wait() {
setTimeout(funciton(){
// this === window, 因为是setTimeout触发的执行,而不是wait触发的执行
console.log(this);
});
},
};
// 作为对象方法被调用,setTimeout为箭头函数
const zhangsan = {
name: "张三",
sayHi() {
// this 为当前对象
console.log(this);
},
waitAgain() {
setTimeout(() => {
// this 为当前对象,箭头函数的this永远取它上级作用域的this
console.log(this);
});
},
};
// 在class方法中被调用
class People {
constructor(name) {
this.name = name;
this.age = 30; // 这里的this指向创建的实例
}
sayHi() {
console.log(this); // 这里的this指向创建的实例
}
}
const zhangsan = new People("张三");
zhangsan.sayHi(); //zhangsan对象
手写bind
//模拟 bind
Function.prototype.bind1 = function () {
// 将参数拆解为数组
const args = Array.prototype.slice.call(arguments);
// 获取 this(数组第一项),并改变原数组
const t = args.shift();
// Array.prototype.slice.call()方法能够将一个具有length属性的对象转换为数组
// fn1执行,所以this指向 fn1.bind(...) 中的 fn1
const self = this;
return function () {
return self.apply(t, args);
};
};
function fn1(a, b, c) {
console.log(this); // {x: 100}
console.log(a, b, c); // 10 20 30
return "this is fn1";
}
const fn2 = fn1.bind1({ x: 100 }, 10, 20, 30);
const res = fn2();
console.log(res); // this is fn1
// eg.
obj.myFun.call(db,'成都','上海'); // 德玛 年龄 99 来自 成都去往上海
obj.myFun.apply(db,['成都','上海']); // 德玛 年龄 99 来自 成都去往上海
obj.myFun.bind(db,'成都','上海')(); // 德玛 年龄 99 来自 成都去往上海
obj.myFun.bind(db,['成都','上海'])(); // 德玛 年龄 99 来自 成都, 上海去往 undefined
闭包的应用
- 隐藏数据
- 做一个简单的cache工具
// 闭包隐藏数据,只提供API
function createCache() {
const data = {}; //闭包中的数据,被隐藏,不被外界访问
return {
set: function (key, val) {
data[key] = val;
},
get: function (key) {
return data[key];
},
};
}
const c = createCache();
c.set("a", 100);
console.log(c.get("a")); // 100
异步和单线程
同步和异步的区别
- JS是基于单线程语言
- 异步不会阻塞代码执行
- 同步会阻塞代码执行
手写promise加载图片
const url = "https://img.mukewang.com/5ab869bb000180cd05000500-100-100.jpg";
const url2 = "https://img1.sycdn.imooc.com/szimg/5c7c82630820acf806000338-358-201.jpg";
function loadImg(src) {
p = new Promise((resolve, reject) => {
const img = document.createElement("img");
img.onload = () => {
resolve(img);
};
img.onerror = () => {
reject(new Error("加载失败"));
};
img.src = src;
});
return p;
}
loadImg(url)
.then((img) => {
console.log(img.width); // 100
return img;
// 这里返回值由两种形式:
// 1. 如果是普通对象,下面的then的参数接收的参数为该对象
// 2. 如果是promise实例,下面的then的参数接收的是promise返回的promise实例,即resolve或reject
})
.then((img) => {
console.log(img.height); // 100
return loadImg(url2);
})
.then((img2) => {
console.log(img2.width); // 358
})
.catch((ex) => console.log(ex));
异步进阶
event loop(事件循环/事件轮询)
promise进阶
Promise三种状态:
- pending resolved rejected
- pending -> resolved / pending -> rejected
- 变化不可逆
状态表现:
- pending状态,不会触发then和catch
- resolved状态,触发then回调
- rejected状态,触发catch回调
// 另一种写法
const p = Promise.resolve(100);
p.then(res = > {
console.log(res); // 100
}).catch(err => {
console.log(res); // 不会被执行
})
then和catch状态改变
-
then正常返回resolved状态的promise,里面有报错则返回rejected状态的promise
-
catch正常返回resolved状态的promise,里面有报错则返回rejected状态的promise
-
resolved状态的promise会触发后面的then
-
rejected状态的promise会触发后面的catch
// resolved 状态示例
const p1 = Promise.resolve().then((res) => {
return 100;
});
console.log("p1", p1); //resolved触发后续then回调
p1.then(() => {
console.log("123");
});
const p2 = Promise.resolve().then((res) => {
throw new Error("then error");
});
console.log("p2", p2); //rejected触发后续catch回调
p2.then(() => {
console.log("456"); // 不执行
}).catch((err) => {
console.log("err456", err);
});
// rejected 状态示例
const p3 = Promise.reject("my error").catch((err) => {
console.log(err);
});
console.log("p3", p3); // resolved状态
const p4 = Promise.reject("my error").catch((err) => {
throw new Error("catch err");
});
console.log("p4", p4); // rejected状态
例题:
// 第一题
Promise.resolve()
.then(() => {
console.log(1); // 1
})
.catch(() => {
console.log(2);
})
.then(() => {
console.log(3); //3
}); // 最后返回resolved状态的promise
// 第二题
Promise.resolve()
.then(() => {
console.log(1); // 1
throw new Error("error1");
})
.catch(() => {
console.log(2); // 2
})
.then(() => {
console.log(3); // 3
}); // 最后返回resolved状态的promise
// 第三题
Promise.resolve()
.then(() => {
console.log(1); // 1
throw new Error("error1");
})
.catch(() => {
console.log(2); // 2
})
.catch(() => {
console.log(3);
}); // 最后返回resolved状态的promise
async/await
- Promise then catch 链式调用,但也基于回调函数
- saync/await是同步语法,彻底消灭了回调函数
与promise的关系
- 执行async函数,返回的是promise对象
- await相当于promise的then
- try…catch可捕获异常,代替了promise的catch
// 执行async函数,返回的是promise对象
async function fn1() {
return 100;
// return Promise.resolve(100); //这两句等价,如果返回的不是promise对象,会封装成一个promise返回,如果返回的是一个promise对象,直接返回
}
const res1 = fn1(); //执行saync函数,返回的是一个promise对象
console.log(res1); //promise对象
res1.then((data) => {
console.log(data); // 100
});
// await相当于promise的then,(await后的函数为promise函数或普通数)
!(async function () {
const p1 = Promise.resolve(100);
const data = await p1;
const data1 = await 100; // 如果不是promise,会封装成一个promise返回
console.log(data); // 100
console.log(data1); // 100
})();
// await相当于promise的then,(await后的函数为async函数)
async function fn1() {
return Promise.resolve(100);
}
!(async function () {
const p1 = await fn1();
console.log(p1); // 100
})();
// try...catch可捕获异常,代替了promise的catch
!(async function () {
const p1 = Promise.reject("err");
try {
const res = await p1;
console.log(res);
} catch (ex) {
console.log(ex); // err
}
})();
微任务/宏任务(macroTask/microTask)
- 宏任务: setTimeout、setInterval、Ajax、DOM事件
- 微任务:Promise、async/await
- 微任务执行时机比宏任务要早
evnt loop 和DOM渲染
- js是单线程的,而且和DOM渲染共用一个线程
- js执行的时候,得留一些时机供DOM渲染 (alert可以终端js继续执行)
说明:
- 每次callstack清空(即每次轮询结束),即同步任务执行完
- 都是DOM重新渲染的机会,DOM结构如有改变则重新渲染
- 然后再去触发下一次Eventloop
微任务和宏任务的区别:
- 宏任务:DOM渲染后触发,如setTimeout。宏任务是ES6语法规定的,存放在Web APIs里面
- 微任务:DOM渲染前触发,如Promise,微任务是浏览器规定的,存放在micro task queue里面
渲染时机:
- 执行代码,将微任务和宏任务分别放在对应的队列中
- 执行微任务
- 重新渲染DOM
- 执行宏任务
- 触发event loop
- …
例子:
!(async function () {
console.log("start"); //start
const a = await 100;
console.log("a", a); // a 100
const b = await Promise.resolve(200);
console.log("b", b); // b 200
const c = await Promise.reject(300); // reject得用try/catch来,所以后面的都不执行
console.log("c", c);
console.log("end");
})();
例子:
!(async function () {
console.log("start"); //start
const a = await 100;
console.log("a", a); // a 100
const b = await Promise.resolve(200);
console.log("b", b); // b 200
const c = Promise.reject(300); // reject得用try/catch来接,await接不住,所以后面的都不执行
try {
const x = await c;
console.log('x',x);
} catch (error) {
console.log('error',error);
}
console.log("c", c);
console.log("end");
})();
例子:
async function async1() {
console.log("async1 start"); // 2
await async2();
console.log("async1 end"); // 6
}
async function async2() {
console.log("async2"); // 3
}
console.log("script start"); // 1
setTimeout(function () {
console.log("setTimeout"); // 8
}, 0);
async1();
//初始化primose时,传入的函数会被立即执行
new Promise(function (resolve) {
console.log("promise1"); // 4
resolve(); // 写resolve(),then才会被执行
}).then(function () {
console.log("promise2"); // 7
});
console.log("script end"); // 5
JS Web API
DOM的本质:是从html中解析出来的一棵树
attribute和property
这两个都是属性,(都可能引起DOM重新渲染,一般尽可能使用property) :
- property 修改对象属性,不会体现到html结构中
const pList = document.querySelectorAll('p');
const p1 = pList[0];
p1.style.width = '50px';
p1.className = 'red';
- attribute 标签上的属性,会改变html结构
p1.setAttribute('data-name', 'imooc');
console.log(p2.getAttribute('data-name'));
//也能操作标签上的style
p1.setAttribute('style', 'font-size: 50px');
DOM结构操作
const div1 = document.getElementById("div1");
const div2 = document.getElementById("div2");
// 新增节点
const newP = document.createElement("p");
newP.innerHTML = "this is newP.";
div1.appendChild(newP);
// 移动节点(对于现有节点appendChild会将其移动)
const p1 = document.getElementById("p1");
div2.appendChild(p1);
// 获取父元素
console.log(p1.parentNode);
// 获取子元素列表
const div1ChildNode = div1.childNodes;
console.log(div1.childNode);
const div1ChildNodeP = Array.prototype.slice.call(div.childNodes).filter((child) => {
// 过滤出需要的节点类型
if (child.nodeType == 1) {
return true;
}
return false;
});
// 删除
div1.removeChild(div1ChildNodeP[0]);
DOM性能
- DOM操作非常‘昂贵’,避免频繁的DOM操作
- 对DOM查询做缓存
- 将频繁的操作改为一次性操作
正确写法:
const listNode = document.getElementById("list");
// 创建一个文档片段,此时还没有插入到DOM中
const frag = document.createDocumentFragment();
// 插入到文档片段中
for (let x = 0; x < 10; x++) {
const li = document.createElement("li");
li.innerHTML = "list item" + x;
frag.appendChild(li);
}
// 完成之后再插入到DOM中
listNode.appendChild(frag);
BOM
// navigator
const ua = navigator.userAgent;
const isChrome = ua.indexOf("Chrome");
console.log(isChrome);
// screen
console.log(screen.width);
console.log(screen.height);
// location
console.log(location.href); //整个网址
console.log(location.protocol); // 协议:http https
console.log(location.host); // 端口
console.log(location.pathname); // 路径 /classchapter/115.html
console.log(location.search); //查询参数: ?a=100&b=2
console.log(location.hash); //#Anchor
// history
history.back();
history.forward();
JS Web API 事件
事件绑定
写一个通用的事件绑定函数:
// 通用的绑定函数
function bindEvent(ele, type, fn) {
ele.addEventListener(type, fn);
}
const a = document.getElementById("link1");
bindEvent(a, "click", (e) => {
e.preventDefault(); // 阻止默认行为
alert("clicked");
});
事件冒泡
基于DOM树形结构,事件会顺着触发元素向上冒泡。
阻止事件冒泡:
e.stopProgagation()
事件代理
通过冒泡的形式,在父节点拿到子节点的信息并加以处理。优点:简洁、减少浏览器内存占用、不要滥用。
场景如:瀑布流形式滑动加载图片,点击后进入新的页面。
<div id="div3">
<a href="#">a1</a>
<a href="#">a2</a>
<a href="#">a3</a>
<a href="#">a4</a>
<a href="#">a5</a>
<button>加载更多...</button>
</div>
const div3 = document.getElementById("div3");
bindEvent(div3, "click", (e) => {
// a标签有默认行为,会跳转链接
e.preventDefault();
const target = e.target;
if ((target.nodeName = "A")) {
alert(target.innerHTML);
}
});
优化通用的事件绑定函数:
// 通用的绑定函数
function bindEvent(ele, type, selector, fn) {
// 两种绑定方式传入参数数量不同
if (fn == null) {
fn = selector;
selector = null;
}
ele.addEventListener(type, (e) => {
const target = e.target;
if (selector) {
//代理绑定
if (target.matches(selector)) {
fn.call(target, e);
}
} else {
// 普通绑定
fn.call(target, e);
}
});
}
const div1 = document.getElementById("div1");
bindEvent(div1, "click", function (e) {
e.preventDefault();
alert(this.innerHTML);
});
const div3 = document.getElementById("div3");
bindEvent(div3, "click", "a", function (e) {
e.preventDefault();
alert(this.innerHTML);
});
Ajax核心API
手写ajax
最原始的写法:
const xhr = new XMLHttpRequest();
// get请求
xhr.open("GET", "/data.json", true); // true表示异步
// 状态改变时
xhr.onreadystatechange = function () {
if (xhr.readyState === 4) {
if (xhr.status === 200) {
alert(xhr.responseText);
} else {
alert("other cases");
}
}
};
xhr.send(null);
// post请求
xhr.open("POST", "/login", false);
// 状态改变时
xhr.onreadystatechange = function () {
if (xhr.readyState === 4) {
if (xhr.status === 200) {
alert(xhr.responseText);
} else {
alert("other cases");
}
}
};
const postData = {
username: "a",
};
// 必须发送字符串
xhr.send(JSON.stringify(postData));
封装成ajax:
function ajax(url) {
const p = new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
xhr.open("GET", url, true);
xhr.onreadystatechange = function () {
if (xhr.readyState === 4) {
if (xhr.status == 200) {
resolve(JSON.parse(xht.responseText));
} else if (xhr.status === 404) {
PromiseRejectionEvent(new Error("404 not found"));
}
}
};
xhr.send(null);
});
return p;
}
// jQuery中的ajax使用
$.ajax({
url: "http://localhost:8882/x-origin.json",
type: 'POST',
contentType: "application/json;charset=UTF-8",
data: JSON.stringify(list),
success: function (data) {
console.log(data);
},
error: function (data) {
console.log(e.status);
console.log(e.responseText);
}
});
xhr.readyState
- 0 (未初始化)还有没有调用send()方法
- 1 (载入)已调用send方法,正在发送请求
- 2 (载入完成)send方法执行完成,已经接收到全部相应
- 3 (交互)正在解析响应内容
- 4(完成)相应内容解析完成,可以在客户端使用
xhr.status
- 2xx 表示成功处理请求
- 3xx 需要重定向,浏览器直接跳转。如301(永久重定向) 、302(临时重定向) 304(资源未改变)
- 4xx 请求错误。403(没有权限)
- 5xx 服务端报错
跨域
同源策略
- ajax请求时,浏览器要求当前网页必须和server同源(安全)
- 同源:协议、域名、端口,三者必须一致
加载图片 css js可无视同源策略
<img src="跨域的图片地址" />
可用于统计打点,可使用第三方统计服务<link src="跨域的css地址" />
可使用CDN,CDN一般是外域<script src="跨域的js地址" ></script>
可使用CDN,CDN一般是外域。可实现JSONP
JSONP
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<script>
window.abc = function (data) {
console.log(data);
};
</script>
<script src="http://localhost:8002/jsonp.js?username=xxx&callback=abc"></script>
</body>
</html>
// 利用jQuery
$.ajax({
url: "http://localhost:8882/x-origin.json",
dataType: "jsonp",
jsonpCallback: "callback",
success: function (data) {
console.log(data);
},
});
CORS - 服务器设置 http header
服务器设置,允许跨域:
// 第二个参数填写允许跨域的域名称,不建议直接写*
response.setHeader("Access-Control-Allow-Origin", "http://localhost:8011");
response.setHeader("Access-Control-Allow-Headers", "X-Requested-With");
response.setHeader("Access-Control-Allow-Methods", "PUT,POST,GET,DELETE,OPTIONS");
// 接收跨域的cookie
response.setHeader("Access-Control-Allow-Credentials", "true");
Fetch axios
请注意,fetch 规范与 jQuery.ajax() 主要有三种方式的不同:
当接收到一个代表错误的 HTTP 状态码时,从 fetch() 返回的 Promise 不会被标记为 reject, 即使响应的 HTTP 状态码是 404 或 500。相反,它会将 Promise 状态标记为 resolve (但是会将 resolve 的返回值的 ok 属性设置为 false ),仅当网络故障时或请求被阻止时,才会标记为 reject。
axios传送门
支持浏览器和noide.js
支持promise API
JS-Web-API-存储
cookie
是一个追加的过程,不是重新赋值。如果key存在则更新值:
document.cookie = 'a=100'; // 'a=100'
document.cookie = 'b=200'; // 'a=100;b=200'
document.cookie = 'a=300'; // 'a=300;b=200'
刷新页面不会清除cookie,除非手动删除。因为cookie也可以被借用来做本地存储。
缺点:
- 最大存4kb
- 每次请求都会带上,影响请求时长
localStorage 和sessionStorage
- HTML5专门为存储而设计,针对每个域名最大可存5M
- API简单 setItem getItem
- 不会被http发送出去
- localStorage数据会永久存储,除非代码或手动删除
- sessionStorage只存在于当前会话,浏览器关闭则清空
- localStorage用的多一些,(例如下次用户来的时候继续使用)
HTTP
状态码
- 1xx 服务器收到请求
- 2xx 表示成功处理请求
- 3xx 需要重定向,浏览器直接跳转。如301(永久重定向) 、302(临时重定向) 304(资源未改变)
- 4xx 请求错误。403(没有权限)
- 5xx 服务端报错。500(服务器错误)、504(网关超时)
百度搜索引擎、qq邮箱、外链、短链等的链接都是先访问自己的域名下的链接,之后再跳转的。将链接粘出来之后重新打开一个浏览器跳转,会发现请求302了。如果之前通过重定向打开过这个网页,再次访问就有可能是200
http methods
- get 获取数据
- post 新建数据
- patch/put 更新数据
- delete 删除数据
Restful API
- 一种新的API设计方法
- 传统API设计:把每个url当作一个功能。例如:/api/list?pageIndex=2
- Restful API设计:把每个url当作一个唯一的资源的标识。例如:/api/list/2
例如: - post请求,/api/blog
- patch请求,/api/blog/100
- get请求,/api/blog/100
http headers
常见的Request Headers
- Accept 浏览器可接收的数据格式
- Accept-Encoding 浏览器可接收的解压算法,如gzip
- Accept-Language 浏览器可接收的语言,如zh-CN
- Connection:keep-alive 一次TCP连接重复使用
- cookie
- Host
- User-Agent (简称UA)浏览器信息
- Content-type 发送数据格式,如application/json,一般post或者patch需要
常见的Respones Headers
- Content-type 返回数据格式,如application/json
- Content-length 返回数据大小,多少字节
- Content-Encoding 返回数据的压缩算法,如gzip
- Set-Cookie 服务端通过这个属性改cookie
缓存相关的headers
- Cache-Control
- Expires
- Last-Modified
- If-Modified-Since
- Etag
- If-None-Match
可以自定义header,如axios自定义header
header:{'aaa':'aaa'}
http缓存 - 强制缓存
- Cache-Control 控制强制缓存的逻辑,服务端来控制
catch-control:max-age=5184000
[常用]
catch-control:no-catch
[常用] 不用强制缓存,交给服务器处理
catch-control:no-store
不用缓存,服务器处理重新给一份
catch-control:private
只允许最终用户做缓存,例如手机、电脑
catch-control:private
允许中间的代理做一些缓存 - Expires 在response header中,控制缓存过期,现已被catch-control代替。两者都有时以catch-control为准
http缓存 - 协商缓存
- 服务器端缓存策略
- 服务器判断客户端资源,是否和服务器端资源一样
- 一致则返回304,否则返回200和最新资源
资源标识: - 在ResponseHeaders中,有两种
- Last-Modified资源的最后修改时间
- Etag资源的唯一标识(一个字符串 ,类似人类的指纹)
Last-Modified和Etag
- 会优先使用Etag
- Last-Modified只能精确到秒级
- 如果资源被重复生成,而内容不变,则Etag更精准
三种刷新操作:
- 地址栏输入url,跳转链接,前进后退(强制缓存有效,协商缓存有效)
- 手动刷新:F5,点击刷新按钮,右击菜单刷新(强制缓存失效,协商缓存有效)
- 强制刷新:shift+commid+r 或 ctrl+F5(强制缓存失效,协商缓存失效)
开发环境
抓包
-
移动端h5页,查看网络请求,需要抓包工具
-
Windows一般用fiddler
-
Mac OS一般用charles
-
手机和电脑连同一个局域网
-
将手机代理到电脑上
-
手机浏览网页,即可抓包
-
查看网络请求
-
网址代理
-
https
webpack 和 babel
- ES6模块化,浏览器暂不支持
- ES6语法,浏览器并不完全支持
- 压缩代码,整合代码,让网页加载更快
//package.json
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"build": "webpack --config webpack.config.js",//指定webpack打包的文件是webpack.config.js,这个文件是webpack的默认名字,可以不写
"build": "webpack"
},t
npm install html-webpack-plugin
解析html的插件
npm install webpack-dev-server
启动服务的插件
// webpack.config.js
const path = require("path");
const HtmlWebpackPlugin = require("html-webpack-plugin");
module.exports = {
mode: "development", //production模式会压缩代码
entry: path.join(__dirname, "src", "index.js"), //__dirname是当前目录
output: {
filename: "bundle.js",
path: path.join(__dirname, "dist"),
},
plugins: [
new HtmlWebpackPlugin({
template: path.join(__dirname, "src", "index.html"), //根据这个文件模板
filename: "index.html", //产出文件名
}),
],
devServer: {
port: 3000,
contentBase: path.join(__dirname, "dist"), // 当前目录
},
};
运行环境
加载过程
- DNS解析: 域名 -> IP地址
- 浏览器根据IP地址向服务器发起http请求
- 服务器处理http请求,并返回给浏览器
渲染过程:
- 根绝html生成DOM Tree
- 根据CSS代码生成CSSOM(一个css可结构化的对象)
- 将DOM Tree和CSSOM整合成Render Tree
- 根据Render Tree渲染页面
- 遇到
为何建议把css放在header中?
答:先渲染ODM Tree后发现还有css 还要再根据css重新整合成render tree。如果将css放在头部,在dom树生成完成之前就加载完,当dom Trere加载完成之后,跟css一起生成DOM tree
为何建议把js放在body最后?
答:可能会出现渲染过程中页面卡住了,停一会儿之后再加载。
img标签在网页加载比较慢的时候,不会阻塞dom渲染。
window.addEventListener('load', function () {
// 页面的全部资源加载完成才会执行,包括图片、视频
})
window.addEventListener('DOMContentLoaded', function () {
// DOM渲染完即可执行,此时图片、视频可能还没有加载完
// 这种更好
})
性能优化
优化原则
- 多使用内存、缓存或其他方法
- 减少CPU计算量,减少网络加载耗时
-( 适用于所有编程的性能优化 - 空间换时间)
从何入手:
- 让加载更快
- 让渲染更快
让加载更快:
- 减少资源体系,压缩代码
- 减少访问次数:合并代码,SSR服务器端渲染,缓存
- 使用更快的网络:CDN
让渲染更快:
- css放在head,js放在body最下面
- 尽早开始执行js,用DOMContentLoaded触发
- 懒加载(图片懒加载,上拉加载更多)
- 对DOM查询进行缓存
- 频繁DOM操作,合并到一起插入DOM结构
- 节流throttle防抖debounce
缓存:
- 静态资源加hash后缀,根据问价内容计算hash
- 文件内容不变,则hash不变,则url不变
- url和文件不变,则会自动触发http缓存机制,返回304
SSR:
- 服务器端渲染:将网页和数据一起加载,一起渲染
- 非SSR(前后端分离):先加载网页,再加载数据,再渲染数据
- 早先的JSP ASP PHP,现在的vue react SSR
懒加载:
DOM
防抖
const input1 = document.getElementById("input");
function debounce(fn, delay = 500) {
// timer是闭包中的
let timer = null;
return function () {
if (timer) {
clearTimeout(timer);
}
timer = setTimeout(() => {
fn.apply(this, arguments);
timer = null;
}, delay);
};
}
input1.addEventListener(
"keyup",
debounce(function (e) {
console.log(input1.value);
}, 2000)
);
节流
- 拖拽一个元素的时候要拿到该元素的位置
- 直接用drag事件会被频繁触发,容易卡顿
- 节流:无论拖拽速度多快,都每隔固定时间触发一次(100ms)
const div1 = document.getElementById("div1");
function throttle(fn, delay = 100) {
let timer = null;
return function () {
if (timer) {
return;
}
timer = setTimeout(() => {
fn.apply(this, arguments);
timer = null;
}, delay);
};
}
div1.addEventListener(
"drag",
throttle(function (e) {
console.log(e.offsetX, e.offsetY);
}, 100)
);
安全
常见的web安全攻击方式有哪些?
- xss跨站请求攻击:
预防:
- XSRF 攻击
预防: