一、变量提升(函数里面先形参赋值,再变量提升,最后执行代码)
1、概念:当浏览器开辟出供代码执行的栈内存后,代码并没有自上而下立即执行,而是继续做了一些事情,把当前作用域所有带var和function关键字进行提前的声明和定义,这个阶段就叫做变量提升。
2、变量提升机制:(函数优先级高于变量优先级)
- 带var的只提前申明,"var a"如果没有赋值,默认值是undefined;
- 带function不尽提前声明,而且还定义了,定义其实就是赋值,准确的来说就是让某个变量和某个值产生关联。
- 自执行函数不进行变量提升(没名字)
- 条件判断里面,不管条件是否成立,都会先变量提升
var a=1;
function fn(){
if(!a){ //a会先进行变量提升=》undefined;再取反!undefined=>true,执行条件判断里面内容
var a=10
}
console.log(a)
}
fn()//10
```js
let a=1;
function fn(){
if(!a){ //函数内部a不会进行变量提升,a=1;再进行取反!1=>false,不执行条件判断({}里面let/const会形成块级作用域)
let a =10
}
console.log(a)
}
fn()//1
```js
let a=1;
function fn(){
if(a){ //函数内部a不会进行变量提升,a=1执行条件判断;({}里面let/const会形成块级作用域)
let a=10
}
console.log(a) //取全局的a,
}
fn()//1
二、全局变量和私有变量
1、全局变量:在全局作用域下申明的变量
2、私有变量:在私有作用域下申明的变量是私有变量,创建的6种方式(var/let/const/function/class/import)和形参都是私有变量;
3、私有变量不受全局变量的影响,引用类型的形参除外。
三、带var和不带var的区别
/**
*在全局作用域下的区别
*不带var的相当于给全局对象window设置了一个属性a
* window.a=13
*/
a=13;
console.log(a)//window.a=13
/**
*带var的:相当于在全局作用域下面申明了一个变量b(全局变量),但是在全局下面申明的变量也同样相当于给window添加了一个属性b(只有全局作用域具备这个特点)
*/
var b=14; //创建了变量b,给全window设置了属性b
console.log(b) //14
console.log(window.b) //14
四、let /const和var的区别
1. let和const不存在变量提升
创建变量的6中方式,var和function有变量提升,而let、const、class、import都不存在这个机制
2. var允许在相同的作用域下重复声明,而let不允许
- 如果使用var和function关键字申明变量后再次申明,是不会有影响的(申明第一次之后,再次遇到就不会再申明了)
- 但是let和const就不行,浏览器会检验当前域会否已经存在这个变量了,如果已经存在,再次基于let/const声明就会报错。
/**
*在浏览器开辟栈内存代码自上而下执行之前,不仅有变量提升的操作,还有其他很多操作,=》‘词法解析和词法检测’,就是检测当前要执行的代码是否有“语法错误-SyntaxError”,如果出现错误,整个代码都不会执行。
*/
console.log(1);
let a=2;
console.log(a);
let a=3; //Uncaught SyntaxError: Identifier 'a' has already been declared
console.log(a);
/**
*所谓重复申明,不管之前通过申明方法,只要当前栈内存在这个变量,而我们再使用let和const申明,就会出现这个语法错误
*/
console.log(1);
let a=2;
console.log(a);
var a=3; Uncaught SyntaxError: Identifier 'a' has already been declared
console.log(a);
3、
解决了js中暂时性死区问题
console.log(a); //Uncaught ReferenceError: a is not defined
console.log(typeof a);//undefined,这是浏览器的BUG,本身应该报错没有a,(暂时性死区)
//es6修复了暂时性死区的bug
console.log(typeof a);//Uncaught ReferenceError: Cannot access 'a' before initialization
let a
4、let/const所在的{}(for循环、条件判断、函数等{}都可以形成块级作用域)可以形成块级作用域,var/function不可以
5、let创建的全局变量没有给window设置对应属性
五、let和const区别
Es6新增的用来创建变量和常量的;
区别:const创建的简单类型,不能修改;复杂类型:不能修改引用
六、JavaScript 中的作用域(栈内存)和作用域链
1、作用域链查找机制:关键在于如何查找上级作用域;
- 从函数开始创建作用域就已经确定好了;
- 当前这个函数是在那个作用域下(N)创建的,那么函数执行的作用域(M)的上级就是N(和函数在哪执行的没有关系,只和在哪创建的有关系)
七、闭包作用域
1、创建函数
3. 开辟一个堆内存
4. 把函数体中的代码当作字符串存储进去
5. 把堆内存的地址赋值给函数名或者变量
6. 函数在哪创建,那么他执行时所需要查找的上级作用域就是谁
2、函数的执行
7. 形成一个全新的私有作用域、执行上下文、私有栈内存(执行一次形成一个,多个之间也不会产生影响)
8. 行参赋值 & 变量提升
9. 代码执行(把所有堆内存中的字符串代码,拿出来一行行执行)
10. 遇到一个变量,首先看他是否为私有变量(行参和在私有作用域中声明的变量是私有变量),是私有变量就操作自己的变量即可,不是自己的向上级作用域查找,一直找到全局为止=》作用域链查找机制
11. 关于堆内存释放问题(以google为例)
函数执行之后就行形成栈内存(从内存汇总分配的一块空间),如果内存都不销毁释放,很容易就会导致栈内存溢出(内存爆满,电脑就卡死了),堆栈内存的释放问题是学习js的核心问题之一。
12. 堆内存释放问题
// 创建一个引用值,就会产生一个堆内存
// 如果当前创建的不被其他东西占用了,(浏览器会在空闲的时候,查找每一个内存的引用情况,不被占用的都会被回收释放掉),则会释放
let obj={
nameL:'xhm'
}
let obj1=obj;
//此时obj和obj1都占用着对象的堆内存,想要释放内存,需要手动清除变量和值的关联(null:空对象指针)
obj=null;
obj1=null;
- 栈内存释放
//栈内存的形成
// 1、打开浏览器形成的全局作用域是栈内存
// 2、手动执行函数形成的私有作用域是栈内存
// 3、 基于es6的let/const形成的块作用域也是栈内存
// 4、...
/**
14. 栈内存的释放时机
15. 1、 全局栈内存:关掉页面的时候才会销毁
16. 2、私有栈内存:
17. a.一般情况下,函数执行完成,形成的私有栈内存就会销毁(排除递归和死循环模式)
18. b.但是栈内存中的某个东西(一般是堆地址)被私有作用域以外的事物给占用了,则当前栈内存不会被立即释放掉(特点:私有作用域的私有变量也被保存下来了)=》市面上认为的闭包:函数形成不能被释放的私有内存,这才是闭包
*/
function fn(){
}
fn() //函数执行形成栈内存,执行完占内存销毁
function X(){
return function(){}
}
let f=X() //f占用了X执行所形成的一个东西(函数对应的堆内存),则X执行形成的栈内存不会被销毁。
f=null //可以释放私有栈内存
闭包的作用
- 保护(私有变量和外界没有必然联系)
jquery前端非常经典的类库:提供了大量的方法供开发人员使用;=》为了防止全局污染(和自定义变量重名),jq中的方法和变量需要通过闭包保护起来 - 保存(形成不能销毁的内存,里面的私有变量等信息被保存下来了)
选项卡保存index
- 自定义变量
- 闭包
- let,简历私有作用域
八、js闭包(参考链接)
- 概念:闭包就是能够读取其他函数内部变量的函数。只有函数内部的子函数才能读取局部变量,在本质上,闭包是函数内部和函数外部连接起来的桥梁。(函数执行形成的私有内存,会把内存中所有的哪有变量保护起来,和外面没有任何关系=》函数执行的这种保护机制就叫“闭包”)
var a=b=10;//相当于var a=10;b=10;a是fn函数私有变量,b是全局变量
- 作用
- 可以读取函数内部的变量
- 让这些变量的值始终保持在内存中,不随着它的上下文环境一起销毁
- 使用闭包的注意点
- 由于闭包会使得函数中的变量都被保存在内存中,内存消耗很大,所以不能滥用闭包,否则会造成网页的性能问题,在IE中可能导致内存泄露。解决方法是,在退出函数之前,将不使用的局部变量全部删除。
- 闭包会在父函数外部,改变父函数内部变量的值。所以,如果你把父函数当作对象(object)使用,把闭包当作它的公用方法(Public Method),把内部变量当作它的私有属性(private value),这时一定要小心,不要随便改变父函数内部变量的值。
- 🌰
function foo(x) {
var tmp = 3;
return function (y) {
alert(x + y + tmp);
x.memb = x.memb ? x.memb + 1 : 1;
alert(x.memb);
}
}
var age = new Number(2);
var bar = foo(age); // bar 现在是一个引用了age的闭包
bar(10);
- 闭包的使用场景
九、this指向
函数执行的主体(不是上下文),意思是谁把函数执行的,那么执行主体就是谁;
1、给元素的某个事件绑定方法,当事件触发方法执行的时候,方法中的this是当前操作元素的本身;
2、如何确定执行主体(this)是谁,当我们执行的时候看看方法前面是否有点,没有点this是window或者undefined;有点,点前面是谁this就是谁
var name='kk'
function fn(){
console.log(this)
}
var obj={
name:'xhm',
fn:fn
}
obj.fn();//this=>obj;xhm
fn();//this=>window(非严格模式下,严格模式下是undefined);kk
//相当于window.fn()
(function(){
//自执行函数,里面的this是window或者undefined
})()
思考
let obj={
fn:(function(){
//this=>自执行函数都是window/undefined
return function(){
//this=>obj
}
})()
}
obj.fn()
3、在构造函数模式执行中,函数体中的this是当前类的实例;
4、可以通过call/apply/bind修改this
5、箭头函数没有this,它里面用到的this,都是自己所处上下文的this
十、严格模式和非严格模式
1、如何确定执行主体(this)是谁,当我们执行的时候看看方法前面是否有点,没有点this是window或者undefined(严格模式下是window,非严格模式下是undefined);有点,点前面是谁this就是谁;
2、非严格模式下可以修改形参;严格模式下argements和形参的映射机制就断了,无法修改
function fn(a,b,c){
argements[2]=10
console.log(c) //非严格模式下=》10,严格模式下argements和形参的映射机制就断了=>3
}
fn(1,2,3)
十一、箭头函数
1、与普通函数的区别
-
箭头函数比普通函数更简洁
-
箭头函数没有argements,但是可以基于剩余运算符获取实参,二期es6是支持给形参设置默认值的
let obj={};
let fn=(a=1,...arg){
//argements 会报错
//arg是剩余参数
}
fn(1,2,3,4)
fn()
- 箭头函数没有自己的this,它里面用到的this,
都是自己所处上下文的this
,使用call/aaply/bind可以修改this
案例1:
function fn(){
console.log(this);
}
document.body.onclick=()=>{
console.log(this); //window
}
document.body.onclick=fn //window
案例2
var obj={
name:'xhm',
fn:()=>{
console.log(this)
},
fn2:function(){
const f=(){
console.log(this)
}
f()
return f
},
}
obj.fn() //this
var f=obj.fn2() //obj
f() // obj
案例3:
var obj={
name:'xhm',
fn:function(){
//需求:1s后把obj.name改为kk
//第一种无法修改
setTimeout(function(){
this.name='kk'
console.log(this) //window
},1000)
//第二种提前保存this,可以修改
let that=this;
setTimeout(function(){
that.name='kk'
console.log(that) //window
},1000)
//第三种。使用箭头函数,可以修改
setTimeout(()=>{
that.name='kk'
console.log(that) //window
},1000)
},
}
十二、js同步和异步编程
1、浏览器渲染流程
- 浏览器是多线程的,但是浏览器只分配一个线程从上到下执行,js/html/css是单线程的
- 因为是单线程,所以代码执行一般是同步的
- 资源文件加载的异步执行流程:当浏览器自上而下执行代码,遇到异步任务link/script/img等,浏览器会帮我们开辟其他线程去执行,主线程辉继续向下执行代码
- 第一次页面从头到尾渲染完后,发现只渲染完了dom,生成了dom tree
- 当其他异步资源加载完成后,主线程开始按照谁先加载回来顺序一个个渲染。生成一个CSSOM。
- CSSOM+DOM Tree结合到一起生成Render Tree;
- 浏览器按照这个Render Tree开始渲染呈现
2、异步任务有哪些
- 定时器
- 事件绑定也是异步
- ajax请求
- 回调函数可以理解为异步
- Promise/async/await
- node也提供其余的异步编程方式
3、什么是事件循环
js主线程自上而下执行的时候,代码进栈执行,执行完就出栈,当遇到异步编程代码会把当前这个任务放到任务队中(event queue),存起来。主线程辉继续执行,当主线程全部代码执行完成,空闲下来,再去任务队列里面找,那个异步任务到达指定的完成时间,就拿到主线程中执行(这个任务没有执行完,其他任务也不能执行)。执行完成后在去任务队列里面拿,…,反反复复的过程就是事件循环
十三、js事件阻止
1、阻止a标签默认行文
- href=‘javascript:;’
- 点击a标签先触发click行为,然后再去执行href的跳转
link.onclick=()=>{
return false;//返回一个false,相当于结束后面即将执行的操纵
}
link.onclick=(ev)=>{
ev.preventDefault()
}
2、浏览器鼠标右键阻止操作弹框出现
window.addEventListener('contextmenu',(ev)=>{
ev.preventDefault()
//可以处理自己想操作的逻辑
})
3、阻止输入框的默认行为
js事件传播
1、事件传播机制
-
捕获阶段 从最外层向最里层事件原依次进行查找(目的:是为冒泡阶段事先计算好传播的层级路径)
-
目标阶段:当前元素的相关事件元素触发
-
冒泡传播:触发当前元素的某一个事件行为,不仅他的这个行为被触发了,而且他所有的祖先元素(一直到window)相关事件都会被依次触发(从内到外)
- 阻止冒泡
dom.onclick=(ev)=>{ ev.stopPropagation() }
🌰:
<div id="outer">outer
<div id="inner">inner
<div id="center">center</div>
</div>
</div>
document.body.onclick=()=>{
console.log('body');
}
outer.onclick=()=>{
console.log('outer');
}
inner.onclick=()=>{
console.log('inner');
}
center.onclick=()=>{
console.log('center');
}
//center、inne、outer、body
document.body.onclick=()=>{
console.log('body');
}
outer.addEventListener('click',()=>{
console.log('outer');
},true) //true代表在捕获阶段
inner.onclick=()=>{
console.log('inner');
}
center.onclick=()=>{
//outer center inner body
十四、 DOM重绘和回流(重排)
- 重绘:元素的样式改变(但宽高、大小、位置不变)
- 如:outline/visibility/color/ankground-color等
- 回流:元素的大小或者位置发生变化(当页面布局和几何信心发生变化时),触发了重新布局,导致渲染树重新计算布局和渲染
- 如:浏览器窗口尺寸发生变化
- 如:页面一开始渲染的时候
- 如:添加删除可见的DOM元素
- 如:元素的尺寸发生变化
- 如:内容发生变化(比如文本变化或者图片被另一个不同尺寸的图片替换)
回流一定会发生重绘,重绘不一定发生回流
十五、客户端和服务端/用户在地址栏输入url,发生了什么? //TODO
- 客户端:可以向服务端发送请求,并接受返回的内容进行处理
- 服务端:能接受客户端发送的请求,并且把相关的资源信息返回给客户端
十六、URL/URI/URN区别
十七、一个完整的url所包含的内容
http://www.jd.com:80/plus/index.html?from=wx&lx=1#home
- 协议(http://)
- http
- https
- ftp:文件传输协议(一般用于将本地资源上传到服务器端)
- 域名(www.jd.com):一个用户方便记住的名字(不通过服务器域名,直接用服务器的外网ip也能访问,但是外网ip很难记住)
- 顶级域名:jd.com
- 一级域名:www.jd.com
- 二级域名:plus.jd.com
- 三级域名:m.plus.jd.com
域名类型
- .com 国际域名
- .cn中国域名
- .com.cn
- .edu教育
- .gov政府
- .io博客
- .org官网阻止
- .net系统类
- 端口(:80):端口号的取值范围:0-65535,用端口号来区分同一服务器的不同项目
- http默认端口号:80
- https默认端口号:443
- ftp默认端口号:21
如果项目采用默认端口号,我们在书写地址的时候不用添加,浏览器在发送请求的时候默认会给我们加上
- 请求资源名称(/plus/index.html)
- 问号传参信息(?from=wx&lx=1)
- 客户端想把信息传递给服务端,有很多种方式
- url地址问号传惨
- 请求报文传输(请求头和请求主体)
- 也可以在不同页面之间的信息传递,例如:从列表跳转到详情
- hash值(#home)
- 也能充当信息传输的方式
- 锚点定位
- 基于hash实现路由管控(不同的hash展示不同的组件和模块)
url的编码和解码
十八、建立tcp链接(3次握手)/四次挥手
四次挥手
性能优化之一:Connection:keep-alive(怎么保持tcp协连接不中断)
十九、 HTTP请求
1、HTTP请求方式
get和post本质却别如下:
- get传递给服务器的参数一般是采用:问号传惨;
- post传递给服务器的参数一般是采用:设置请求主体
1、get传递给服务器的内容有限制,因为url右最长大小限制(chrome浏览器一般是4-8k,超出长度部分自动被浏览器截取了)
2、get请求会产生缓存(缓存是不可控的);因为请求的地址浏览器会认为你要和上次的数据一样,返回的是上一次请求的数据;解决方案:在后面加一个时间戳等随机数
3、post相对get来说更安全,get是基于问号传惨传递给服务器;而post基于请求主体传递信息,不容易被劫持
2、http状态码:1-5开头,三位数字
-
2~:表示成功
- 200:成功
- 201:一般用于告诉服务器创建一个新文件,最后服务器创建成功返回的状态码
- 204:对于某些请求(例如PUT或者DELETE),服务器不想处理,返回空内容,并且用204状态码告知
-
3~:成功(周转成功)
-
301:永久性重定向(永久转移);一般用于服务器迁移、域名迁移
-
302:临时转移,很早以前基本用307来做,但是现在主要是用307来做
-
304 Not Modified:设置http协商缓存
-
307:临时重定向,主要用于负载均衡(大促的时候,服务器忙不过来临时转移到其他服务器,等不忙的时候就自己处理啊)等
-
-
4~:失败
- 400:传递给服务器的参数错误
- 401:无权限访问
- 404:请求地址错误
-
5~
- 500:未知服务器错误(服务器出错了)
- 503:服务器超负荷(服务器最多承受100多个人访问,第101个人访问就会返回503)
二十前端性能优化:
1、避免dom的回流
- 放弃传统的操作dom时代,基于react/vue开始数据影响实图模式
- dom操作读写分离-现代浏览器都有渲染队列机制(读写样式和修改样式不要交叉写,尽量批量操作和读取)
//=》现代版的浏览器都有“渲染队列”机制,发现某一行要修改的元素的样式,不是立即渲染,而是看看下一行,如果下一行也是修改元素样式,则会把修改样式的操作放到“渲染队列”中...,依次到不是修改元素样式的操作,整理渲染依次,引发一次回流
box.style.width='20px'
box.style.height='20px'
box.style.background='red'
box.style.margin='20px auto'
//整体发生一次回流
box.style.width='20px'
console.log(box.offsetTop)
box.style.height='20px'
console.log(box.offsetBottom)
box.style.background='red'
box.style.margin='20px auto'
//整体发生三次回流
- 样式集中改变(不要一次改变一个样式)
- 缓存布局信息-先把要设置的样式用变量保存起来,再设置到dom上
- 元素批量修改
- 文档碎片:createDocumentFragment
- 模版字符串拼接
- 动画效果应用到position,脱离文档流
2、减少HTTP请求次数和传输报文的大小
1、CSS SPRITE(雪碧图/图片精灵)技术
2、 使用字体图标、SV、webp(需要服务端支持)等
3、 图片懒加载
4、音视屏文件取消预加载(preload=‘none’),这样可以增加第一次页面渲染的速度,当需要播放的时候再加载
5、客户端和服务端 数据传输尽可能使用JSON完成(XML格式比JSON格式要大一些)
6、把页面CSS/JS合并压缩,最好JS和CSS只导入一个文件
7、图片地图,对于多次使用的图片(背景图),尽可能提取成公共样式,而不是每次都设置background;
8、建立connction:keep-alive TCP长连接
9、建立Catch control和Express HTTP的强缓存
10、图片使用base64,减少http请求,增加浏览器渲染速度
3、代码方面的性能优化
1、减少对闭包的使用(因为过多使用闭包会产生很多不销毁的内存,处理不好会导致内存溢出-“栈溢出”)
- 死递归
- 对象相互引用
2、对于动画来说,能用css不要用js(能用transform,不用传统css样式),能用requestAnimationFrame不用定时器;还有动画元素使用定位脱离文档流
3、避免使用iframe,因为iframe会嵌入其他页面,这样父页面渲染时还要渲染子页面。肯定会慢
4、减少对dom元素的操作(减少dom回流和重绘)
5、低耦合高内聚(基于封装(函数、插件、组件、框架、类库等)的方式,减少页面代码冗余,提高代码使用率)
6、尽可能使用事件委托
7、项目中尽量使用异步编程来模拟出多线程的效果,避免主线程堵塞(异步基于Promise)
8、减少选择器、样式的层级()选择器是从右往左解析
9、尽可能减少TABLE布局
4、设置各种缓存、预处理、长链接机制
1、 把不经常更改的资源做缓存处理(一般做的事304或者ETAG等协商缓存)
2、DNS缓存或者预处理,减少DSN查找
3、设置本地离线缓存,或者把一些不经常更改的数据做本地缓存(webStorage)等
4、 有钱就做cdn(地域分布式服务器)和加服务器
5、手机回收堆栈内存(赋值为null)
6、使用HTTP2协议
二十一 js中数据类型检测
1、typeof;
2、 instanceof:本意是用来检测实例是否属于某个类,我们基于这种方式也可以用来检测某些数据的类型,例如:数组、正则等
局限性:
- 不能处理基本类型
- 只有在当前实例的原型链(proto)上出现过,检测结果都是true(用户可能会手动修改原型链的指向或者在类的继承中的情况都不准确)
js [] instanceof Object //true [] instanceof Array //true
3、contructor:构造函数
在类的原型上一般都有constructor属性,存储当前类本身,我们也是利用这一点,获取某实例的contructor属性,验证是否为所属类
4、Object.prototype.toString.call()
- 原理:调用Object原型上的toSting方法,让方法执行的时候,方法中的this就要要检测的数据类型,从而获得数据类型所属类的详细信息;
- 返回值:“[object 所属类]”,例如:“[object Array]”![
二十二:js事件循环
二十三浏览器缓存
1、HTTP缓存(参考)
浏览器缓存是浏览器在本地磁盘对用户最近请求过的文档进行存储,当访问者再次访问同一页面时,浏览器就可以直接从本地磁盘加载文档。 浏览器缓存主要分为强缓存(也称本地缓存)和协商缓存(也称弱缓存)。
- 使用缓存有下面的优点:
- 减少冗余的数据传输
- 减少服务器负担
- 加快客户端加载网页的速度
2、浏览器缓存
有本地小容量存储与本地大容量存储
小容量的Cookie:
一般用于以下场景:
记住用户名密码
欢迎语
小容量的LocalStorage:
记录后只要不手动清除就会一直存在。
小容量的SessionStorage:
仅在本次会话时有效。关闭当然窗口后就没有了。
- 比较一下Cookie、LocalStorage、sessionStorage的异同:
- cookie会在在同源的http请求中携带,不能超过4K,参与和服务器交互;
- sessionStorge和locaStorage保存数据在本地,限制最多5M,不参与和服务器交互;
- sessionStorage仅在当前窗口关闭前有效;
- localstorage会一直保存在浏览器中,除非手动删除;
- cookie在设置过期时间之前一直有效,不设置过期时间窗口关闭则清除;
- sessionStorage不在不同的浏览器窗口中共享,即使是同一个页面,
- localstorage和cookie在所有同源窗口中共享
3、应用程序缓存-分为应用缓存和PWA
浏览器两个窗口通信
1 、localStorage
一个窗口更新localStorage,另一个窗口监听window对象的”storage”事件,来实现通信。
注:两个页面要同源(URL的协议、域名和端口相同)
// 本窗口的设值代码 localStorage.setItem('aaa', (Math.random()*10).toString()) // 其他窗口监听storage事件 window.addEventListener("storage", function (e) { console.log(e) console.log(e.newValue) })
2、WebSocket
所有的WebSocket都监听同一个服务器地址,利用send发送消息,利用onmessage获取消息的变化,不仅能窗口,还能跨浏览器,兼容性***,只是需要消耗点服务器资源。
3、postMessage
JavaScript 中的垃圾回收 //TODO
19-1=18。i=19
20-2=18。i=20
19-3=16 i=19
18-4=14 i=18
19-5=14. i=19
19