1.什么是webpack
webpack 可以看作是模块打包机,它做的事情是:分析你的项目结构,找到JavaScript 模块以及其他的一下浏览器不能运行的拓展语言(typeScript,Scss等),并将其打包为合适的格式以供浏览器使用。(webpack 基于node,遵循commonjs规范)
构建就是把源代码转换成发布到线上的可执行JavaScript,css ,html 代码;包括内容如下:
-
代码转换: typescript 编译成JavaScript,scss 编译成css
-
文件优化:压缩JavaScript,css html 代码,压缩合并图片等。
-
代码分割:提取uoge页面的公共代码、提取首屏不需要执行部分的代码让其异步加载
-
模块合并:在采用模块化的项目里会有很多个模块和文件,需要构建功能把模块分类合并成一个文件。
-
代码校验:在代码被提交到仓库前需要校验代码是否符合规范,以及单元测试是否通过。
-
自动发布:更新完成代码后,自动构建出线上发布代码并传输个发布系统。
构建其实是工程化、自动化思想在前端开发中的体现,把一系列流程用代码去实现,让代码自动化的执行这一系列复杂的流程。构建给前端开发人员注入了更大的活力,解放了我们的生成力。
2. webpack.config.js 文件
let path = require('path');
let HtmlWebpackPlugin = require('html-webpack-plugin');
let CleanWebpackPlugin = require('clean-webpack-plugin');
module.exports ={
entry:'', // 入口
output:'', // 出口
devServer:{ // 开发服务器
contentBase:'./build'
},
module:{} , // 模块配置
plugins:[ // 插件配置
//打包html插件,作用:把src目录下的html文件,在打包时生成build 文件下index.html 中导入的 buid.js
new HtmlWebpackPlugin({
template:'./src/index.html', // 文件位置
title:'x', // 文件title
minify:{
removeAttributeQutes:true, // 编译后去掉双引号
collapseWhitespace:true //文件压缩成一行
}
}),
// 用于清空build 文件
new CleanWebpackPlugin(['./build'])
],
mode:'development' , // 可以更改模式
resolve:{}, // 配置解析
}
3.0闭包
3.01 概念
闭包就是能够度去其他函数内部变量的函数,本质上就是:将函数内部和函数外部连接起来的桥梁(或者说定义在一个函数内部的函数!)。
要理解闭包,首先需要理解JavaScript的全局变量和局部变量
JavaScript 语言的特别支持就在于: 函数内部可以直接读取全局变量,但是在函数外部无法读取函数内部的局部变量。
总结: 形成闭包需要满足的条件是返回一个函数且这个函数对局部变量存在引用
3.02 为什么需要闭包
闭包又两个作用: ①可以读取函数内部的变量;②可以让这些变量始终保持在内存中,不会在被调用后自动清除;
总结:局部变量无法共享和长久的保存,而全局变量可能造成变量污染,当我们希望又一种机制既可以长久的保存变量又不会造成造成全局污染的时候,我们就会使用闭包!(这也就印证了为什么滥用闭包会造成内存泄漏的原因了。)
闭包示例:
function f1(){
var x= 10;
function f2(){
x++
console.log(x)
}
return f2
}
var f= f1() // (f2 返回的变量被存储在了全局变量中。)
f() // 运行第一次: 11
f() // 运行第二次: 12
解析: 局部变量,通过闭包导致内存并没有被回收,导致x 的值在不断增加(闭包:为了维护局部变量的状态不被回收!)
非闭包示例:
function f3(){
var a= 10;
a++
return a;
}
var ff =f3 // (虽然也存储于全局变量中,但是每次都重新执行f3,a 变量被重新赋值为10)
ff() // 运行第一次 11
ff() // 运行第二次 11
// 闭包应用:两人心中,定义一个函数记录两人行走的状态
function f4(){
var a= 10;
function f5(){
a++
console.log(a)
}
return f5
}
var a = f4(); // 甲
var b = f4(); // 乙
a() // 执行第一次: 11
a() // 执行第二次: 12
a() // 执行第三次: 13
b() // 执行第一次: 11
b() // 执行第二次: 12
b() // 执行第三次: 13
4.0 浏览器解析全过程(输入URL到页面展现)
-
DNS解析,将域名地址解析为ip地址(从上往下寻找,直到找到为止)
-
浏览器DNS缓存
-
系统DNS缓存
-
路由器DNS缓存
-
网络运营商DNS缓存
-
递归搜索:blog.aa.com(举例)
-
.com 域名下查找DNS解析
-
.aa 域名下查找DNS解析
-
blog 域名下查找DNS 解析
-
再查不到就出错了
-
-
-
TCP连接:TCP三次握手
-
第一次握手:由浏览器发起,告诉服务器,我要发送请求了
-
第二次握手:由服务器发起,告诉浏览器我准本接收了,你赶紧发送吧
-
第三次握手:由浏览器发送,告诉服务器,我马上就发了,准备接受吧
解析:之所以需要发送三次请求是因为:如果浏览器只发送一次请求,但是服务器很忙并没有空去处理你这个请求;浏览器并不知道服务器有没有空给处理直接把数据发送给了服务器;但是服务器并没有准备好去接收(没拿到数据);所以需要第二次握手,告诉浏览器我已经准备好接收; 这时候浏览器也不一定准备好了啊!所以还需要浏览器给服务再次发送请求,和服务器说准备接收吧!(三次握手来保证通讯的准确性!)
-
-
发送请求
-
请求报文:http协议的通讯内容
-
-
接收响应
-
响应报文
-
-
渲染页面
-
遇见HTML标记,浏览器调用HTML解析器解析成Token并构建dom树
-
遇见style/link 标记,浏览器调用css解析器,处理css标记并构建cssom树
-
遇见script标记,调用javasScript解析器,处理script代码(事件绑定,修改dom树/cssom树)
-
将dom树和cssom树合并成一个渲染树
-
根据渲染树来计算布局,计算每个节点的几何信息(布局,每个盒子显示在页面的某个地方)
-
将各个节点颜色绘制到屏幕上(渲染)
注意:以上这五个步骤不一样按照顺序执行,如果dom树或cssom 树被修改了,可能会执行多次布局和渲染;往往实际页面中,这些步骤都会执行多次的!
-
-
断开连接:TCP四次挥手
-
第一次挥手:由浏览器发起,发送给服务器,我东西发完了(请求报文),你准备关闭吧
-
第二次挥手:由服务器发起,告诉浏览器,我接收完了(请求报文),我准备关闭了,你也准备吧。
-
第三次挥手:由服务器发起,告诉浏览器,我东西都发完了(响应报文),你准备关闭吧。
-
第四次握手:由浏览器发起,告诉服务器,我都系都接收完了(响应报文),我准备关闭了,你也准备吧。
注:一般服务器先关闭,然后再浏览器关闭
-
5.0原型链
5.1作用域链
window; // 全局作用域对象
function test(){
var num = 10
function mc(){
console.log(num) // 10
}
mc(); // 作用域对象.mc()
}
test(); // window.test()
// 每个方法都是一个作用域,最外层的是全局作用域;
// 引用方法都是:对象.方法()
// 每一个方法都是有对象的: alert() --> window.alert()
关于以上代码console.log(num)打印解析:
-
如果mc 方法里面申明:var num= 10; 则 打印: 10
-
如果test方法里面申明:var num= 100,且mc 方法里面没有申明var; 则 打印: 100
-
如果申明全局变量: var num = 1000 ,且test 和 mc 方法里面没有申明var; 则打印:1000
总结:每一个方法里面都是一个作用域,其都有自身的作用域对象;
-
当你在使用某个变量的时候,它会先在自己的作用域中去寻找这个变量,如果当前作用域没有这个变量,它就会向上查找(其父级作用域);最终查找到全局作用;这样子形成的链条就5是:作用域链
5.2 原型链的继承机制
原型链: 首先在自身找,如果没找到就沿着自己的原型链条(对象.--proto--)找到其原型: prototype
class Student {
constructor(name,score){
this.name = name;
this.score = score;
}
introduce(){
console.log(`我是${this.name},考了$(this.score)分。`)
}
}
//创建对象
const student = new Student('张三',99)
// 调用子类函数
student.introdece() // 我是张三,考了 99 分
原型: 注: Student.prototype === student.__proto__
student.hasOwnProperty(name) // 通过hasOwnProperty这个属性来判断name是否是属于teacher本身的属性。
6.0 函数节流& 函数防抖
6.0.1 函数节流
感念:一个函数执行一次后,只有大于设定的执行周期后才会执行第二次(有个需要频繁处罚函数,储于优化性能角度,在规定时间内,只让函数触发的第一次生效,后面不生效)
--应用示例:(鼠标滚动触发事件)
<style>
heml,body{
height:200%;
}
</style>
// 节流函数
// fn 要被节流的函数
// delay 规定的时间
function throttle(fn,delay){
var lastTime = 0;
// 通过闭包来保存lastTime的状态
return function(){
// 记录当前函数触发的时间
var nowTime = Date.now();
if(nowTime- lastTime > delay){
fn()
// 好的代码: fn.call(this) --让当前this的指向修正到fn
// 同步时间
lastTime = nowTime;
}
}
}
// 绑定鼠标滚动事件,触发函数
document.onscroll = throttle(function(){
console.log('函数被触发了')
},2000)
6.0.2 防抖函数
概念: 一个需要频繁触发的函数,在规定时间内,只让最后一次生效,前面的不生效;
// 节流函数
// fn 要被防抖的函数
// delay 规定的时间
<button id="btn"></button>
function debounce(fn,delay){
// 记录上一次的延时器
return function(){
// 清除上一次延时器
clearTimeout(timer);
timer = setTimeout(function(){
fn.apply(this)
},delay)
}
}
document.getElementById('btn').onclick = debounce(function(){
console.log("点击事件被触发了 !")
},1000);
7.0 深浅拷贝
7.0.1浅拷贝
概念: 可以将对象最外层属性全部复制,里层属性仍然是引用关系
var obj = {a:1,b:2,c:{d:10,e:20}}
var obj1 = {}
Object.assign(obj1,obj)
obj.a = 2;
obj.c.d = 100
console.log(obj1) // {a:1,b:2,c:{d:100,e:20}}
// 可以看出a不变, 但是d 的值被 obj 改变了!
7.0.2 深拷贝
举例
// 通过以上的方法可以实现深考贝
obj1 = JSON.parse(JSON.stringify(obj)) // 原理是:把对象转换成字符串,然后再转换成对象;
obj.c.d = 100
console.log(obj1) // {a:1,b:2,c:{d:10,e:20}}
// 但是这种方法是有弊端的(无法实现深拷贝)
//①: 在obj 对象种加入 set get 方法,会导致方法被运算
//②: 对obj 对象通过 Object.defineProperty() 添加属性时无法被拷贝
//例:
Object.defineProperty(obj,"h",{
value :30
})
obj1 = JSON.parse(JSON.stringify(obj))
console.log(obj) // {a:1,b:2,c:{d:10,e:20},h:30}
console.log(obj1) // {a:1,b:2,c:{d:10,e:20}}
-
JSON.parse(JSON.stringify ( ) )
-
自己写一个深拷贝的方法,然后引用。
-
lodash 里面的_.cloneDeep() 方法。
8 Promise
8.1 promise().then()
// 创建一个Promise 函数
function getImage(src) {
return new Promise (function(res,rej){
let img = new = image()
img.onload =function(){
res(img)
};
img.onerror = function(){
rej('这是错误信息')
}
img.src = src;
})
}
// .then() 调用该函数
getImage('img/a.jpg').then(function(img){
console.log(img) // 成功回调
},function(){
console.log(error) // 失败回调: 这是错误信息
})
如果要执行多张图片的加载
getImage('img/b.jpg').then(function(img){
console.log(img) // 成功回调
},function(){
console.log(error) // 失败回调: 这是错误信息
})
上面的这个函数相当于:
getImage('img/b.jpg').then(function(img){
console.log(img) // 成功回调
}).catch(err=>{console.log(err //失败回调)})
// 这样子就会打印两次
// <img src= 'img/a.jpg'>
//<img src= 'img/b.jpg'>
// 但是以上这么写,代码过于冗余,且不利于解读;优化到以下写法
getImage('img/a.jpg').then(function(img){
console.log(img) // 成功回调
return getImg('img/b.jpg')
}).then(res => console.log(res))
.catch(res=> console.log(res))
// <img src= 'img/a.jpg'>
//<img src= 'img/b.jpg'>
8.2 promise.all()
all 并列执行!是几个异步全部完成后,执行的结果!结果是统一返回第一个参数的数组。
// 创建一个Promise 函数
function getImage(src) {
return new Promise (function(res,rej){
let img = new = image()
img.onload =function(){
res(src) //这边将在下面放回
};
img.onerror = function(){
rej('这是错误信息')
}
img.src = src;
})
}
Promise.all([getImage('img/a.jpg'),getImage('img/b.jpg'),getImage('img/c.jpg')])
.then(function(list){ // 这个list 数组就promise 成功回调返回的函数
console.log(list) //[img/a.jpg,img/b.jpg,img/c.jpg]
})
8.3 promise.race();
race() 并列执行,谁先执行完就输出谁,别的就不管了!
Promise.all([getImage('img/a.jpg'),getImage('img/b.jpg'),getImage('img/c.jpg')])
.then(function(list){ // 这个list 数组就promise 成功回调返回的函数
console.log(list) // img/a.jpg
})
8.4 async await 使用
异步转同步;这样子就可以不用 使用promise().then() 函数了,看起来比原来更加简洁!
-
函数使用之前必须有async , await 后面必须是一个 Promise 函数
-
await 后面必须是一个 Promise 实例
function loadImg(src){
var promise = new Promise((res,rej)=>{
resolve(img)
})
return promise
}
const load = async function(){
const result1 = await loadImg(src1)
const result2 = await loadImg(src2)
}
load();