赞
踩
函数防抖debounce:在特定的时间内,没有触发特定条件,就执行一次任务
函数节流throttle:在特定的时间内,无论触发多次条件,仅执行一次任务
函数防抖有可能在很长时间内一次任务都不执行,只有最后一次延时时间达到之后执行一次
函数节流在特定时间内会固定触发一次任务,并且是规律的
函数防抖:
关键字搜索,限制接口调用频率
表单验证,验证邮箱的格式,停止输入时再做验证
函数节流:
onresize(页面放大/缩小)、 onscroll(滚动页面)、 onmousemove(鼠标移动),(一般用于分页加载数据)对于高频事件一般要做触发频率的显示
// 函数防抖(固定时间内没有触发条件,就执行一次)
- // 函数防抖(固定时间内没有触发条件,就执行一次)
- async inputHandle () {
- clearTimeout(this.timer)
- this.timer = setTimeout(async () => {
- let res = await request('goods/qsearch', 'get', {
- query: this.keyword
- })
- this.searchResult = res.data.message
- }, 1000)
- }
-
-
-
- // 函数节流(固定时间内无论触发几次,仅执行一次)
- keywordSearch () {
- if (this.isLoading) {
- // 终止后续代码的执行,终止请求
- return
- }
- this.isLoading = true
- setTimeout(async () => {
- let res = await request('goods/qsearch', 'get', {
- query: this.keyword
- })
- this.searchResult = res.data.message
- // 重新打开发送请求的开关
- this.isLoading = false
- }, 1000)
- }

执行双击事件(dblclick)时,除了触发双击,也触发了两次单击事件(click)
单击(click):mousedown,mouseout,click;
双击(dblclick):mousedown,mouseout,click , mousedown,mouseout,click,dblclick;
- <button onclick="single(event)" ondblclick="double(event)">按钮</button>
- <script>
- var time = 200; //300以上,双击才生效
- var timeOut = null;
-
- function single (e) {
- clearTimeout(timeOut); // 清除第一个单击事件
- timeOut = setTimeout(function () {
- console.log('单击');
- // 单击事件的代码执行区域
- // ...
- }, time)
- }
- function double (e) {
- clearTimeout(timeOut); // 清除第二个单击事件
- console.log('双击')
- // 双击的代码执行区域
- // ...
- }
- </script>
-
- // 释义
- 关于time=200,大家知道js的事件循环机制,点击事件会添加一个任务队列。time=0,也会添加一个任务队列。那么time=0与time=200有什么区别呢?
- 因为第一次单击事件后,主线程没有任何任务,就会立马执行这个单击事件的任务。待第二次单击的时候,假设距离第一次单击事件是150ms, 如果你的定时器小于150ms, 那么第一次的任务队列就会执行完。要想不执行第一次的任务队列,那么定时器时间间隔就必须大于两次单击的时间间隔了。 所以,这个200是酌情值,大于间隔就行。常用值有:200、300、500、1000。第一次单击任务不执行了,是被定时器延时,然后第二次点击的时候给清除了。那么第二次点击事件呢?在两次单击之后,会立马执行一个双击事件,双击事件的一开头就把这个第二次点击事件给清除了。这就是双击事件的大概过程。

BFC是一个块级元素,块级元素在垂直方向上依次排列。
BFC是一个独立的容器,内部元素不会影响容器外部的元素。
属于同一个BFC的两个盒子,外边距margin会发生重叠,并且取最大外边距。
计算BFC高度时,浮动元素也要参与计算。
给父级元素添加以下任意样式
overflow: hidden;
display: flex;
display: inline-flex;
display: inline-block;
position: absolute;
position: fixed;
表格单元格:table-cell
1.解决外边距的垂直塌陷问题
2.解决当父级元素没有高度时,子级元素浮动会使父级元素高度塌陷的问题
3.解决子级元素外边距会使父级元素塌陷的问题(也可以通过将子级元素的margin-top改为父级元素的padding-top来解决)
4.BFC可以阻止标准流元素被浮动元素覆盖(可实现文字环绕)
添加、删除或更改DOM元素:这些操作会导致浏览器重新计算元素的位置、大小等信息,从而触发重排。
修改元素的尺寸、位置、边距、填充、边框等样式属性:这些操作同样会导致浏览器重新计算元素的布局信息,从而触发重排。
修改页面的字体大小、样式等:这些操作会影响页面的渲染,从而触发重排或重绘。
用户交互事件:这些事件会触发页面的重排和重绘。例如,窗口大小改变会导致页面重新布局,从而触发重排;滚动则会导致页面重新绘制,从而触发重绘。
1.避免频繁的DOM操作
2.使用CSS3动画
3.避免使用table布局
4.避免频繁的重复样式
5.使用position:absolute或fixed定位
6.将样式表放在头部等方法
display是CSS中的一个重要属性,用于定义元素在页面上的显示方式
1. block:将元素显示为块级元素。块级元素会独占一行,相邻块级元素会另起一行显示。宽度默认为父元素的100%。
2. inline:将元素显示为内联元素。内联元素不会独占一行,相邻内联元素会在同一行显示。宽度默认由内容决定。
3. inline-block:将元素显示为内联块级元素。内联块级元素不会独占一行,可以设置宽度、高度等属性。
4. none:将元素隐藏,不占据任何空间。元素及其内容不会在页面上显示。
5. flex:将元素显示为弹性容器。可以使用flex布局来控制子元素的排列方式。
6. grid:将元素显示为网格容器。可以使用grid布局来控制子元素的排列方式。
7. table:将元素显示为表格。可以使用表格相关的CSS属性控制元素的布局。
8. inline-table:将元素显示为内联表格。
9. table-cell:将元素显示为表格单元格。
10. table-caption:将元素显示为表格标题。
以下 6 个属性设置在容器上:
flex-direction:决定主轴的方向。
flex-wrap:如果一条轴线排不下,如何换行。
flex-flow:flex-direction 属性和 flex-wrap 属性的简写属性。
justify-content:定义项目在主轴上的对齐方式。
align-items:定义项目在交叉轴上如何对齐。
align-content:定义了多根轴线的对齐方式。如果项目只有一根轴线,该属性不起作用。
以下6个属性设置在项目上。
order:定义项目的排列顺序。数值越小,排列越靠前,默认为 0。(一般用来改变元素的顺序)
flex-grow:定义项目的放大比例,默认为 0,即如果存在剩余空间,也不放大。
flex-shrink:定义了项目的缩小比例,默认为 1,即如果空间不足,该项目将缩小。
flex-basis:定义了在分配多余空间之前,项目占据的主轴空间(main size)。它的默认值为 auto,即项目的本来大小。
flex: flex-grow 、 flex-shrink 和 flex-basis 的简写,默认值为 0 1 auto 。后两个属性可选。
align-self:允许单个项目有与其他项目不一样的对齐方式,可覆盖 align-items 属性。默认值为 auto 。
flex属性是flex-grow,flex-shrink和flex-basis的简写,默认值为0 1 auto。
块级作用域,就是有{}括号中可用范围,不像之前var定义的变量都是函数作用域。
let定义的变量可以改变值,const定义的都是静态变量,不可以修改的。但是像数组,只对数据进行push操作的话,也可以定义成const的。
反引号(``),变量(${})
从数组和对象中提取值 ,对变量进行赋值 ,这被叫作解构赋值。
// 数组解构
let [a,b,c]=[1,2,3];
console.log(c);
// 对象解构
let {name:myname,age}={name:'lily',age:12};
console.log(myname,age)
// 字符串解构
let str = 'qaz';
let [a,b,c]=str;
console.log(a,b,c);
箭头函数作为普通函数的一个补充,将this指向了函数体之外最近一层的this,而不是向普通JS一样将this指向window变量。
- function fn(){
- console.log('real',this) //{a:100}
- var arr = [1,2,3]
- //普通JS
- arr.map(function (item){
- console.log('js',this) //window
- return item + 1
- })
- //箭头函数
- arr.map(item => {
- console.log('es6',this) //{a:100}
- return item + 1
- })
- }
- fn.call({a:100}) //将{a:100}设置为this
function(a, b=0){ //如果b为空,默认b等于0
}
语法都是…arr。不同在于,剩余参数是将一个不定数量的参数表示为一个数组。扩展运算符是将数组(对象)转为用逗号分隔的参数序列。
//剩余参数是把参数转成数组
function func(arg1, ...args){
// arg1 == 1, args == [2,3,4]
}
func(1,2,3,4);
const sum = (...args) =>{
let total = 0;
arrgs.foreach(item => total += item);
return total;
}
//扩展运算符,合并数组
let arr1 = [1,3,5];
let arr2 = [2,4,6];
let arr3 = [...arr1, ...arr2]; // arr3 == [1,3,5,2,4,6];
// 或者
arr1.push(...arr2); // arr1 = [1,3,5,2,4,6];
剩余参数是把参数转成数组,扩展运算符是把数组转成非数组。
const target = { a: 1, b: 2 };
const source = { b: 4, c: 5 };
const returnedTarget = Object.assign(target, source);
console.log(target); // Object { a: 1, b: 4, c: 5 }
console.log(returnedTarget); // Object { a: 1, b: 4, c: 5 }
Set类似于数组,但是成员的值都是唯一的,没有重复的值。
const s = new Set([1,2,3,4,5,5]); // 会剔除重复的值,实际上 s=={1,2,3,4,5}
s.size; // 5, 数据结构的大小
add(value):添加某个值,返回Set结构本身
delete(value):删除某个值,返回一个布尔值,表示删除是否成功
has(value):返回一个布尔值,表该值是否为Set的成员
clear(value):清除所有成员,没有返回值
s.add(6).add(7);++
s.delete(7); //true
s.delete(8); //false
s.has(6); //true
s.has(7); //false
s.clear() // s.size === 0
s.forEach( value => console.log(value)); //遍历s数据结构的值
Map是类似Object的一种键值对集合
es6模块化 中 import和export 用法 (前端工程化以及如何通过Node.js中babel来编译es6模块化代码)
es6的 promise对象 (JavaScript进阶之Ajax的问题和什么是promise)
asyn 和 await 函数 (fetch和axios接口调用方式的用法)
for...in 只能获得对象的键名 循环主要是为了遍历对象而生,不适用于遍历数组
for...of 遍历获得键值 循环可以用来遍历数组、类数组对象,字符串、Set、Map 以及 Generator 对象
Number,String,Boolean,Undefined,Null,BigInt,
Symbol(Symbol表示一个独一无二的值,用于创建对象属性的唯一标识符)
引用数据类型 :Object,Array,Function,Date,RegExp(正则)
undefined 是表示一个未定义或未初始化的值,常用于声明但未赋值的变量,或者访问不存在的属性。
null 是一个被赋予的值,用于表示变量被故意赋值为空。
在判断变量是否为空时,使用严格相等运算符(===),因为 undefined 和 null 在非严格相等运算符(==)下会相等。
null,数组,对象 返回的都是object类型,对于函数类型 返回的则是function
数组是属于Object类型的,也就是引用类型,所以不能用typeof判断其具体类型
const arr = []
1.isArray
Array.isArray(arr) // true
2.instanceof (用来检测 constructor.prototype 是否存在于参数 arr 的原型链上。)
console.log(arr instanceof Array) // true
3.Object.prototype.toString.call()
Object.prototype.toString.call(arr); // "[object Array]"
4.利用构造函数来判断他的原型是否为Array
console.log(arr.constructor === Array)
5.通过 Object.getPrototypeOf()来判断是否为数组类型
console.log(Object.getPrototypeOf(arr)===Array.prototype)
6.通过 isPrototypeOf() 方法来判断是否为数组类型
console.log(Array.prototype.isPrototypeOf(arr))
- accumulator 累计器
- currentValue 当前值
- currentIndex 当前索引
- array 数组
-
- 空数组调用reduce()方法时,如果没有提供初始值参数,则会抛出一个TypeError错误。
- 解决这个问题,可以提供一个初始值参数作为reduce()的第二个参数。
-
- 1.无初始值
- [0, 1, 2, 3, 4].reduce(function(accumulator, currentValue, currentIndex, array){
- return accumulator + currentValue;
- });
- callback 被调用四次,每次调用的参数和返回值如下表:
-
- callback accumulator currentValue currentIndex array return value
- first call 0 1 1 [0, 1, 2, 3, 4] 1
- second call 1 2 2 [0, 1, 2, 3, 4] 3
- third call 3 3 3 [0, 1, 2, 3, 4] 6
- fourth call 6 4 4 [0, 1, 2, 3, 4] 10
- 2.添加初始值
- // 提供初始值为 10
- [0, 1, 2, 3, 4].reduce((accumulator, currentValue, currentIndex, array) => { return accumulator + currentValue; }, 10 );
-
- callback 被调用四次,每次调用的参数和返回值如下表:
-
- callback accumulator currentValue currentIndex array return value
- first call 0 1 1 [0, 1, 2, 3, 4] 1
- second call 1 2 2 [0, 1, 2, 3, 4] 3
- third call 3 3 3 [0, 1, 2, 3, 4] 6
- fourth call 6 4 4 [0, 1, 2, 3, 4] 10
-
- 用途:求和
- var total = [ 0, 1, 2, 3 ].reduce(
- ( acc, cur ) => acc + cur,
- 0
- );
- // total 6
- 累加对象里的值
- let sum = [{x: 1}, {x:2}, {x:3}].reduce(
- (accumulator, currentValue) => accumulator + currentValue.x
- ,0
- );
- console.log(sum) // logs 6
- 二维数组变一维
- var flattened = [[0, 1], [2, 3], [4, 5]].reduce(
- ( acc, cur ) => acc.concat(cur),
- []
- );
- // [0, 1, 2, 3, 4, 5]
-
- const names = ['Alice', 'Bob', 'Tiff', 'Bruce', 'Alice'];
- 计算数组中每个元素出现的次数
- let countedNames = names.reduce(function (allNames, name) {
- if (name in allNames) {
- allNames[name]++;
- }
- else {
- allNames[name] = 1;
- }
- return allNames;
- }, {});
- // countedNames is:
- // { 'Alice': 2, 'Bob': 1, 'Tiff': 1, 'Bruce': 1 }
- 按属性对object分类
- var people = [
- { name: 'Alice', age: 21 },
- { name: 'Max', age: 20 },
- { name: 'Jane', age: 20 }
- ];
-
- function groupBy(objectArray, property) {
- return objectArray.reduce(function (acc, obj) {
- var key = obj[property]
- if(!acc[key]){
- acc[key] = []
- }
- acc[key].push(obj)
- return acc
- }, {});
- }
-
- var groupedPeople = groupBy(people, 'age');
- // groupedPeople is:
- // {
- // 20: [
- // { name: 'Max', age: 20 },
- // { name: 'Jane', age: 20 }
- // ],
- // 21: [{ name: 'Alice', age: 21 }]
- // }

在foreach中增加数组元素,不会导致循环增加,循环次数还是原来数组的长度。
- var arr=[1,2,3];
- arr.forEach((item)=>{
-
- if(item==2){
- arr.push(7);
- arr.push(8);
- }
- console.log(item); // 1,2,3
- });
- console.log(arr.length); // 5
和增加不同的是,中数组中减少元素却会减少循环次数,并且删除的元素后面的元素会被“跳过”
- var arr=[1,2,3];
- arr.forEach((item)=>{
- if(item==2){
- arr.splice(1,1);
-
- }
- console.log(item);//1,2
- });
- console.log(arr.length); //2
for循环中退出循环有3种方式:return(终止)、break(退出整个循环)、continue(退出当次循环)。
forEach()本身无法跳出循环,必须遍历所有的数据才能结束。
forEach()只能识别上面三种退出循环中的return,其它都识别不了,且return在forEach()中相当于continue。
forEach()可以通过try{}catch(){}结合throw抛错的方式退出循环:
- const forEachArray = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n']
-
- try {
- forEachArray.forEach((item) => {
- if (item === 'd') {
- throw new Error("到d就停止吧");
- }
- console.log(item)
- })
- } catch (e) {
- console.log(e.message); // 到d就停止吧
- }
- const list1 = [{
- id: 1,
- name: 'z'
- }, {
- id: 2,
- name: 's'
- }]
- const list2 = [{
- id: 1,
- name: 'z'
- }, {
- id: 2,
- name: 't'
- }, {
- id: 3,
- name: 'r'
- }]
- const filterList = list2.filter(item2 => {
- return list1.every(item1 => {
- return item2.name !== item1.name
- // return item2.id !== item1.id
- })
- })
- console.log(filterList); //[{id: 2,name: 't'}, {id: 3,name: 'r'}]

setTimeout(f,0) 作用:把f放到运行队列的最后去执行。
● 使用 setInterval 时,某些间隔会被跳过;
● 可能多个定时器会连续执行;
- function mySetInterval(fn, wait) {
- let timer = null
- function interval() {
- timer = setTimeout(() => {
- fn()
- interval()
- }, wait);
- }
- interval()
- return {
- cancel() {
- clearTimeout(timer)
- }
- }
- }
- function mySetTimeout(fn, wait) {
- const timer = setInterval(() => {
- fn()
- clearInterval(timer)
- }, wait);
- }
1、call()方法可以进行普通函数的调用
2、call()方法可以改变this的指向,如果没有参数,this指向window
3、call()方法可以改变this的指向,如果有一个参数,this指向该参数
4、call()方法可以改变this的指向,如果有多个参数,this指向第一个参数,剩下的是个参数列表(构造函数继承的案例)
1、 apply()方法可以进行普通函数的调用
2、apply()方法可以改变this的指向,如果没有参数,this指向window
3、apply()方法可以改变this的指向,如果有一个参数,this指向该参数
4、apply()方法可以改变this的指向,如果有多个参数,第一个参数是null或者window,第二个参数是数组
推荐使用第二种形式,第三种用的相对较少
bind() 函数会创建一个新函数(称为绑定函数),新函数与被调函数(绑定函数的目标函数)具有相同的函数体(在 ECMAScript 5 规范中内置的call属性)。当目标函数被调用时 this 值绑定到 bind() 的第一个参数,该参数不能被重写。绑定函数被调用时,bind() 也接受预设的参数提供给原函数。一个绑定函数也能使用new操作符创建对象:这种行为就像把原函数当成构造器。提供的 this 值被忽略,同时调用时的参数被提供给模拟函数。
bind()不能进行函数的调用
可以改变this指向
(1)创建一个新对象;
(2) 将构造函数的作用域赋给新对象(因此 this 就指向了这个新对象) ;
(3) 执行构造函数中的代码(为这个新对象添加属性) ;
(4) 返回新对象。
ES6中的类只是语法糖,它并没有改变类实现的本质。
ES6定义class中的方法,定义在原型对象上的。与ES5不同的是,这些定义在原型对象的方法是不可枚举的。
ES6类和模块是严格模式下的;
不存在变量提升,保证子类父类的顺序;
es6类继承:子类必须在父类的构造函数中调用super(),这样才有this对象,因为this对象是从父类继承下来的。而要在子类中调用父类的方法,用super关键词可指代父类。
ES5中类继承的关系是相反的,先有子类的this,然后用父类的方法应用在this上。ES6类继承子类的this是从父类继承下来的这个特性,使得在ES6中可以构造原生数据结构的子类,这是ES5无法做到的。
es5继承:es5继承
函数是一个对象,函数名是指向这个对象的指针。
函数名后面加上括号---->就表示立即调用执行这个函数里面的代码。
使用不带圆括号的函数是------>访问函数的指针,而非调用函数。
函数名后面加括号,就直接执行函数返回值。
例如:
函数不加括号的,都是把函数名称作为函数的指针,用于传参,此时不是得到函数的结果,因为不会运行函数体代码。它只是传递了函数体所在的地址位置,在需要的时候好找到函数体去执行。
EX:document.οnmοusedοwn=fx; fx不会立即执行,document.onmousedown事件发生时会调用函数fx
函数只要加括号,是要调用它进行执行的。此时,函数()实际上等于函数的返回值。当然,有些没有返回值,但已经执行了函数体内的行为
EX:document.οnmοusedοwn=fx(); 只调用fx,和document.onmousedown事件无关
因为JavaScript的主要用途是和用户互动和操作DOM的,如果用户添加一个信息然后再去删除,肯定是先生成这个信息才能去进行删除操作的,单线程的操作代步同时时间只能进行一件事,那么有些任务是非常耗时的,会阻塞代码的执行,比如你在看一个打开一个视频的时候,可能视频加载需要时间,但是你依旧可以进行点赞和投币操作的,所以我们把代码又分成了同步代码和异步代码,同步代码是会立即加入JS引擎(js主线程)执行,并且会原地等待,而异步代码是先放入宿主环境中(一般是游览器环境来处理异步任务),不必等待,不阻塞主线程,异步结果将来执行
代码分为同步代码和异步代码,异步代码又分为宏任务和微任务
宏任务包括:script(整体代码)、seTimeout、setInterval、setImmediate、I/O(Ajax的请求和数据传给后台都是宏任务)等
微任务:promise.then() catch() (promise本身是同步的,异步的是他的then和catch)process.nextTick()(node)、async/await、object.observe等等
什么是js的事件循环,同时也解释为什么会有事件循环:
1.JS是单线程,防止代码阻塞,我们把代码(任务):同步和异步
2.同步代码给js引擎执行,异步代码交给宿主环境
3.同步代码放入执行栈中,异步代码等待时机成熟送入任务队列排队
4.执行栈执行完毕,会去任务队列看是否有异步任务,有就送到执行栈执
行,反复循环查看执行,这个过程是事件循环(eventloop)
http:(协议)//www.example.com(域名):8080(端口号)/role/list(虚拟目录地址)?name=123&password=123456(参数部分)#people(锚部分)
DNS域名解析:将域名解析成对应了IP地址
比如www.bilibili.com你可能一会就记住了但是如果是www.192.168.31.1.com辨识度就非常低也不容易区分
当一个请求url的协议、域名、端口三者之间的任意一个与当前页面url不同即为跨域。
cors:(跨域资源共享)是一种允许当前域的资源被其他域的脚本请求访问的机制 (需要后端)
请求方法为:
简单请求:
HEAD、GET、POST中的一种。
HTTP请求头中字段不超过:Accept、Accept-Language、Content-Language、Last-Event-ID
Content-Type字段值为application/x-www-form-urlencoded、multipart/form-data、text/plain中的一种。
非简单请求:
请求方法为put、delete.
发送JSON格式的ajax请求。
http中带自定义请求头。
对于简单请求:
浏览器发现是跨域请求,就会自动在请求头中加上Origin字段,代表请求来自哪个域(协议+主机名+端口号)。服务器在收到请求后,根据请求头中Origin字段值来判断是否允许跨域请求通过。具体实现方法是:在响应头Access-Control-Allow-Origin字段中设置指定的域名,表示允许这些域名的跨域请求。如果请求头中Origin字段的域名包含在这些域名中,则可以实现跨域请求(当然有时候还需要结合其他字段来判断),否则不通过。
(Access-Control-Allow-Credentials字段代表服务器允许cookie可以包含在请求中,一起发送给服务器,值为布尔类型。如果要把cookie一起发送到服务器,还需要在请求中打开withCredentials属性。注意:如果要发送cookie,Access-Control-Allow-Origin的值不能为“*”,只能是具体的域名。)
对于非简单请求:
非简单请求在发送http请求时,会预先发送一次“预检”(OPTIONS)请求。预检请求会事先询问服务器,当前域名是否在服务器允许的范围内,以及可以使用哪些HTTP动词和头信息字段。只有得到肯定答复后,浏览器才会真正发出http请求,否则就会报错。
CORS相比较JSONP的优势:
CORS支持所有类型的http请求,jsonp只支持get请求。JSONP的优势在于支持老式浏览器,以及向不支持CORS的网站请求数据。
使用代理服务器:nginx/webpack中做相应配置
服务端请求服务端会跨域吗? 不会
webpack或者vite中可以使用proxy解决跨域,它解决跨域的原理是什么? 代理服务器
为什么跨域的时候有时候请求会发两次? 非简单请求
那为什么非要有这个预检请求呢?
浏览器限制跨域请求一般有两种方式:
1. 浏览器限制发起跨域请求
2. 跨域请求可以正常发起,但是返回的结果被浏览器拦截了
一般浏览器都是第二种方式限制跨域请求,那就是说请求已到达服务器,并有可能对数据库里的数据进行了操作,但是返回的结果被浏览器拦截了,那么我们就获取不到返回结果,这是一次失败的请求,但是可能对数据库里的数据产生了影响。
为了防止这种情况的发生,规范要求,对这种可能对服务器数据产生副作用的HTTP请求方法,浏览器必须先使用OPTIONS方法发起一个预检请求,从而获知服务器是否允许该跨域请求:如果允许,就发送带数据的真实请求;如果不允许,则阻止发送带数据的真实请求。
方法一: 使用localStorage实现通信
1、同域数据共享,跨域数据无法共享;
2、持久化数据存储
3、提供storage事件监听localStorage变化
方法二:使用cookie
cookie特点:
1、同域数据共享,跨域数据无法共享
2、存储空间较小,4kb
3、请求时会自动携带cookie
方法三:使用websocket
websocket特点:
1、保持连接状态,而http协议是无状态连接即请求完毕会关闭连接
2、全双工通信,客户端和服务端平等对待,可相互通信
3、建立在TCP协议上
4、没有同源策略,可以跨域
方法四:使用SharedWorker:SharedWorker (一种在多个浏览器上下文之间共享脚本执行的机制)
它可以在不同的标签页之间进行通信。可以创建一个SharedWorker,然后在各个标签页中连接到该SharedWorker,使它们能够共享数据和通信。
vue2的响应式原理主要使用的是Object.defineProperty( ),里面需要传入三个参数,分别是:
【响应源数据的对象,源数据中的需要读写的属性,相对应的对象方法(包含了get和set方法)】
Vue2实现响应式的原理核心之一是利用Object.defineProperty()函数,他的主要作用就是数据的劫持/代理,既然要实现响应式,那么就需要有触发者和响应者,数据是触发者,那么Object.defineProperty()就是监视数据的变化,主要通过set和get方法,在我们访问数据的时候会触发get方法,例如obj.a,当我们修改数据的时候就会触发set方法,例如obj.a=6,这样我们就能知道数据在变化了,但是由于set修改后的值无法及时被get捕捉到,我们就需要让get的return值返回一个变量,而且是一个需要保留的变量,下次访问还希望存在,但同时不希望声明成全局变量污染环境,于是用到了闭包,我们将Object.defineProperty()函数封装成defineReactive()函数方法,我们在函数内全局声明var tep 将他作为get的返回值,这样数据就实时更新了,到此我们就及时捕捉到数据的变化了,换而言之就是数据能够作为触发者了
总结就是:在get函数的地方通过watcher收集依赖存储到dep列表中,然后set函数更新的时候dep循环通知依赖的watcher,然后watcher根据回调去dom做视图的更新
vue3的响应式原理主要依靠的是ES6新增的 Proxy 以及相配合的 Reflect,需要在Proxy的实例对象中传入两个参数
【源数据对象,处理对象的方法【get,set,deleteProperty…等】
差距: 不需要在单独的想vue2之中那样需要特意去指定监控某个对象的变化
Proxy可以监听到对象中的对象的引用。
当使用Proxy包装一个对象时,可以为该对象的任何属性创建一个拦截器,包括属性值为对象的情况。
- const obj = {
- nestedObj: { foo: 'bar' }
- };
-
- const handler = {
- set(target, property, value) {
- console.log(`Setting property '${property}' to '${value}'`);
- target[property] = value;
- return true;
- }
- };
-
- const proxyObj = new Proxy(obj, handler);
-
- proxyObj.nestedObj.foo = 'baz'; // 输出: Setting property 'foo' to 'baz'
-
- 我们通过Proxy创建了一个代理对象proxyObj,它包装了原始的obj。然后,我们对proxyObj中的nestedObj.foo进行赋值操作,这会触发set拦截器,并打印相应的信息。

Proxy无法直接监听基本数据类型,Proxy只能在对象级别上进行操作,而不是基本数据类型。
如果要监听基本数据类型的更改,最好使用其他方式,例如通过触发事件或调用回调函数来通知更改。可以创建一个自定义的数据包装器,将基本数据类型包装在对象中,并在该对象上实现监听逻辑。
- function ValueWrapper(value) {
- this.value = value;
- this.onChange = null;
- }
-
- ValueWrapper.prototype.setValue = function (newValue) {
- this.value = newValue;
- if (typeof this.onChange === 'function') {
- this.onChange(this.value);
- }
- }
-
- const wrapper = new ValueWrapper('Hello');
- wrapper.onChange = newValue => {
- console.log(`Value changed: ${newValue}`);
- }
- wrapper.setValue('Hi');
- // 输出:Value changed:Hi

vue2中v-for优先级更高,
vue2的优化方案
1.可以用计算属性替换
2.外层嵌套template
标签,页面渲染不生成DOM节点,在template
标签上使用v-if
,在内层使用v-for
vue3中v-if优先级更高
v-if和v-show的区别?
v-if:适合在切换不频繁的场景中使用,因为它在渲染DOM时会频繁地创建或销毁元素,影响性能。
v-show:适合在切换频繁的场景中使用,因为它只是改变元素的CSS属性值,无需频繁创建或销毁元素,性能更好。
在Vue2和Vue3中,v-show的优先级高于v-if,因为v-show只需要根据条件设置元素的样式,而不需要频繁地创建/销毁DOM,因此它的优先级更高。
它两在对页面产生重绘重排上面有什么不同?
v-show:引起重排
v-if:引起重绘和重排
分别为一个组件设置v-if和v-show,值由false变成true,或者由true变成false,组件的生命周期会怎么执行?
display:none
来控制元素是否展示。当v-if指令附属于普通元素时,v-if指令状态变化会使得父组件的dom发生变化,父组件将会更新视图,所以会触发父组件的beforeUpdate和updated钩子函数。
当v-if指令令附属于组件时,v-if指令状态变化对父组件的影响和上一条一致,但是对于本身组件的生命周期的影响是不一样的。
v-if从false切换到true时,会触发beforeCreate,created,beforeMount,mounted钩子。
v-if从true切换到false时,会触发beforeDestroy和destroyed钩子函数。
一个style标签拥有scoped属性时,它的CSS样式就只能作用于当前的组件,这样就可以使得组件之间的样式不互相污染。
scope的本质是分别给 HTML 标签和 CSS 选择器添加 data-v-xxx,来达到样式隔离的效果。
使用 scoped 后,父组件的样式将不会渗透到子组件中。
但是父组件可以修改子组件根节点样式。
vue2 >>> :>>> 会被编译成当前父组件的date-v-xxx
vue3 ::v-deep(.class)
- <style>
- .a >>> .b >>> span{
- color:red;
- }
- </style>
-
- // 渲染结果,当有多个时只有最左边的>>>生效
- .a[data-v-xxx] .b >>> span{
- color:red;
- }
created和mounted的区别
created:实例创建
mounted:DOM挂载完毕
能在这两个声明周期里面通过this拿到method,data,$refs这些吗?
created:能拿到method,data
mounted:能拿到$refs
1.创建事件中心管理组件之间的通信
- // event-bus.js
-
- import Vue from 'vue'
- export const EventBus = new Vue()
2.在某个兄弟组件中引入事件中心,$emit发送事件
3.某个兄弟组件中引入事件中心,$on接受事件
provide / inject
是Vue提供的两个钩子,和data
、methods
是同级的。并且provide
的书写形式和data
一样。
provide
钩子用来发送数据或方法inject
钩子用来接收数据或方法- // 依赖注入所提供的属性是非响应式的
- //父组件中:
- provide() {
- return {
- num: this.num
- app: this
- };
- }
- data() {
- return {
- num: 1
- };
- }
-
- //子组件中:
- //inject: ['num']
- inject: ['app']
- console.log(this.app.num)
- //如此可获得父组件的所有属性

$refs进行通讯的弊端:
1.数据流向不清晰,破坏VUE中单向数据流,容易导致状态管理的混乱和难以维护
2.父子组件紧密耦合,可能导致一个组件发生变化另一个组件也无法正常工作
3.组件复用困难,$refs将父子组件绑定在一起,不能实现真正的组件复用
- //子组件中:
- export default {
- data () {
- return {
- name: 'JavaScript'
- }
- },
- methods: {
- sayHello () {
- console.log('hello')
- }
- }
- }
-
-
- //父组件中
- <template>
- <child ref="child"></component-a>
- </template>
- <script>
- import child from './child.vue'
- export default {
- components: { child },
- mounted () {
- console.log(this.$refs.child.name); // JavaScript
- this.$refs.child.sayHello(); // hello
- }
- }
- </script>

$parent
可以让组件访问父组件的实例(访问的是上一级父组件的属性和方法)$children
可以让组件访问子组件的实例,但是,$children
并不能保证顺序,并且访问的数据也不是响应式的。$root
来访问根组件的实例$children
的值是数组,而$parent
是个对象#app
上拿$parent
得到的是new Vue()
的实例,在这实例上再拿$parent
得到的是undefined
,而在最底层的子组件拿$children
是个空数组- //APP.vue
- <template>
- <div id="app">
- //此处监听了两个事件,可以在B组件或者C组件中直接触发
- <child1 :p-child1="child1" :p-child2="child2" @test1="onTest1" @test2="onTest2"></child1>
- </div>
- </template>
- <script>
- import Child1 from './Child1.vue';
- export default {
- components: { Child1 },
- methods: {
- onTest1() {
- console.log('test1 running');
- },
- onTest2() {
- console.log('test2 running');
- }
- }
- };
- </script>
-
- //Child1.vue
- <template>
- <div class="child-1">
- <p>props: {{pChild1}}</p>
- <p>$attrs: {{$attrs}}</p>
- <child2 v-bind="$attrs" v-on="$listeners"></child2>
- </div>
- </template>
- <script>
- import Child2 from './Child2.vue';
- export default {
- props: ['pChild1'],
- components: { Child2 },
- inheritAttrs: false,
- mounted() {
- this.$emit('test1'); // 触发APP.vue中的test1方法
- }
- };
- </script>
-
-
- //Child2.vue
- <template>
- <div class="child-2">
- <p>props: {{pChild2}}</p>
- <p>$attrs: {{$attrs}}</p>
- </div>
- </template>
- <script>
- export default {
- props: ['pChild2'],
- inheritAttrs: false,
- mounted() {
- this.$emit('test2');// 触发APP.vue中的test2方法
- }
- };
- </script>

vue3 Composition Api 组合式API 提高代码逻辑的可复用性,把跟一个功能相关的东西放在一个地方,组合式API的写法见不到this
的使用,减少了this
指向不明的情况
vue2 Options Api 选项式API 上手简单,但是一个功能往往需要在不同的vue配置项中定义属性和方法,在vue2
中是通过Mixins
重用逻辑代码,容易发生命名冲突且关系不清
vue2: vue2流程 模板编译成rander函数 执行rander函数得到虚拟dom 虚拟dom 通过patch函数转换成真实的dom ; vdom 是一个单根树形结构,所以patch在遍历的时候是从根节点开始遍历,patch函数要求它必须是一个单个的数据结构
vue3 : 在vue3中引入fragment概念,在vue3组件中出现多个根节点,则会自动创建fragment节点把组件中的根节点作为自己的children,在patch时如果发现fragment则会开始遍历children
vue-cli搭建,npm run build
Vuex 实现了一个单向数据流,在全局拥有一个 State 存放数据,当组件要更改 State 中的数据时,必须通过 Mutation 提交修改信息, Mutation 同时提供了订阅者模式供外部插件调用获取 State 数据的更新。而当所有异步操作(常见于调用后端接口异步获取更新数据)或批量的同步操作需要走 Action ,但 Action 也是无法直接修改 State 的,还是需要通过Mutation 来修改State的数据。最后,根据 State 的变化,渲染到视图上。
虚拟DOM是对DOM的抽象,是一个用js对象来表示真实DOM结构叫法。
1.保证性能下限,在不进行手动优化的情况下,提供过得去的性能,首次渲染大量DOM时,慢
页面渲染的流程:解析HTML -> 生成DOM -> 生成 CSSOM -> Layout (布局)-> Paint(绘制) -> Composite(组合)
2. 跨平台:可以很方便的跨平台操作,比如服务端渲染、uniapp等
1.浏览器的地址栏输入URL并按下回车。
2、浏览器查找当前URL是否存在缓存,并比较缓存是否过期。
3、DNS解析URL对应的IP。
4、根据IP建立TCP连接(三次握手)。
5、HTTP发起请求。
6、服务器处理请求,浏览器接收HTTP响应。
7、渲染页面,构建DOM树。(参考上题页面渲染)
8、关闭TCP连接(四次挥手)。
1.模块化(js的模块化,css的模块化,其他资源的模块化)
2.组件化(可复用的UI结构、样式、行为)
3.规范化(目录结构划分合理,编码规范化,接口规范化,文档规范化,版本控制规范化)
4.自动化(自动化构建,自动化部署,自动化测试)
loader
是针对module
级别的,即针对每个待打包的文件进行转换处理,并最终输出打包后的文件。
- module: {
- rules: [
- {
- test: /\.scss$/,
- use: ['style-loader', 'css-loader', 'sass-loader'] //链式调用loader
- },
- {
- test: /\.js$/,
- exclude: /node_modules/,
- loader: 'babel-loader' //单一loader
- }
- ]
- }
常见loader
module是Webpack处理的单个文件,代表了应用程序的组成部分。
bundle是由Webpack生成的最终输出文件,它包含了所有模块的代码和资源。
chunk是逻辑上的代码块,表示一组相互依赖的模块。它可以根据需要进行拆分和加载。
JavaScript有ECMAScript BOM DOM组成
node.js 由 ECMAScript 和 Node模块API 组成
node是 js
的后端运行环境。
浏览器是 js
的前端运行环境。
node环境中没有bom和dom(文档对象的顶级)的概念
tree-sharking是什么 : es6 推出了tree shaking机制,tree shaking就是当我们在项目中引入其他模块时,他会自动将我们用不到的代码,或者永远不会执行的代码摇掉,在Uglify阶段查出,不打包到bundle中。
- // 开发环境配置tree-sharking
- // webpack.config.js
- module.exports = {
- // ...
- mode: 'development',
- optimization: {
- usedExports: true,
- }
- };
-
-
- //生产环境配置tree-sharking
- // webpack.config.js 生产环境下只需要把mode配置成‘production’即可
- module.exports = {
- // ...
- mode: 'production',
- };
-
-
- //根据环境的不同进行配置以后,还需要在 package.json 中,添加字段:**sideEffects: false,告诉 Webpack 哪些代码可以处理

但是!!!!!
对于那些直接引入到 js 文件的文件,例如全局的 css,它们并不会被转换成一个 CSS 模块。
- // main.js
- import "./styles/reset.css"
例如这样,在打包后,打开页面,就会发现样式并没有应用上,原因在于:上面我们将sideEffects 设置为 false后,所有的文件都会被 Tree Shaking,通过 import 这样的形式引入的 CSS 就会被当作无用代码处理掉。
为了解决这个问题,可以在 loader 的规则配置中,添加 sideEffects: true,告诉 Webpack 这些文件不要Tree Shaking。
- // webpack.config.js
- module.exports = {
- // ...
- module: {
- rules: [
- {
- test: /\.css$/i,
- use: ["style-loader", "css-loader"],
- sideEffects: true
- }
- ]
- },
- };
先将10万条数据10个一堆分成一万堆(相当于转化成二维数组),然后对一万堆数据进行for循环,循环中在setTimeout钟解构赋值,setTimeout间隔时间设置为20*i 一次拿20条
先将10万条数据10个一堆分成一万堆,然后写个方法利用requestAnimationFrame的刷新频率,在requestAnimationFrame中取出一项拼接下一项,递归调用这个函数
先将10万条数据10个一堆分成一万堆(相当于转化成二维数组),加载的时候,把二维数组的第一项取出来,拼接到要展示的表格数据中去,拼接展示以后,二维数组的第一项的数据要删除;触底加载相当于把二维数组的每一项取出来用,用完return停止即可
首屏加载的时候,只加载可视区域内需要的列表项,当滚动发生时,动态通过计算获得可视区域内的列表项,并将非可视区域内存在的列表项删除
首先是跟后端约定好,每个分片是多大。
但是我们要如何选择一个合适的分片呢?因此我们要考虑如下几个事情:
1.分片越小,那么请求肯定越多,开销就越大。因此不能设置太小。
2.分片越大,灵活度就少了。
然后要判断文件大小,如果文件还没有一个分片大,那就直接走单文件直接上传的逻辑。否则就走分片上传的逻辑。我们约定的大小是5mb
首先将文件md5加密,获得加密的md5值,然后切片,(字节流)slice方法来切割的。切完片过后呢,开始走上传,这个时候我们做的秒传功能就体现出来了。在第一个分片里带上我们文件的md5值,后端判断,这个文件是否已经上传过,如果上传过,就直接返回一个标识符,就不用继续上传 ,直接秒传成功
假如没有,然后开始上传,上传使用的是并行上传。这里需要判断是并行上传还是串行上传。如果是串行上传的话,就对那个分片数组进行for循环,用async/await进行控制。如果是并行上传,就使用promise.allSettled来控制,这个api可以接收一个promise数组,然后并行执行里面的promise,然后返回一个结果数组,这个数组里面的每一项正好对应了那个promise数组里面的每一项promise的结果。
全部上传完成过后呢,会调用一个接口,在这个接口里后端会返回给我,他有哪些分片没有接收到,在我传给他的第一个分片中,已经告诉了他这个文件一共多少片,然后在上传每一片的时候,会带一个这一片是第几片的参数,也就是index,所以他能知道有哪些分片他没接收到。
如果真的有分片没有接收到。就得走续传的逻辑,这个时候我再重新上传,但是这次的重新上传,就只会上传上一次上传失败的那些分片,而不是全部重新上传。这次上传完过后,再去请求那个最后的接口,让后端告诉我他接收完了吗。如果接收完了,文件上传就结束了。如果没接收完。还是继续
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。