至2021-08-11
- object是引用数据类型,且只存储于堆(heap)中 ( X )
解析:基本类型都存储在栈中,引用数据类型的引用存储在栈中,引用对象的内容存储在堆中
- 函数式声明有提升现象,函数表达式不会提升
解析:
1 | // 代码片段1,实际执行顺序:先声明函数,再调用 |
- delete运算符只能删除自由属性,不能删除继承属性。
1 | const Book = { |
堆分为大顶堆和小顶堆,
满足a[i]>=a[2i+1]&&a[i]>=a[2i+2]称为大顶堆,
满足 a[i]<=a[2i+1]&&a[i]<=a[2i+2]称为小顶堆
客户端渲染:浏览器中输出 Vue 组件,进行生成 DOM 和操作 DOM。
服务器端渲染: 将组件渲染为服务器端的 HTML 字符串,将它们直接发送到浏览器,最后将这些静态标记”激活”为客户端上完全可交互的应用程序。
服务器端渲染优点:
1.更好的 SEO,由于搜索引擎爬虫抓取工具可以直接查看完全渲染的页面。
2.更快的内容到达时间,特别是对于缓慢的网络情况或运行缓慢的设备。无需等待所有的 JavaScript 都完成下载并执行,才显示服务器渲染的标记,所以你的用户将会更快速地看到完整渲染的页面。
缺点:
1.开发条件所限。浏览器特定的代码,只能在某些生命周期钩子函数中使用;一些外部扩展库可能需要特殊处理,才能在服务器渲染应用程序中运行。
2.涉及构建设置和部署的更多要求。与可以部署在任何静态文件服务器上的完全静态单页面应用程序 (SPA) 不同,服务器渲染应用程序,需要处于 Node.js server 运行环境。
3.更多的服务器端负载。在 Node.js 中渲染完整的应用程序,显然会比仅仅提供静态文件的 server 更加大量占用 CPU 资源,因此如果你预料在高流量环境下使用,请准备相应的服务器负载,并明智地采用缓存策略。有效防止XSS的手段有:
过滤用户请求中的非法字符
对请求中的特殊字符进行转译
配置CSP(Content Security Policy)
下面关于transform说法正确的是
只对受控于盒模型的元素生效
变形的原点默认是元素中心
可以通过matrix函数整合多种变形效果
CSS 中的以下几个属性能触发硬件加速:
- transform
- opacity
- filter
- will-change
XSS(Cross Site Scripting跨站脚本)。XSS定义的主语是“脚本”,是一种跨站执行的脚本,也就是javascript脚本,指的是在网站上注入我们的javascript脚本,执行非法操作。
CSRF(Cross-site request forgery跨站请求伪造,也被称为“One Click Attack”或者Session Riding,通常缩写为CSRF或者XSRF,是一种对网站的恶意利用。CSRF定义的主语是”请求“,是一种跨站的伪造的请求,指的是跨站伪造用户的请求,模拟用户的操作.
Object.assign()
是浅拷贝XSS(Cross Site Scripting跨站脚本)。XSS定义的主语是“脚本”,是一种跨站执行的脚本,也就是javascript脚本,指的是在网站上注入我们的javascript脚本,执行非法操作。
CSRF(Cross-site request forgery跨站请求伪造,也被称为“One Click Attack”或者Session Riding,通常缩写为CSRF或者XSRF,是一种对网站的恶意利用。CSRF定义的主语是”请求“,是一种跨站的伪造的请求,指的是跨站伪造用户的请求,模拟用户的操作.
XSS攻击发生的条件是可以执行javascript脚本,一般在站点中总会有发表文章、留言等信息的表单,这种表单一般是写入到数据库中,然后在某个页面进行展示。我们可以在这些表单中直接编写javascript代码(
<script>alert("哈哈哈哈,你被攻击了!");</script>
)进行测试,看是否可以执行。如果在信息展示页面js代码可以执行,XSS攻击就成功了。CSRF攻击能够成功,是因为黑客可以完全伪造用户的请求,该请求中所有的用户验证信息都是存在于cookie中,因此黑客可以在不知道这些验证信息的情况下直接利用用户自己的cookie 来通过安全验证。要抵御 CSRF,关键在于在请求中放入黑客所不能伪造的信息,并且该信息不存在于 cookie 之中。可以在 HTTP 请求中以参数的形式加入一个随机产生的 token,并在服务器端建立一个拦截器来验证这个 token,如果请求中没有token或者 token 内容不正确,则认为可能是 CSRF 攻击而拒绝该请求。这种方法要比检查 Referer 要安全一些,token 可以在用户登陆后产生并放于session之中,然后在每次请求时把token 从 session 中拿出,与请求中的 token 进行比对
after伪元素清除浮动,设置在父容器中;隔墙法设置在子元素
typeof
是一个一元运算,放在一个运算数之前,运算数可以是任意类型。它返回值是一个字符串,该字符串说明运算数的类型。
运算数为数字 typeof(x) = “number”
字符串 typeof(x) = “string”
布尔值 typeof(x) = “boolean”
对象,数组和null typeof(x) = “object”
函数 typeof(x) = “function”
instanceof
运算符用来测试一个对象在其原型链中是否存在一个构造函数的prototype
属性。instanceof
可以在继承关系中用来判断一个实例是否属于它的父类型。基本数据类型:Number、String、Boolean、Null、 Undefined、Symbol(ES6),这些类型可以直接操作保存在变量中的实际值。
引用数据类型:Object(在JS中除了基本数据类型以外的都是对象,数据是对象,函数是对象,正则表达式是对象)
const
定义的对象\数组中的属性值可以修改,基础数据类型不可以
至2021-08-17
闭包
闭包简单来说就是能够读取其他函数内部变量的函数,由于在
Javascript
语言中,只有函数内部的子函数才能读取局部变量,因此可以把闭包简单理解成”定义在一个函数内部的函数”。原因就在于f1是f2的父函数,而f2被赋给了一个全局变量,这导致f2始终在内存中,而f2的存在依赖于f1,因此f1也始终在内存中,不会在调用结束后,被垃圾回收机制(garbage collection)回收。
这会使得函数中的变量都被保存在内存中,导致内存消耗变大,同时由于父函数内部的变量会被闭包获取到,可能会导致父函数内部的值被修改,产生一些意料之外问题
–>垃圾回收机制
JS
的垃圾回收机制是为了以防内存泄漏。变量生命周期结束后会被释放内存,全局变量的生命周期持续到浏览器关闭页面,局部变量的生命周期在函数执行后就结束了。js
垃圾回收有两种方式:标记清除、引用计数标记清除:
标记阶段:垃圾回收器从根对象开始遍历,每一个可以从根对象访问到的对象都会被添加一个可到达对象的标识。
清除阶段:垃圾回收器会对堆内存从头到尾进行线性遍历,如果有对象没有被标识为可到达对象,那么就将对应的内存回收,并清除可到达对象的标识,以便下次垃圾回收。引用计数:
低版本的IE使用这种方式,但常常会引起内存泄露。原理是跟踪一个值的引用次数,当声明一个变量并将一个引用类型赋值给该变量时引用次数就是1,同一个值又被赋给另一个变量,引用次数+1,包含这个值当引用的变量取得新值,则引用次数-1,当垃圾回收器下次运行时,就会释放那些引用次数0的值占用的内存。
深浅拷贝
深浅拷贝针对的是引用数据类型,浅拷贝指的是只复制指向某个对象的指针,多个指针指向同一块内存,真正的内容只有一份,深拷贝指的是复制一份一模一样的对象,两个对象相互独立;
->基本数据类型:Number、String、Boolean、Null、 Undefined、Symbol(ES6),这些类型可以直接操作保存在变量中的实际值。
->引用数据类型:Object(在JS中除了基本数据类型以外的都是对象,数据是对象,函数是对象,正则表达式是对象)
我在实习的时候,用
Vue
把我之前的那个简单的项目改写了一下,其中有一个网页要实现一个简单的排序功能,数据都在一个数组中,我的做法是将原始数据保持不变,声明一个新的变量存放排序后的数组,然后页面上渲染排序后的数据。这个过程中就遇到了深浅拷贝的问题,一开始我直接用等号赋值,实际上是浅拷贝,导致直接对原始数据进行了操作,最终无法实现重置的功能。后来改成了用展开操作符赋值,解决了问题->各种排序
冒泡排序
1
2
3
4
5
6
7
8
9
10
11
12
13
14function bubbleSort(arr) {
let len = arr.length
let temp = null
for(let i = 0; i < len; i++){
for(let j = 0; j < len - i - 1; j++){
if(arr[i] > arr[j]){
temp = arr[i]
arr[i] = arr[j]
arr[j] = temp
}
}
}
return arr
} 时间复杂度O(n^2),空间复杂度O(1),稳定性:稳定
快速排序
分治思想,思路:
1、选择数组中间数作为基数,并从数组中取出此基数;
2、准备两个数组容器,遍历数组,逐个与基数比对,较小的放左边容器,较大的放右边容器;
3、递归处理两个容器的元素,并将处理后的数据与基数按大小合并成一个数组,返回。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15function quickSort(arr){
if(arr.length <= 1) { return arr }
let pivotIndex = Math.floor(arr.length / 2) //基准的序号,向下取整
let pivot = arr.splice(pivotIndex, 1)[0] //基准的值
let left = []
let right = []
for(let i = 0; i < arr.length; i++){
if(arr[i] < pivot){
left.push(arr[i])
}else{
right.push(arr[i])
}
}
return quickSort(left).concat([pivot], quickSort(right)) //递归
} 时间复杂度O(nlogn),空间复杂度O(logn),稳定性:不稳定
插入排序
类似于整理扑克牌,把每一张都插入到当前的最佳位置
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17function insertSort(arr){
let len = arr.length
let preIndex = null
let current = null //本次循环中源位置
for(let i = 1; i < len; i++){
preIndex = i - 1
current = arr[i]
while(preIndex >= 0 && arr[preIndex] > current){
//往后挪
arr[preIndex + 1] = arr[preIndex]
preIndex--;
}
//插入数值
arr[preIndex + 1] = current
}
return arr
} 时间复杂度O(n^2),空间复杂度O(1),稳定性:稳定
选择排序
选择一个数作为基准,对比其后面的数,遍历寻找其中最小的数,结束后将最小的数和基准进行交换,不断重复
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20function selectSort(arr){
let len = arr.length
let minIndex = null
let tempNum = null
for(let i = 0; i < len - 1; i++){
minIndex = i //基准为i
//从基准后一位开始找
for(let j = i + 1; j < len; j++){
if(arr[minIndex] > arr[j]){
minIndex = j
}
}
//找到一个最小值的索引
//交换
tempNum = arr[minIndex]
arr[minIndex] = arr[i]
arr[i] = tempNum
}
return arr
} 时间复杂度O(n^2),空间复杂度O(1),稳定性:不稳定
->深拷贝方法
1.
Object.assign()
是深拷贝还是浅拷贝
当对象中只有一级属性,没有二级属性的时候,此方法为深拷贝,但是对象中有对象的时候,此方法,在二级属性以后就是浅拷贝。 2.
obj2 = JSON.parse( JSON.stringify(obj1) )
1
2
3
4
5
6
7let a = {
name: 'JJ',
age: 18
}
b = JSON.parse(JSON.stringify(a))
b.age = 25;
console.log(a, b); 3. 对于数组:
newArr = arr.map( item => item)
->展开操作符
用…扩展一个数组对象和字符串
1
console.log([...'hey']) // ['h', 'e', 'y']
如果是对象,对象内部的引用数据类型不会深拷贝,而是浅拷贝
常见
ES6
标准let
,const
->
let
,const
,var
区别:
var
变量提升,没有块级作用域 ,重复声明会覆盖; 任何一对花括号({和})中的语句集都属于一个块,在这之中定义的所有变量在代码块外都是不可见的,我们称之为块级作用域。
let
有块级作用域,不会变量提升,不能重复声明,暂时性死区:1
2
3
4
5console.log(a);
var a = 1; //undefined
console.log(a);
let a = 1;//报错
const
必须有初始值,不会变量提升,不能重复声明,暂时性死区,值无法改变,对象除外,因为本质是引用,修改的是内存中的内容,而不是指针解构赋值
模板字符串(``反引号),个人的理解是可以实现动态字符串,用${}来包裹变量
map()
foreach()
可能会问到二者的区别:map()
会返回一个新的数组,而foreach()
不会Promise promise是为了解决有些时候回调函数过多的问题,也就是所谓回调地狱,他是一个对象,总共有三种状态,分别是pending,resolved和rejected,它的构造函数接受一个函数作为参数,该函数有两个参数,分别是resolve和reject,resolve的作用就是将该promise的状态变成resolved,并且可以将参数传递出去,reject的作用是将状态变成rejected,也可以将错误信息传递出去;promise还有一个叫then()的方法,它接受两个参数,第一个是promise resolved时的回调函数,第二个是rejected时的回调函数,第二个参数不是必须的,同时then()里也可以返回promise,因为在then中使用了return,那么return的值会被
Promise.resolve()
包装,这样就可以链式调用,提高代码的可读性
Vue
双向绑定原理
利用数据劫持结合发布者-订阅者模式实现,需要一个监听器Observer,订阅者Watcher和解析器Compiler,监听器通过
Object.defineProperty()
递归地为每一个属性添加getter和setter,特别是setter,这样可以在属性的值发生变化时被Observer监听到,然后通知订阅者,订阅者收到通知后执行相关的函数,更新视图;编译器Complier来解析编译模板指令,就是去遍历寻找有指令的节点,然后初始化节点,为它绑定更新函数,初始化订阅器,这样就实现了数据的双向绑定。总体来说就是编译器寻找带有指令的节点,绑定订阅器,监听武器监听到属性变化时,会通知订阅器,从而更新视图,订阅器是二者之间的桥梁组件通讯方式
首先是最基础的
props
,可以从父组件向子组件传递数据,$emit
从子组件触发父组件的事件,将子组件的数据通过传参的方式传给父组件$parent
和$children
直接操作父子组件实例,不过$children
是数组类型,不保证顺序也不是响应式的EventBus
提供一根事件总线,可以在任何组件之间通讯,通过bus.$emit()
触发事件,bus.$on()
监听事件Vuex
:了解过,个人的理解是和EventBus
有一个共同的思想,就是用一个总的容器来控制全局,不过是用来进行组件状态管理的,比EventBus
的适用范围要广。工作流程大概是在Vue根组件上挂载一个Store实例,这个实例中有一个State属性,可以存放各种组件的状态,有一个Getter相当于一个计算属性,对State进行操作,一个Mutation,里面定义了一些可以修改State中的值的方法,组件可以通过commit提交这些方法,来修改State中的值,但是这些方法必须是同步的,因为devtools
需要打印出mutation执行前后的状态快照,如果是异步操作,会导致状态无法追踪,如果需要异步,要用到action,action通过Store.dispatch()
触发,action内部可以执行异步操作,通过commit提交mutation来修改state中的值。最后,为了防止单一的store过于庞大,可以用将其分割成多个modules生命周期
beforeCreate
: 只有一些初始事件,没有data,methods等created
:有data,methods等,可以进行初始数据的请求,处理之类的操作,但不能进行DOM操作beforeMount
:判断el的挂载方式,如果没有el,会等待调用$mount(el)
判断是否有template设置 将template进行渲染保存到内存当中,还未挂载在页面上mounted
:此时可以进行DOM操作beforeUpdate
:数据更新,但DOM还没更新updated
:根据data里的最新数据渲染出最新的DOM树,然后将最新的DOM挂载到页面 此时data和页面数据一致,都是最新的beforeDestroy
:此时组件从运行阶段进入到销毁阶段 组件上的data和methods以及过滤器等都出于可用状态,销毁还未执行,一般用来解绑事件之类destroyed
:组件销毁,子组件,事件监听器之类都会被销毁组件生命周期调用顺序:
组件的调用顺序都是先父后子,渲染完成的顺序是先子后父。
组件的销毁操作是先父后子,销毁完成的顺序是先子后父。
加载渲染过程 父beforeCreate->父created->父beforeMount->子beforeCreate->子created->子beforeMount- >子mounted->父mounted
子组件更新过程 父beforeUpdate->子beforeUpdate->子updated->父updated
父组件更新过程 父 beforeUpdate -> 父 updated
销毁过程 父beforeDestroy->子beforeDestroy->子destroyed->父destroyed
MVVM
:Modle View View-Model,Model控制数据,View控制视图,View-Model是二者之间的桥梁,数据更新时,Model通知View-Model,View-Model会自动的更新视图;同样的,当视图更新时,View-Model会更新数据,这可以将视图和业务逻辑分隔开,降低耦合度
Vue-router
hash路由
和history路由
: hash ——即地址栏URL中的#符号(此hsah 不是密码学里的散列运算) hash 虽然出现URL中,但不会被 包含在HTTP请求中,对后端完全没有影响,因此改变hash不会重新加载页面。
history ——利用了HTML5 History Interface 中新增的pushState() 和replaceState() 方法
项目中用的是默认的hash模式,
相应路由参数变化:
watch
$router
对象路由守卫:
router.beforeEach(to, from, next)
:全局前置守卫,to表示要哪,from表示从哪来,next是一个函数,相当于promise中的resolve,如果不调用next,这个守卫会一直处于一个等待中的状态,我个人目前用它来处理一些跳转逻辑,比如验证登录状态,如果登录了,就调用next放行到主页,如果未登录,就强制跳转到登陆页面 还有一些组件内的守卫,
beforeRouteEnter(to, from, next)``beforeRouteLeave(to, from, next)
用来根据业务需要,在组件内部处理跳转的逻辑,比如说beforeRouteLeave()
可以在用户离开页面之前来向用户确认是否要保存未修改的内容之类。跳转方式:
声明式:
<router-link :to="{路径,参数}"></router-link>
编程式:
this.$router.push({path: '', query or param: ''})
param
:显示在url上,刷新时会失去里面的内容
query
:不显示在url上,刷新时内容不会失去 声明式的本质是调用
router.push()
axios
axios
基于Promise,本质上也是对原生XHR的封装,但是是Promise的实现版本,所以使用起来非常方便直观,小型项目的话可以直接使用axios(config)
来发起请求,config
中可以配置请求的参数,比如method``url``data
之类,method
有get``post``delete``put``options
,我比较熟悉的是get
,post
,get
是用来向服务端请求数据的,如果携带参数的话,参数会在url中显示,因此会受到浏览器url的长度限制,导致参数的传递存在上限,同时传递参数会被直接看到,不应该用来传递敏感数据,同时get
的数据只允许ASCII字符post
是用来向服务器提交数据的,并且提交的数据不会在url中显示,也没有大小的限制,更加安全,数据格式也没有限制;此外delete
是用来让服务器删除指定数据的,options
一般会在两种情况下出现,一种是向服务器获取支持的http通信方法,还有一种就是在跨域的时候进行预检请求,通过先发送一个options
方法,来检查请求是否安全,如果安全才会进行接下来的请求,但是只有复杂请求会有options
,复杂请求要满足一下任意一种情况:如果方法是put, delete, patch, post
中的一种,或者数据是json
格式,换言之不是text/plain, urlencoded, formdata
这三种格式之一,或者请求中有自定义的头部,只要满足这其中的一种,就算是复杂请求,复杂请求在跨域时会发出预检请求。fetch
和axios
的区别fetch
是原生js,而不是封装XHR,fetch
只对网络请求报错,对400,500都当做成功的请求,服务器返回 400,500 错误码时并不会 reject,只有网络错误这些导致请求不能完成时,fetch
才会被reject
。fetch
没有办法原生监测请求的进度,而XHR可以相同点:
fetch
和axios
都是基于promise,默认都不携带cookie->跨域
跨域是由于浏览器的同源政策,它限制了页面之间的通信条件,两个页面如果不是同源的,就无法通信,无法读取
cookie
,localStorage
,无法发送请求等,而这个同源,指的是协议+域名+端口号三者完全相同,有任何一个不同的都算做跨域,就会受到限制,同源政策的目的是防止XSS,XSRF等,从而保护用户的安全。不过这样也会导致正常的通信受到限制,如果不跨域的话,在向服务器请求数据的时候就会变得非常困难,所以需要解决跨域问题。解决方案有以下几种,首先是jsonp
,它可以解决服务器和客户端之间的跨域问题,利用了<script>
标签不受同源政策限制的特点,通过将其src属性值设置为要请求的服务器地址,并在url中添加携带的参数(如果有需要的话),和get方法非常像,并且还有一个callback
参数,用来指定请求返回后的回调函数,服务器收到请求后会把数据放在回调函数的参数中返回,返回后这个函数会立即执行,从而实现跨域请求的效果,但是只支持get请求。第二种是CORS跨域资源共享,是目前主流的解决办法,方法是服务器设置相应头中的Access-Control-Allow-Origin
为对应的域名即可,如果开启cookie的话,前后端都要设置Access-Control-Allow-Credentials: true
。此外还有websocket
协议,实现浏览器和服务器全双工通信,这个协议不实行同源政策,因此可以跨域;开发过程中,前端可以设置开启代理服务器,将请求发送给代理服务器,然后代理服务器转发请求至真正的服务器,由于是服务器之间的请求,不会受到同源政策限制,收到响应后再由代理服务器转发给前端,从而实现跨域;以上是前后端通信的跨域,还有一些页面之间的跨域,比如通过自带的window.postMessage()
方法,还有比如说当两个页面的二级域名相同时,可以设置这两个页面的document.domain = '同一个域名'
来实现页面之间的通信。->
cookie
,localStorage
,sessionStorage
相同点:都保存在浏览器端,且同源
不同点:
cookie
会一直在服务器和浏览器之间传递,localStorage
,sessionStorage
只是保存在本地;cookie
的大小一般不 能超过4K,而localStorage
,sessionStorage
可以达到5M以上;除此之外,cookie
在设置的过期时间之前一直有效,在同源 窗口中共享,sessionStorage
只在窗口关闭之前有效,并且即使是同一个页面也不会共享,localStorage
只要不被清除,就一 直有效,在同源窗口中共享。->XSS, XSRF
XSS(Cross Site Scripting跨站脚本)。一般来说是在提交的内容中注入一些JavaScript代码来进行攻击 ,可以通过过滤掉或者替换掉 关键字,比如html标签,js事件等,或者是将内容重新编码,使得内容仅仅被显示而不会被执行
XSRF(Cross-site request forgery)跨站请求伪造,指的是跨站伪造用户的请求,模拟用户的操作,比如用户同时打开了一个普通 网站和一个恶意网站,这个恶意网站通过获取用户在普通网站的
cookie
,来模拟用户操作,向服务器发送恶意请求。可以通过验 证HTTP请求中的Referer字段来防御,或者是服务器生成token并返回给客户端,之后客户端的请求需要携带tokenhttp状态码
200 成功处理请求
404 找不到页面;具体情况:后端ip地址变了
401 用户没有权限
403 用户有授权但访问被禁止
302 临时重定向;具体情况:后端同事为了调试,把接口临时移到了别的地方
304 内容没有改变,可以引出协商缓存机制
500 服务器遇到未曾预料的错误,无法处理请求;具体情况:识别引擎崩了,后端接口还开着,但是处理不了前端发过来的数据,导致返回500
->协商缓存,强缓存
强缓存:直接读取本地缓存,返回200,分为两种
expires
和cache-control
,expires
表示过期时间,是一个固定的时间戳,存在问题:与客户端时间对比,客户端时间可以修改,且服务器和客户端时间可能不一致;cache-control
表示资源多长时间后国企,是一个时间长度,比expires
更好 协商缓存:与服务器对比,若没有改变,则读取本地缓存,返回304,分为两种
last-modified
和etag
,last-modified
表示该资源最后修改的时间,与请求中的if-modified-since
字段对比,如果相同则返回304,不同则返回新的资源,并修改last-modified
,存在的问题:只要编辑了,不管内容是否真的修改,都会被判断为已修改,导致不必要的传输;时间精确到秒,如果是一秒内的修改则不会被检测到。etag
是基于内容编码生成的字符串,代表了内容的唯一性,当etag
和if-none-match
相同时,返回304,否则返回新的资源,并更新etag
,因此etag
要优于last-modified
,但是生成编码会提高服务器开销。 所有字段都是在服务器的响应头中添加。
输入url后发生了什么
首先构建请求
如果有强缓存,并且没有过期,直接使用
否则要去找到对应的ip地址以建立连接,找ip地址的时候,首先去查找本地是否有缓存,依次从浏览器缓存,系统缓存,路由器缓存,系统hosts文件中查找,如果都没有,则向DNS服务器请求,得到服务器的ip地址,
然后向服务器发起TCP连接,并把http请求发送给服务器,服务器处理请求,并返回相应结果,这里只考虑结果是一个html文件的情况,浏览器在收到该文件后,开始构建页面
首先根据html构建DOM树,其间如果遇到js脚本和外部js连接,会停止构建DOM树,转而先执行和下载响应代码,因此js代码最好放在html代码后面,防止页面加载卡顿,影响用户体验;同时遇到一些静态资源比如图片,音频等,会先判断是否有缓存,如果有则直接使用,否则进行并行下载
然后根据外联样式,内部样式,内联样式,来构建CSSOM树
构建完毕后将CSSOM树和DOM树合并成为渲染树
然后进行布局,确定各个元素的位置和尺寸
最后就是渲染页面,呈现给用户
普通函数和箭头函数的区别
- this方面:
普通函数内部的this,默认指向window,严格模式下指向undefined;
箭头函数内部的this,与上一级作用域中的this指向同一个地方。
- arguments方面:
普通函数,可以通过arguments来实现重载;
箭头函数中,没有arguments,代替它功能是剩余参数rest(…)。
- 原型对象方面:
普通函数,是有自己的原型对象的;
箭头函数,没有原型对象。
- new方面:
普通函数,可以作为构造函数,通过new实例化出子函数;
箭头函数,不能作为构造函数,使用new会报错。
- 简易程度:
箭头函数比普通函数的使用简短更多;同时箭头函数通常是匿名函数。
事件循环
先执行所有宏任务,完毕后执行微任务,更新render,重复
宏任务:script( 整体代码)、setTimeout、setInterval、I/O、UI 交互事件、setImmediate(Node.js 环境)
微任务:Promise、MutaionObserver、process.nextTick(Node.js 环境)
至2021-08-18
防抖函数及优化
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28function debounce(func, delay, immediate, ...args){
let timer = null
return function(){
const _this = this
//如果有计时器,清空计时器
if(timer) {
clearTimeout(timer)
}
//立即执行的情况
if(immediate){
let canRun = !timer
if(canRun){
func.apply(_this, args) //this也行, 因为args是一个数组,所以用apply()
}
timer = setTimeout(() => {
timer = null //清空timer,这样下一次进来就会立刻执行
}, delay);
}else{//不用立即执行的情况
timer = setTimeout(() => {
func.apply(_this, args)
}, delay);
}
}
}
function realFun(arg){
console.log(arg)
}
document.getElementById('myButton').onclick = debounce(realFun, 1000, false, 1,2,3)节流函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58function throttle(func, delay, immediate, ...args){
let valid = true
let timer = null
let startTime = new Date();
return function(){
const _this = this
let curTime = new Date()
if(immediate){
//用状态变量的版本
if(valid){
func.apply(_this, args)
valid = false
setTimeout(() => {
valid = true
}, delay);
}
//用计时器的版本
if(!timer){
func.apply(_this, args)
timer = setTimeout(() => {
timer = null
}, delay);
}
//用时间戳的版本
if(curTime - startTime > 500){ //500可以改成一个参数传入,为每多少秒必须触发一次
func.apply(_this, args)
startTime = curTime
}
}else{
//用状态变量的版本
if(valid){
valid = false
setTimeout(() => {
func.apply(_this, args)
valid = true
}, delay)
}
//用计时器的版本
if(!timer){
timer = setTimeout(() => {
func.apply(_this, args)
timer = null
}, delay);
}
//用时间戳的版本
//第一次触发,最后再触发一次,好像用时间戳做不到第一次不触发
clearTimeout(timer)
if(curTime - startTime > 500){
func.apply(_this, args)
startTime = curTime
}else{
timer = setTimeout(() => {
func.apply(_this, args)
}, delay);
}
}
}
}瓶盖递归
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18let bottle = 0; //瓶盖数
let num = 0; //瓶子数
let money = 20; //钱的数量
function buyWater(){
while(money > 0){
money--
num++
bottle++
//递归
if(bottle === 3){
money++
bottle = 0
buyWater()
}
}
}
buyWater()
console.log(num, bottle)this绑定的四种方法
默认绑定
1
2
3
4
5
6var bar=2021;
function foo(){
var bar=1998;
console.log(this.bar);
}
foo();//2021,没有调用对象,this默认指向了window1
2
3
4
5
6
7
8
var bar=2021;
function foo(){
var bar=1998;
console.log(this.bar);
}
foo();//Uncaught TypeError: Cannot read property 'bar' of undefined
//严格模式下,不允许this指向window,因此是undefined隐式绑定
1
2
3
4
5
6
7
8
9
10
11
12
13
14var foo=1998;
function bar(){
console.log(this.foo);
}
var obj={
foo:2021,
bar:bar,
obj2:{
foo: 2022,
bar: bar
}
}
obj.bar();//2021 函数被调用时,将this隐式绑定到了上下文对象,并且指向离函数最近的对象
obj.obj2.bar();//2022隐式丢失
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20var foo=1998;
function bar(){
console.log(this.foo);
}
var obj={
foo:2021,
bar:bar
}
var baz=obj.bar;
baz();//1998 相当于 baz = bar,this直接指向了window
------------------------------------------------------
var foo=1998;
function bar(){
console.log(this.foo);
}
var obj={
foo:2021,
bar:bar
}
setTimeout(obj.bar,0)//1998 匿名函数的this指向window硬绑定
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17var foo="我是全局 foo";
function bar(){
console.log(this.foo);
}
var obj1={
foo:"我是obj1 foo",
bar:bar
}
var obj2={
foo:"我是obj2 foo",
}
var obj3={
foo:"我是obj3 foo"
}
obj1.bar.apply(obj2)//我是obj2 foo
obj1.bar.call(obj3)//我是obj3 foo
obj2.bar.bind(obj3)()//我是obj3 foonew绑定
1
2
3
4
5
6var foo="我是全局 foo";
function bar(foo){
this.foo=foo;
}
var baz=new bar("新传的bar");
console.log(baz.foo);//新传的bar在定义好函数后,都会new一下,生成一个新的对象,new的过程中会执行下面的操作:
1、创建一个全新的对象
2、给这个对象挂载prototype属性
3、新对象会绑定到函数调用的this(调用这个对象下的函数方法时,this会指向该对象)
4、如果函数没有返回其他对象,那new表达式中的函数调用会返回这个新对象
1
2
3
4
5
6
7
8
9
10
11
12
13function foo(){
return a => {
console.log(this.a)
}
}
var obj = {
a: 7
}
var obj1 = {
a: 9
}
var bar = foo.call(obj) //箭头函数创建时的this根据上下文来,即foo()的this,它被绑定到了obj,因此箭头函数的this指向obj
bar.call(obj1) //7 箭头函数的this无法修改