记录当初伤害过我的一些概念😊
文章目录
- 一、闭包
- 二、深拷贝、浅拷贝
- 三、slice、splice、join、split、filter、concat、sort、some、every
- 四、for in和for of、 map和foreach
- 五、原型和原型链
- 六、跨域
- 七、vue相关
- 1、生命周期
- 2、响应式原理
- 3、watch和computed
- 4、vue3和vue2区别
- 5、传值
- 6、组件封装
- 7、vuex和pinia
- 8、nextTick
- 八、css相关
- 1、flex布局
- 2、垂直水平居中方法
- 3、盒子模型
- 九、promise、async和await
- 十、get和post
- 十一、作用域
- 十二、箭头函数和普通函数
- 十三、cookie、sessionStorage、localStorage
- 十四、vite和webpack
- 十五、浏览器的渲染过程
- 十六、diff算法
- 十七、typeof和instanceof
- 十八、
一、闭包
闭包是指有权访问另一个函数作用域中变量的函数。
var i = 0;
function a() {
console.log(i);
}
a();
// 这就是一个简单的闭包,该函数使用了外部的数据
我们先说一下闭包的特点:
(1)通过闭包可以让外部环境访问到函数内部的局部变量。
(2)通过闭包可以让局部变量持续保存下来,不随着它的上下文环境一起销毁。
理解闭包以及它的特点我们需要先了解两个概念:
(1)作用域链:比如你在一个函数中用到了一个变量,但是在当前作用域中没有找到它的值,就会向上级作用域去查,直到查到全局作用域,这个过程就形成了一条作用域链。
(2)垃圾回收:垃圾收集器会周期性地找出那些不再使用的变量,然后释放其内存。如果该变量还在使用,那么就不会被回收。
然后我们来看个栗子:
function a() {
var i = 0;
}
a();
console.log(i); // 报错i is not defined
输出会报错,为什么?因为执行完a函数后,i 作为局部变量不再使用会被销毁(垃圾回收),所以在输出就会报错not defined。
function a() {
var i = 0;
return function b() {
console.log(i); // 输出0
}
}
var c = a();
c();
显而易见,b函数中用到了a函数中的变量,于是形成了闭包。为什么执行完a函数后i 没被销毁呢?因为执行完a函数后,函数b被赋值给了c,c是全局变量不会被销毁,也就是函数b中的i 还会继续被使用,所以不会被回收。可以看出外部环境也可以访问到函数内部的局部变量。
理解完这个🌰再回看它的概念以及特点就比较好理解了,同时也会发现由于变量不会被销毁,所以过度的使用闭包就可能会产生内存泄漏的问题。
闭包的应用场景:保护函数内的变量安全;在内存中维持一个变量。
完事儿来做两个题吧!!:
(1)请补全JavaScript代码,要求每次调用函数"closure"时会返回一个新计数器。每当调用某个计数器时会返回一个数字且该数字会累加1。
注意:1. 初次调用返回值为1;2. 每个计数器所统计的数字是独立的。
//来自牛客网上的一道js题
<!DOCTYPE html>
<html>
<head>
<meta charset=utf-8>
</head>
<body>
<script type="text/javascript">
const closure = () => {
// 补全代码
}
</script>
</body>
</html>
答案(没有标准答案哦,实现就行):
const closure = () => {
var i = 0;
return function () {
return ++i;
}
}
let a = closure();
a();
(2)执行以下代码,会输出什么结果?
for (var i = 1; i <= 3; i++) {
setTimeout(function () {
console.log(i);
}, 1000);
}
答案: 4 4 4
解析:在匿名函数中使用了外部变量i ,形成了闭包,匿名函数在1s后执行,因为是闭包所以在循环结束后依然可以访问到i ,此时i 已经变成4(i =4后,不符合条件,才停止循环),所以就会输出三个4。
扩展:如何可以输出1 2 3呢?最简单的办法把var写成let即可。
二、深拷贝、浅拷贝
(1)浅拷贝:只复制某个对象的指针,不复制对象本身,新旧对象共享同一块内存。
(2)深拷贝:会另外创造一个一模一样的对象,新对象和原对象不共享内存,修改新对象不会影响原对象,可以用lodash插或者递归调用实现深拷贝。
扩展:
1、es6中的 Object.assign,如果对象的属性值为基础类型,通过Object.assign()拷贝的那个属性而言是深拷贝。如果对象的属性值为引用类型,通过Object.assign()拷贝的那个属性而言是浅拷贝。
2、es6中还有一个扩展运算符"…"也是浅拷贝。
扩展:js的数据类型分为两类,基本数据类型和引用数据类型,前者是存储在栈内存中,后者是将其地址存在栈内存中,而真实数据存储在堆内存中。
(1)基本类型:number string boolean null undefined symbol。基本类型的值是不可变得,基本类型的比较是值的比较。
(2)引用类型:Object、Array、RegExp、Date、Function。引用类型值是可变的,引用类型的比较是引用的比较,即引用类型进行相等判断时,会比较址是否相等,也就是说它会比较是否为内存中的同一个东西。
三、slice、splice、join、split、filter、concat、sort、some、every
(1)slice():可操作字符串和数组,通过索引位置获取新的数组,该方法不会改变原数组,只是返回一个新的子数组(左闭右开)。
(2)splice(index,howmany,item1,…,itemX): 只可操作数组,删除、插入和替换,这种方法会改变原数组。
参数如下:
1、index:必需。规定从何处添加/删除元素;
2、howmany:可选。规定应该删除多少元素。必须是数字,但可以是 “0”,如果未规定此参数,则删除从 index 开始到原数组结尾的所有元素;
3、item:可选。要添加到数组的新元素;
(3)split():把一个字符串分割成字符串数组。
(4)join():把一个数组转换成字符串。
(5)filter():对数组进行过滤,Array.filter(function(currentValue, index, arr), thisValue),不改变原数组。
(6)concat():用于连接两个或多个数组,该方法不会改变原数组。
(7)sort():用于对数组的元素进行排序,默认排序顺序为按字母升序,使用数字排序,你必须通过一个函数作为参数来调用,会改变原数组。
(8)some(): 用于检测数组中的元素是否满足指定条件(函数提供),该方法会依次执行数组的每个元素,如果有一个元素满足条件,则表达式返回true , 剩余的元素不会再执行检测;如果没有满足条件的元素,则返回false。
注意:some() 不会对空数组进行检测。 some() 不会改变原始数组。
(9)every(): 用于检测数组所有元素是否都符合指定条件(通过函数提供),该方法使用指定函数检测数组中的所有元素,如果数组中检测到有一个元素不满足,则整个表达式返回 false ,且剩余的元素不会再进行检测。如果所有元素都满足条件,则返回 true。
注意:every() 不会对空数组进行检测。every() 不会改变原始数组。
四、for in和for of、 map和foreach
(1)for…in 循环:只能获得对象的键名,不能获得键值,不能return。
(2)for…of 循环:允许遍历获得键值,遍历数组,不可遍历对象,可以return。
(3)map:循环遍历数组中的每一项,只能遍历数组,返回新的数组,有返回值。
(4)foreach:循环遍历数组中的每一项,只能遍历数组,会修改原来的数组,没有返回值。
五、原型和原型链
首先我们要知道什么是构造函数,用new关键字来进行调用的函数称为构造函数,一般首字母要大写。(扩展:new函数和普通函数的区别:点这里!!)
下面的可以看这篇文章!我觉得写得很清楚易懂!!!➡️ 原型和原型链
扩展:使用for-in循环,不仅能遍历对象本身的属性和方法,还能够遍历对象原型链上的所有属性方法。可以使用hasOwnProperty判断一个属性,是否是对象自身的属性。Object.create(null) 会造成创建的对象其 proto 指向为空
举个🌰(包含很多知识点!!):
Object.prototype.c = 3;
var obj = {
b: 2
}; // 对象都能访问Object.prototype中的属性和方法
obj.__proto__.a = 1; // 注意哈,对象只有__proto__属性,函数才有prototype属性
console.log(obj.__proto__ === Object.prototype); // 打印true,几乎所有的JavaScript对象都是Object的实例
for (var i in obj) {
console.log(i); // 能迭代出原型链里面的属性,所以会打印出 b c a
console.log(obj.hasOwnProperty(i) == true); // b true, c false , a false
}
六、跨域
当一个请求url的协议、域名、端口三者之间任意一个与当前页面url不一同,即跨域。
原因:出于浏览器的同源策略限制,如果缺少了同源策略,浏览器很容易受到XSS、CSRF等攻击。
如何解决跨域:解决跨域的常用方法有三种,分别为使用代理(如nginx),CORS(跨域资源共享),jsonp。
(1)通过jsonp跨域:JSONP 是服务器与客户端跨源通信的常用方法。最大特点就是简单适用,兼容性好(兼容低版本IE),缺点是只支持get请求,不支持post请求。
核心思想:网页通过添加一个 < script >元素,向服务器请求JSON数据,服务器收到请求后,将数据放在一个指定名字的回调函数的参数位置传回来。
(2)document.domain + iframe跨域(主要用于主域相同的域之间)。
(3)location.hash + iframe
(4)window.name + iframe跨域
(5)postMessage跨域:页面和其打开的新窗口的数据传递、多窗口之间消息传递、页面与嵌套的iframe消息传递,上面三个场景的跨域数据传递。
(6)跨域资源共享(CORS):它是 W3C 标准,属于跨源 AJAX 请求的根本解决方法。
1、普通跨域请求:只需服务器端设置Access-Control-Allow-Origin。
2、带cookie跨域请求:前后端都需要进行设置。
(7)nginx代理跨域:Nginx 实现原理类似于 Node 中间件代理,需要你搭建一个中转 nginx 服务器,用于转发请求。
(8)nodejs中间件代理跨域
(9)WebSocket协议跨域
七、vue相关
1、生命周期
Vue 实例从创建到销毁的过程为生命周期。
(1)vue2:
创建前:beforeCreate() 只有一些实例本身的事件和生命周期函数。
创建后:Created() 是最早使用data和methods中数据的钩子函数。
挂载前:beforeMount() 指令已经解析完毕,内存中已经生成dom树。
挂载后:Mounted() dom渲染完毕页面和内存的数据已经同步。
更新前:beforeUptate() 当data的数据发生改变会执行这个钩子,内存中的数据是新的,页面是旧的。
更新后:Updated() 内存和页面都是新的。
销毁前:beforeDestroy() 即将销毁data和methods中的数据此时还是可以使用的,可以做一些释放内存的操作。
销毁后:Destroyed() 已经销毁完毕。
(2)vue3:
setup、onBeforeMount、onMounted、onBeforeUpdate、onUpdated、onBeforeUnmount、onUnmounted
问:第一次页面加载会触发哪几个钩子?
答:会触发 beforeCreate,created,beforeMount, mounted 这几个钩子。
问:DOM 渲染在哪个周期中就已经完成?
答:DOM 渲染在 mounted 中就已经完成了。
2、响应式原理
响应式原理就是指的是MVVM的设计模式的核心,即数据驱动页面,一旦数据改变,视图也会跟着改动。
(1)vue2的响应式原理是由Object.defineProperty() 实现的 (数据劫持)
(2)vue3的响应式原理是由es6中的Proxy所实现的 (数据代理)
3、watch和computed
(1)watch:监听属性,每当监听的数据变化时都会执行回调进行后续操作,支持异步操作。 (watch需要传递监听的对象,watchEffect不需要)
(2)computed:计算属性,依赖其它属性值,并且computed的值有缓存,只有它依赖的属性值发生改变,下一次获取computed的值时才会重新计算computed的值,有返回值,且其返回值也会被监听,不支持异步操作。
运用场景:当需要进行数值计算,并且依赖于其它数据时,应使用computed,因为可以利用computed的缓存特性,避免每次获取值时,都要重新计算;当我们需要在数据变化时执行异步或开销较大的操作时使用watch。总结一下,当多个数据可能影响一个数据时,适用computed;当一个数据需要去影响多个数据时,适用watch。
4、vue3和vue2区别
(1)双向数据绑定原理;
(2)vue2采用optionAPI,vue3采用Composition API (组合api);
(3)生命周期变化;
(4)v-if 和 v-for的优先级,vue2中v-for 优先于 v-if 生效,vue3反之。
5、传值
父传子:props
子传父:emit
祖传孙:provide 和 inject
组件通信:vuex、pinia
父获取子属性:ref(子组件通过expose暴露出去,父组件可以获取到)
6、组件封装
组件封装指的是将一段可复用的代码封装成一个单独的模块,使得该模块可以在多个地方被重复使用,而不需要每次都重新编写。传参、事件和插槽是封装组件时常用的三种方式,通过这些方式可以让组件更加灵活和具有通用性。同时,组件的封装还包括对于内部实现逻辑的封装使得外部使用者无需了解组件内部的细节,只需要使用组件提供的接口即可完成相应的功能。
7、vuex和pinia
pinia中只有state、getter、action,抛弃了Vuex中的Mutation,可以直接修改state里的数据,支持ts。
问:vuex页面刷新后如何防止丢失?
答:可以将vuex中的数据直接保存到浏览器缓存中(sessionStorage、localStorage、cookie),页面刷新后再从浏览器中取出。
8、nextTick
定义:在下次 DOM 更新循环结束之后执行延迟回调。在修改数据之后立即使用这个方法,获取更新后的 DOM。
使用场景:在对数据进行赋值之后,需要对新dom做一些操作的时刻,但是此刻最新的dom还没有进行更新,它可以帮助我们在下一次更新dom后执行它的回调函数。
nextTick 的原理是 vue 通过异步队列控制 DOM 更新,nextTick底层是promise,所以是微任务。
八、css相关
1、flex布局
2、垂直水平居中方法
居中元素固定尺寸:
(1)absolute + 负margin:position: absolute; top: 50%; left: 50%; margin-top: -高度的一半px; margin-left: -宽度的一半px;
(2)absolute + margin auto: position: absolute; top: 0; bottom: 0; right: 0; left: 0; margin:auto;
(3)absolute + calc: position: absolute; top: calc(50% - 125px) left: calc(50% - 125px);
居中元素不固定尺寸:
(1)使用绝对定位 + transform:top: 50%; left: 50%; transform: translate(-50%, -50%);
(2)父元素使用flex布局,并设置相关的属性值为center:display:flex; justify-content:center; align-items:center;
(3)flex + margin auto:父组件设置display:flex
,子组件设置margin:auto
(4)table- cell等。
3、盒子模型
所有HTML元素可以看作盒子,在CSS中,"box model"这一术语是用来设计和布局时使用。
CSS盒模型本质上是一个盒子,封装周围的HTML元素,它包括:margin、border、padding、content。
标准盒子模型、ie盒子模型
4、
九、promise、async和await
(我在b站上看了尚硅谷的promise课程,感觉讲的挺清楚的,大家可以去看看,还包括手写promise,有时间的话我再自己整理一遍。➡️b站链接🔗)
异步方法可以在不等待完成的情况下并发执行多个操作。在JS中,有两种常用的实现异步方法的方式:promise和async/await。
(promise本身是同步,但promise的回调then和catch是异步)
1、promise
ES6引入了Promise对象。
优点:支持链式调用,可以解决回调地狱问题,便于异常处理。
Promise.all() :该方法接受一个数组,将多个Promise对象组合成一个新的Promise对象,并在所有操作都完成后得到结果,所有子Promise都成功时才成功,任何一个子Promise失败时就失败。
Promise.race():竟速模式,将多个Promise合成一个Promise来执行,最先成功(fulfilled)返回的请求当作该Promise的响应返回。
问:如何中断 promise链?
答:有且只有一种方法,在回调函数中返回一个pendding状态的promise对象。( return new Promise(() => {}); )
2、async和await
ES7引入了async/await语法来简化异步编程。
async/await可以顺序执行多个异步操作,并在每个操作完成后得到结果。
async用于申明一个function是异步的,而await则可以认为是 async await的简写形式,是等待一个异步方法执行完成。async函数返回一个 Promise 对象,所以 await 可以用于等待一个 async 函数的返回值。
注意⚠️:await只能用在async函数里面。async/await可以使用try-catch语句处理异常,await会阻塞进程,会暂停当前 async function 的执行,等待 Promise 处理完成。
一道例题:
async function async1() {
console.log( 'async1 start' )
await async2()
console.log( 'async1 end' )
}
async function async2() {
console.log( 'async2' )
}
console.log( 'script start' )
setTimeout( function () {
console.log( 'setTimeout' )
}, 0 )
async1();
new Promise( function ( resolve ) {
console.log( 'promise1' )
resolve();
} ).then( function () {
console.log( 'promise2' )
} )
console.log( 'script end' )
答案:(执行顺序:同步➡️微任务➡️宏任务)
script start
async1 start
async2
promise1
script end
async1 end
promise2
setTimeout
十、get和post
1、get使用url或Cookie传参,而post将数据放在body中;
2、get的url会有长度上的限制,而post的数据则可以非常大;
3、post比get安全,因为数据在地址栏上不可见;
4、一般get请求用来获取数据,post请求用来发送数据;
5、get产生一个TCP数据包,post产生两个TCP数据包。
推荐一篇比较有趣的博客:GET和POST的区别
十一、作用域
(1)定义:就是能够储存变量当中的值,并且能在之后对这个值进行访问或修改。
(2)JS中作用域有:全局作用域、函数作用域。在ES6中新增了块级作用域,使用let声明的变量只能在块级作用域里访问,有“暂时性死区”的特性(也就是说声明前不可用)。
(3)块级作用域:由 { } 包括,if语句和for语句里面的{ }也属于块作用域。
(4)声明变量的三个关键字:
var:有变量提升(把所有变量声明都拉到函数作用域的顶部),可重复声明。
let:没有变量提升(使用let声明变量前,该变量都不可用),在相同作用域不可重复声明,
const:没有变量提升,声明一个只读的常量,一旦声明,常量的值就不能改变,并且必须声明时立即初始化,不能留到以后赋值,在相同作用域不可重复声明。
十二、箭头函数和普通函数
箭头函数不能作为构造函数使用,箭头函数没有自己的this,它所谓的this是捕获其所在上下⽂的 this 值,作为⾃⼰的 this 值,call()、apply()、bind()等方法不能改变箭头函数中this的指向,箭头函数没有自己的arguments,箭头函数没有prototype。
问:普通函数改变this指向的方法?它们的区别是?
答:三种方法 call、apply、bind。
(1)call方法在调用的时候就会执行函数,第一个参数是this的指向,第二个参数以及后面的参数都是需要传的参数,返回值是原函数中定义的返回值。
(2)apply方法在调用的时候也会执行函数,第一个参数是this的指向,第二个参数是数组,其中是需要传递的参数,返回值是原函数中定义的返回值。
(3)bind方法在调用的时候不会执行函数,第一个参数是this的指向,第二个参数以及后面的参数都是需要传的参数,返回值是一个新的函数。
注意⚠️:若第一参数为null和undefined,那么this默认指向window。
十三、cookie、sessionStorage、localStorage
原文:cookie、localStorage和sessionStorage详解
(1)cookie:
cookie的内容主要包括:名字name,值value,过期时间expires,路径path和域domain。路径和域一起构成cookie的作用范围。一般cookie储存在内存里,若设置了过期时间则储存在硬盘里,浏览器页面关闭也不会失效,直到设置的过期时间后才失效。若不设置cookie的过期时间,则有效期为浏览器窗口的会话期间,关闭浏览器窗口就失效。
原理:客户端请求服务器时,如果服务器需要记录该用户状态,就使用response向客户端浏览器颁发一个Cookie。而客户端浏览器会把Cookie保存起来。当浏览器再请求服务器时,浏览器把请求的网址连同该Cookie一同提交给服务器。服务器通过检查该Cookie来获取用户状态。
使用场景:存储用户名和密码。
使用方法:
// 添加cookie
addCookie(){
document.cookie = "name=Molly"
document.cookie = "age=18"
}
// 读取cookie
readCookie(){
let msg = document.cookie
console.log('读取cookie',msg);
},
// 删除cookie
deleteCookie(){
let d = new Date(new Date().getTime() - 30000)
document.cookie = "name=Molly;"+"expires="+d.toGMTString()
}
(2)localStorage:生命周期是永久,除非手动去清除,否则永远都存在,仅在客户端浏览器上储存,不参与服务器的通信。
使用场景: 用来统计页面访问次数。
使用方法:
// 设置localStorage保存到本地,第一个为变量名,第二个是值
localStorage.setItem('name', 'Molly')
// 获取数据
localStorage.getItem('name')
// 删除保存的数据
localStorage.removeItem('name')
// 清除所有保存的数据
localStorage.clear()
(3)sessionStorage:仅在当前会话下有效,关闭页面或浏览器后被清除,仅在客户端浏览器上储存,不参与服务器的通信。不同窗口的sessionStorege的对象是不同的,而localStorage的值是同步改变的。
使用场景:用来统计当前页面点击次数。
使用方法:
// 设置sessionStorage保存到本地,第一个为变量名,第二个是值
sessionStorage.setItem('sessionName', 'Molly')
// 获取数据
sessionStorage.getItem('sessionName')
// 删除保存的数据
sessionStorage.removeItem('sessionName')
// 清除所有保存的数据
sessionStorage.clear()
三者共同点:都是保存在浏览器端、且同源的。(http协议、域名、端口一致为同源策略)
三者区别:
1、cookie数据始终在同源的http请求中携带(即使不需要),即cookie在浏览器和服务器间来回传递,而webStorage不会自动把数据发送给服务器,仅在本地保存。cookie数据还有路径(path)的概念,可以限制cookie只属于某个路径下
2、存储大小限制也不同,cookie数据不能超过4K,同时因为每次http请求都会携带cookie,所以cookie只适合保存很小的数据,如会话标识。webStorage可以达到5M或更大 。
3、数据有效期不同,sessionStorage仅在当前浏览器窗口关闭之前有效;localStorage始终有效,窗口或浏览器关闭也一直保存,因此用作持久数据;cookie只在设置的cookie过期时间之前有效,即使窗口关闭或浏览器关闭 。
4、作用域不同,sessionStorage不在不同的浏览器窗口中共享,即使是同一个页面;localstorage在所有同源窗口中都是共享的;cookie也是在所有同源窗口中都是共享的 。
5、webStorage支持事件通知机制,可以将数据更新的通知发送给监听者 。
6、webStorage的api接口使用更方便。
注意⚠️:webStorage是以字符串的格式储存的,所以不能直接储存对象和数组类型,需要先转换一下格式再进行储存。使用JSON.stringify()将对象转换成字符串,解析的时候,再使用JSON.parse()转换回去使用。
十四、vite和webpack
共同点:都是现代化打包工具
区别:
十五、浏览器的渲染过程
从用户请求到浏览器渲染的过程大致如下:
1.输入网址,然后DNS解析成IP地址,建立TCP连接;
2.浏览器根据IP地址请求服务器;
3.服务器处理并响应http请求,并返回给浏览器;
4.浏览开始渲染:
。根据html,生成DOM TREE
。根据css,生成CSS TREE
。将DOM TREE和CSS TREE结合生成Render Tree
。根据Render Tree渲染页面
。遇到< script>则暂停渲染,优先执行js,然后再继续渲染(因为js执行和渲染引擎公用一个进程,原因是js可能做了一些dom操作,一般会把js放到页面的底部)
。直至把Render Tree渲染页面
十六、diff算法
diff算法:是一种通过同层的树节点进行比较的高效算法。目的就是找出新旧不同虚拟DOM之间的差异,使最小化的更新视图。
比较方式:深度优先,同层比较。 比较只会在同层级进行,不会跨层级比较;比较的过程中,循环从两边向中间比较。
应用场景:在vue中,用作于虚拟DOM渲染成真实DOM的新旧VNode节点比较。
十七、typeof和instanceof
相同点:都是用来判断数据类型。
不同点:typeof返回的是基本类型,instanceof 返回的是布尔值。
typeof可以精准的判断基本数据类型(null除外),但对于引用类型来说,除了函数都会显示object。
instanceof 运算符用于检测构造函数的 prototype 属性是否出现在某个实例对象的原型链上,能够判断一个变量是否某个对象的实例,但instanceof 不能判断出基本数据类型(string、number、boolean、undefined、null)的值的类型。
扩展:Object.prototype.toString.call() 这种方法可以检测内置类型,缺点是不能精准判断自定义对象,对于自定义对象只会返回[object Object]。
十八、
剩下有空补充!