前端面试准备

1.函数防抖与节流

  • 概念(作用相同:限制任务触发的频率)

 函数防抖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)
}

 2.修改点击事件的执行顺序

 执行双击事件(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。第一次单击任务不执行了,是被定时器延时,然后第二次点击的时候给清除了。那么第二次点击事件呢?在两次单击之后,会立马执行一个双击事件,双击事件的一开头就把这个第二次点击事件给清除了。这就是双击事件的大概过程。

3.BFC 

  • 什么是BFC:块级格式化上下文

  • 特性:

    BFC是一个块级元素,块级元素在垂直方向上依次排列。
    BFC是一个独立的容器,内部元素不会影响容器外部的元素。
    属于同一个BFC的两个盒子,外边距margin会发生重叠,并且取最大外边距。
    计算BFC高度时,浮动元素也要参与计算。

  • 如何创建BFC(条件):

    给父级元素添加以下任意样式
    overflow: hidden;
    display: flex;
    display: inline-flex;
    display: inline-block;
    position: absolute;
    position: fixed;
    表格单元格:table-cell

  • BFC有什么作用:

    1.解决外边距的垂直塌陷问题
    2.解决当父级元素没有高度时,子级元素浮动会使父级元素高度塌陷的问题
    3.解决子级元素外边距会使父级元素塌陷的问题(也可以通过将子级元素的margin-top改为父级元素的padding-top来解决)
    4.BFC可以阻止标准流元素被浮动元素覆盖(可实现文字环绕)

4.重排(reflow)和重绘(repaint) 

 重排必然导致重绘,重绘不一定重排
  原因:

  添加、删除或更改DOM元素:这些操作会导致浏览器重新计算元素的位置、大小等信息,从而触发重排。
        修改元素的尺寸、位置、边距、填充、边框等样式属性:这些操作同样会导致浏览器重新计算元素的布局信息,从而触发重排。
        
        修改页面的字体大小、样式等:这些操作会影响页面的渲染,从而触发重排或重绘。
        用户交互事件:这些事件会触发页面的重排和重绘。例如,窗口大小改变会导致页面重新布局,从而触发重排;滚动则会导致页面重新绘制,从而触发重绘。

 如何减少重排和重绘:

 1.避免频繁的DOM操作
 2.使用CSS3动画
 3.避免使用table布局
 4.避免频繁的重复样式
 5.使用position:absolute或fixed定位
 6.将样式表放在头部等方法

 5.display属性常见的属性值及含义是什么?

 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弹性布局

  •  容器样式属性:

以下 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:1 表示什么

flex属性是flex-grow,flex-shrink和flex-basis的简写,默认值为0 1 auto。

7. ES6的常用语法

1、块级作用域(let、const)

块级作用域,就是有{}括号中可用范围,不像之前var定义的变量都是函数作用域。
let定义的变量可以改变值,const定义的都是静态变量,不可以修改的。但是像数组,只对数据进行push操作的话,也可以定义成const的。 

2、模版字符串

反引号(``),变量(${}) 

3、解构赋值

从数组和对象中提取值 ,对变量进行赋值 ,这被叫作解构赋值。
// 数组解构
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); 

4、箭头函数

箭头函数作为普通函数的一个补充,将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

5、函数默认参数

 function(a, b=0){   //如果b为空,默认b等于0
  }

6. 剩余参数&扩展运算符

语法都是…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];
剩余参数是把参数转成数组,扩展运算符是把数组转成非数组。

7、对象新增方法,Object.assign()

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 }

8、Set和Map数据结构 


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的一种键值对集合

 9、最后其他es6常用的语法

es6模块化 中 import和export 用法 (前端工程化以及如何通过Node.js中babel来编译es6模块化代码)
es6的 promise对象 (JavaScript进阶之Ajax的问题和什么是promise)
asyn 和 await 函数 (fetch和axios接口调用方式的用法)

8.for...in 和 for...of的区别

for...in  只能获得对象的键名   循环主要是为了遍历对象而生,不适用于遍历数组
for...of  遍历获得键值  循环可以用来遍历数组、类数组对象,字符串、Set、Map 以及 Generator 对象

9. js数据类型

基本数据类型 :

Number,String,Boolean,Undefined,Null,BigInt,
Symbol(Symbol表示一个独一无二的值,用于创建对象属性的唯一标识符)

引用数据类型 :Object,Array,Function,Date,RegExp(正则)


null 与 undefined 区别

    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))

 10.数组的reduce方法

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 }] 
	// }

11.forEach

 在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就停止吧
}

 12.筛选两个数组不同的值

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'}]

 13.setTimeout

 setTimeout(f,0) 作用:把f放到运行队列的最后去执行。

 setInterval 有两个缺点:

 ● 使用 setInterval 时,某些间隔会被跳过;
● 可能多个定时器会连续执行;

 setTimeout实现setInterval :

function mySetInterval(fn, wait) {
    let timer = null
    function interval() {
        timer = setTimeout(() => {
            fn()
            interval()
        }, wait);
    }
    interval()
    return {
        cancel() {
            clearTimeout(timer)
        }
    }
}

 用setInterval实现setTimeout:

function mySetTimeout(fn, wait) {
    const timer = setInterval(() => {
        fn()
        clearInterval(timer)
    }, wait);
}

 14.改变this指向的方法

 (1)、call方法

1、call()方法可以进行普通函数的调用

2、call()方法可以改变this的指向,如果没有参数,this指向window

3、call()方法可以改变this的指向,如果有一个参数,this指向该参数

4、call()方法可以改变this的指向,如果有多个参数,this指向第一个参数,剩下的是个参数列表(构造函数继承的案例)

(2)、apply方法 

1、 apply()方法可以进行普通函数的调用

2、apply()方法可以改变this的指向,如果没有参数,this指向window

3、apply()方法可以改变this的指向,如果有一个参数,this指向该参数

4、apply()方法可以改变this的指向,如果有多个参数,第一个参数是null或者window,第二个参数是数组

 (3)、bind方法

推荐使用第二种形式,第三种用的相对较少

bind() 函数会创建一个新函数(称为绑定函数),新函数与被调函数(绑定函数的目标函数)具有相同的函数体(在 ECMAScript 5 规范中内置的call属性)。当目标函数被调用时 this 值绑定到 bind() 的第一个参数,该参数不能被重写。绑定函数被调用时,bind() 也接受预设的参数提供给原函数。一个绑定函数也能使用new操作符创建对象:这种行为就像把原函数当成构造器。提供的 this 值被忽略,同时调用时的参数被提供给模拟函数。
        bind()不能进行函数的调用
        可以改变this指向

 15.new一个对象的时候会做些什么 

(1)创建一个新对象;
(2) 将构造函数的作用域赋给新对象(因此 this 就指向了这个新对象) ;
(3) 执行构造函数中的代码(为这个新对象添加属性) ;
(4) 返回新对象。

16.es6的class和es5的function的关系

 ES6中的类只是语法糖,它并没有改变类实现的本质。

ES6定义class中的方法,定义在原型对象上的。与ES5不同的是,这些定义在原型对象的方法是不可枚举的。

ES6类和模块是严格模式下的;

不存在变量提升,保证子类父类的顺序;

es6类继承:子类必须在父类的构造函数中调用super(),这样才有this对象,因为this对象是从父类继承下来的。而要在子类中调用父类的方法,用super关键词可指代父类。 

ES5中类继承的关系是相反的,先有子类的this,然后用父类的方法应用在this上。ES6类继承子类的this是从父类继承下来的这个特性,使得在ES6中可以构造原生数据结构的子类,这是ES5无法做到的。 

17.es5继承

es5继承:es5继承

18.JS 中函数名后面加与不加括号的区别

函数是一个对象,函数名是指向这个对象的指针。
函数名后面加上括号---->就表示立即调用执行这个函数里面的代码。
使用不带圆括号的函数是------>访问函数的指针,而非调用函数。

函数名后面加括号,就直接执行函数返回值。
例如:
函数不加括号的,都是把函数名称作为函数的指针,用于传参,此时不是得到函数的结果,因为不会运行函数体代码。它只是传递了函数体所在的地址位置,在需要的时候好找到函数体去执行。
EX:document.οnmοusedοwn=fx; fx不会立即执行,document.onmousedown事件发生时会调用函数fx
函数只要加括号,是要调用它进行执行的。此时,函数()实际上等于函数的返回值。当然,有些没有返回值,但已经执行了函数体内的行为
EX:document.οnmοusedοwn=fx(); 只调用fx,和document.onmousedown事件无关

19.为什么JavaScript是单线程的? 

 因为JavaScript的主要用途是和用户互动和操作DOM的,如果用户添加一个信息然后再去删除,肯定是先生成这个信息才能去进行删除操作的,单线程的操作代步同时时间只能进行一件事,那么有些任务是非常耗时的,会阻塞代码的执行,比如你在看一个打开一个视频的时候,可能视频加载需要时间,但是你依旧可以进行点赞和投币操作的,所以我们把代码又分成了同步代码和异步代码,同步代码是会立即加入JS引擎(js主线程)执行,并且会原地等待,而异步代码是先放入宿主环境中(一般是游览器环境来处理异步任务),不必等待,不阻塞主线程,异步结果将来执行

20.什么是宏任务?什么是微任务? 事件循环?

代码分为同步代码和异步代码,异步代码又分为宏任务和微任务
        宏任务包括: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)

21.跨域问题 

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不同即为跨域。 

    解决跨域: 

  1. 设置浏览器禁止检查--disabled-web-security --user-data-dir,设置成功,跨域请求可被正常发送出去。
  2.  jsonp:浏览器限制了页面直接向非同源的服务器发送ajax请求,但是并没有限制页面向非同源的服务器请求JS脚本,就像我们可以使用标签请求任意域上图片一样。(实际上是在<html>的<head>中动态添加了一条<script>标签。)(需要后端)
            eg:
                $.ajax({
                    url:"https://www.imooc.com/index/getstarlist",
                    type: "GET",
                    dataType:"jsonp"
                })
            jsonp弊端:
            jsonp需要服务端配合修改,如果调用的接口不是我们自己的服务器上,jsonp就无能无力了;jsonp只能发送get请求;jsonp发送的时script请求,并不是xhr请求,因此xhr的很多优势都不能使用(例如:异步,事件等)
  3.  

    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的网站请求数据。

  4. 使用代理服务器:nginx/webpack中做相应配置 
    服务端请求服务端会跨域吗? 不会
    webpack或者vite中可以使用proxy解决跨域,它解决跨域的原理是什么? 代理服务器
    为什么跨域的时候有时候请求会发两次? 非简单请求
    那为什么非要有这个预检请求呢?
    浏览器限制跨域请求一般有两种方式:
      1. 浏览器限制发起跨域请求
      2. 跨域请求可以正常发起,但是返回的结果被浏览器拦截了
                    一般浏览器都是第二种方式限制跨域请求,那就是说请求已到达服务器,并有可能对数据库里的数据进行了操作,但是返回的结果被浏览器拦截了,那么我们就获取不到返回结果,这是一次失败的请求,但是可能对数据库里的数据产生了影响。
                    为了防止这种情况的发生,规范要求,对这种可能对服务器数据产生副作用的HTTP请求方法,浏览器必须先使用OPTIONS方法发起一个预检请求,从而获知服务器是否允许该跨域请求:如果允许,就发送带数据的真实请求;如果不允许,则阻止发送带数据的真实请求。

22.浏览器多标签页tab之间的通信 

方法一: 使用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,使它们能够共享数据和通信。

23. 说说的vue的响应式原理

 vue2

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 

vue3的响应式原理主要依靠的是ES6新增的 Proxy 以及相配合的 Reflect,需要在Proxy的实例对象中传入两个参数
【源数据对象,处理对象的方法【get,set,deleteProperty…等】

差距: 不需要在单独的想vue2之中那样需要特意去指定监控某个对象的变化

24.proxy能够监听到对象中的对象的引用吗? 

 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拦截器,并打印相应的信息。

 25.如何让proxy去监听基本数据类型?

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

26.v-if、v-for优先级

vue2中v-for优先级更高,

vue2的优化方案
1.可以用计算属性替换
2.外层嵌套template标签,页面渲染不生成DOM节点,在template标签上使用v-if,在内层使用v-for

vue3中v-if优先级更高

27.v-if、v-show

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,组件的生命周期会怎么执行?

  • v-if值为false时,在该位置创建一个注释节点,用来标识元素在页面中的位置。在值发生改变的时候,通过diff,新旧组件进行patch,创建DOM节点,从而动态显示隐藏。
  • v-show值为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钩子函数。

28.scoped样式击穿

 一个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;
}

29.created,mounted

 created和mounted的区别
    created:实例创建
    mounted:DOM挂载完毕
能在这两个声明周期里面通过this拿到method,data,$refs这些吗?
    created:能拿到method,data
    mounted:能拿到$refs

30.vue的祖孙组件的通信方案

1.父组件props向子组件传值   子组件$emit触发父组件自定义事件并传参数 

2.eventBus事件总线 

 1.创建事件中心管理组件之间的通信

// event-bus.js

import Vue from 'vue'
export const EventBus = new Vue()

2.在某个兄弟组件中引入事件中心,$emit发送事件

3.某个兄弟组件中引入事件中心,$on接受事件

3.依赖注入(父子、祖孙组件之间的通信

provide / inject是Vue提供的两个钩子,和datamethods是同级的。并且provide的书写形式和data一样。 

  • provide 钩子用来发送数据或方法
  • inject钩子用来接收数据或方法
// 依赖注入所提供的属性是非响应式的
//父组件中:
provide() {
 return {
    num: this.num
    app: this
  };
}
data() {
 return {
    num: 1
  };
}

//子组件中:
//inject: ['num']
inject: ['app']
console.log(this.app.num)
//如此可获得父组件的所有属性

4.$refs 

$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>

5. $parent/$children

  • 使用$parent可以让组件访问父组件的实例(访问的是上一级父组件的属性和方法)
  • 使用$children可以让组件访问子组件的实例,但是,$children并不能保证顺序,并且访问的数据也不是响应式的。
  • 可以使用$root来访问根组件的实例
  • $children 的值是数组,而$parent是个对象
  • 在根组件#app上拿$parent得到的是new Vue()的实例,在这实例上再拿$parent得到的是undefined,而在最底层的子组件拿$children是个空数组

 6.$attrs / $listeners(跨代通信)

//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>

31. composition api和公共逻辑、公共组件有何区别?为什么vue3中组件能支持多个根节点?

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

32. 一个vue单文件组件的构建过程

vue-cli搭建,npm run build

33. vuex   状态管理模式

 Vuex 实现了一个单向数据流,在全局拥有一个 State 存放数据,当组件要更改 State 中的数据时,必须通过 Mutation 提交修改信息, Mutation 同时提供了订阅者模式供外部插件调用获取 State 数据的更新。而当所有异步操作(常见于调用后端接口异步获取更新数据)或批量的同步操作需要走 Action ,但 Action 也是无法直接修改 State 的,还是需要通过Mutation 来修改State的数据。最后,根据 State 的变化,渲染到视图上。

34. 虚拟dom , 虚拟dom渲染到页面的时候,框架做了什么动作

虚拟DOM是对DOM的抽象,是一个用js对象来表示真实DOM结构叫法。

为什么要用虚拟DOM?

1.保证性能下限,在不进行手动优化的情况下,提供过得去的性能,首次渲染大量DOM时,慢

页面渲染的流程:解析HTML -> 生成DOM -> 生成 CSSOM -> Layout (布局)-> Paint(绘制) -> Composite(组合)

2. 跨平台:可以很方便的跨平台操作,比如服务端渲染、uniapp等

 真实DOM和虚拟DOM:

  • 真实DOM∶ 生成HTML字符串+重建所有的DOM元素
  • 虚拟DOM∶ 生成vNode+ DOMDiff+必要的dom更新

35.页面从输入URL到展示发生了什么?

1.浏览器的地址栏输入URL并按下回车。

2、浏览器查找当前URL是否存在缓存,并比较缓存是否过期。

3、DNS解析URL对应的IP。

4、根据IP建立TCP连接(三次握手)。

5、HTTP发起请求。

6、服务器处理请求,浏览器接收HTTP响应。

7、渲染页面,构建DOM树。(参考上题页面渲染)

8、关闭TCP连接(四次挥手)。

36. 工程化?webpack?webpack的rule,loader的执行流是怎么样的?有自己写过loader、plugin吗

工程化

1.模块化(js的模块化,css的模块化,其他资源的模块化)
2.组件化(可复用的UI结构、样式、行为)
3.规范化(目录结构划分合理,编码规范化,接口规范化,文档规范化,版本控制规范化)
4.自动化(自动化构建,自动化部署,自动化测试)

loader:加载器 

一个loader只做一件事,多个loader的执行顺序是从右到左、从下到上

loader是针对module级别的,即针对每个待打包的文件进行转换处理,并最终输出打包后的文件。 

module: {
    rules: [
        {
            test: /\.scss$/, 
            use: ['style-loader', 'css-loader', 'sass-loader'] //链式调用loader
        },
        {
            test: /\.js$/, 
            exclude: /node_modules/, 
            loader: 'babel-loader' //单一loader
        }
    ]
}

常见loader

  1. babel-loader: 用于将ES6、ES7等高级语法转换为ES5语法,以便于浏览器兼容。
  2. css-loader: 用于处理CSS文件中的import、url等导入语法。
  3. file-loader: 用于将文件以特定格式打包处理,支持将文件重命名、分离等多种操作。
  4. url-loader: 类似于file-loader, 但将小于指定大小的文件转换为base64格式,以减少请求量。
  5. image-loader: 用于处理图片文件,支持压缩、解析等多种功能。
  6. json-loader: 用于处理JSON格式的文件,支持import导入语法。
  7. xml-loader: 用于处理XML格式的文件。
  8. csv-loader: 用于处理CSV格式的文件。

plugin插件:用于扩展webpack的功能。 

 常用plugin:

  1. html-webpack-plugin  : 生成html文件,而且会自动引入所依赖的打包好的js代码
  2. uglifyjs-webpack-plugin   : 用来缩小(压缩优化)js文件
  3. mini-css-extract-plugin   :取代style-loader的作用且打包的时候打包到css文件中
  4. optimize-css-assets-webpack-plugin  :用于优化css资源的插件。

webpack的module、bundle、chunk分别指的是什么

module是Webpack处理的单个文件,代表了应用程序的组成部分。

bundle是由Webpack生成的最终输出文件,它包含了所有模块的代码和资源。

chunk是逻辑上的代码块,表示一组相互依赖的模块。它可以根据需要进行拆分和加载。

37. 用过node吗?怎么用的?

  • JavaScript有ECMAScript BOM DOM组成

  • node.js 由 ECMAScript 和 Node模块API 组成

  • node是 js 的后端运行环境。

  • 浏览器是 js 的前端运行环境。

  • node环境中没有bom和dom(文档对象的顶级)的概念

38.项目优化:tree-sharking 

tree-sharking是什么 : es6 推出了tree shaking机制,tree shaking就是当我们在项目中引入其他模块时,他会自动将我们用不到的代码,或者永远不会执行的代码摇掉,在Uglify阶段查出,不打包到bundle中。

Tree Shaking 只支持 ESM 的引入方式,不支持 Common JS 的引入方式。
  • ESM: export + import
  • Common JS: module.exports + require
 项目中如何配置tree-shaking?
// 开发环境配置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 哪些代码可以处理

 webpack  tree-sharking 配置

但是!!!!! 

对于那些直接引入到 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
      }
    ]
  },
};

39.一次渲染10万条数据:

 setTimeout

  先将10万条数据10个一堆分成一万堆(相当于转化成二维数组),然后对一万堆数据进行for循环,循环中在setTimeout钟解构赋值,setTimeout间隔时间设置为20*i 一次拿20条

 requestAnimationFrame

 先将10万条数据10个一堆分成一万堆,然后写个方法利用requestAnimationFrame的刷新频率,在requestAnimationFrame中取出一项拼接下一项,递归调用这个函数

 触底加载数据

 先将10万条数据10个一堆分成一万堆(相当于转化成二维数组),加载的时候,把二维数组的第一项取出来,拼接到要展示的表格数据中去,拼接展示以后,二维数组的第一项的数据要删除;触底加载相当于把二维数组的每一项取出来用,用完return停止即可

表格渲染 (用到插件VXETable)
  
虚拟列表实现          

 首屏加载的时候,只加载可视区域内需要的列表项,当滚动发生时,动态通过计算获得可视区域内的列表项,并将非可视区域内存在的列表项删除 

40.分片上传大文件 断点续传  

首先是跟后端约定好,每个分片是多大。

但是我们要如何选择一个合适的分片呢?因此我们要考虑如下几个事情:

1.分片越小,那么请求肯定越多,开销就越大。因此不能设置太小。

2.分片越大,灵活度就少了。

然后要判断文件大小,如果文件还没有一个分片大,那就直接走单文件直接上传的逻辑。否则就走分片上传的逻辑。我们约定的大小是5mb

首先将文件md5加密,获得加密的md5值,然后切片,(字节流)slice方法来切割的。切完片过后呢,开始走上传,这个时候我们做的秒传功能就体现出来了。在第一个分片里带上我们文件的md5值,后端判断,这个文件是否已经上传过,如果上传过,就直接返回一个标识符,就不用继续上传 ,直接秒传成功

假如没有,然后开始上传,上传使用的是并行上传。这里需要判断是并行上传还是串行上传。如果是串行上传的话,就对那个分片数组进行for循环,用async/await进行控制。如果是并行上传,就使用promise.allSettled来控制,这个api可以接收一个promise数组,然后并行执行里面的promise,然后返回一个结果数组,这个数组里面的每一项正好对应了那个promise数组里面的每一项promise的结果。

全部上传完成过后呢,会调用一个接口,在这个接口里后端会返回给我,他有哪些分片没有接收到,在我传给他的第一个分片中,已经告诉了他这个文件一共多少片,然后在上传每一片的时候,会带一个这一片是第几片的参数,也就是index,所以他能知道有哪些分片他没接收到。

如果真的有分片没有接收到。就得走续传的逻辑,这个时候我再重新上传,但是这次的重新上传,就只会上传上一次上传失败的那些分片,而不是全部重新上传。这次上传完过后,再去请求那个最后的接口,让后端告诉我他接收完了吗。如果接收完了,文件上传就结束了。如果没接收完。还是继续

41.vue常用的修饰符

 http://t.csdnimg.cn/xFemt  

  • 20
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值