Live2d Test Env

每天三道面试题~持续更新中。。。。。

1,vue响应式原理是如何实现?有何缺陷?vue3为什么抛弃vue2中的实现响应式方式?如何使用响应式实现数据双向绑定?如何解决数据响应到视图过度执行问题(如何实现精确更新)。

首先,这是一类问题,我在之前的文章有详细介绍

使用JavaScript编写vue指令v-model

手写v-model,订阅发布者模式

i.vue3为何抛弃vue2实现响应式的方式:

1 .Object.defineProperty 无法低耗费的监听到数组下标的变化,导致通过
数组下标添加元素,不能实时响应;
2).Object.defineProperty 只能劫持对象的属性,从而需要对每个对象,每个
属性进行遍历。如果属性值是对象,还需要深度遍历。 Proxy 可以劫持整个
对象, 并返回一个新的对象。
3).Proxy 不仅可以代理对象,还可以代理数组。还可以代理动态增加的属性。

2,react/vue中列表遍历为什么用key,其作用是什么?

为了使遍历对象的时候使得每个项一一对应一个标签(网上的解读有很多,很多情况自己看还不一定看懂,我自己就简单总结了)

每个项一一对应一个标签这个是重点,普通遍历的时候,不给key,没什么问题,有个隐形的问题是效率低下。

virtual dom

要理解diff的过程,先要对virtual dom有个了解,这里简单介绍下。
【作用】

我们都知道重绘和回流,回流会导致dom重新渲染,比较耗性能;而virtual dom就是用一个对象去代替dom对象,当有多次更新dom的动作时,不会立即更新dom,而是将变化保存到一个对象中,最终一次性将改变渲染出来。

为什么不能使用index作为key而是使用对象的唯一表示作为key

	<head>
		<meta charset="UTF-8" />
		<title>姓名案例_计算属性实现</title>
		<!-- 引入Vue -->
		<script type="text/javascript" src="../js/vue.js"></script>
	</head>
	<body>
		<div id="root">
		<button @click='add'>加</button>
		<ul>
			<li v-for="(p,index) in array" :key='index'>{{p.name}}
				<input type="text" >
			</li>
			
		</ul>
	</div>
	</body>
	<script type="text/javascript">
		Vue.config.productionTip = false //阻止 vue 在启动时生成生产提示。
		const vm = new Vue({
			el:'#root',
			data:{
				array:[
					{id:'1',name:'xiao'},
					{id:'2',name:'bai'},
					{id:'3',name:'hahah'},
					{id:'4',name:'wuwuw'}]
			},
			methods: {
				add(){
					let p={id:'5',name:'biee'}
					this.array.unshift(p)
				}
			},
		})
	</script>

初始化input输入:

添加之后结果:

会发现没有实现一一对应关系:原因——input在生成的时候,input使用的是数组的1,2,3,4下标的index,所以它会在上面,此时要想一一对应,得input这里需要使用数组项的唯一标识,这样不管数组加了多少元素,input使用的index和数组项的唯一标识都会一一对应。

<li v-for="(p,index) in array" :key='p.id'>
    {{p.name}}
	<input type="text" >
</li>

 

3,['1', '2', '3'].map(parseInt)执行结果?

数组的map方法:array.map(item,index)。item遍历的数组项,index是数组下标

字符串的方法:string.parseInt(str,num)。str是字符串,num是进制

完整写法:

['1', '2', '3'].map((item,index)=>{
    return parseInt(item,index)

})

结果为:1,NaN,NaN

4,什么是防抖和节流?有什么区别? 如何实现?

防抖就是触发高频事件后 n 秒内函数只会执行一次,如果 n 秒内高频事件再次被触发,
则重新计算时间。
节流就是高频事件触发,但在 n 秒内只会执行一次,所以节流会稀释函数的执行效率。
5,SetMap的区别?
i.Set:是一个集合对象,其元素不可重复。
获取set实例
let set =new Set()

Set实例属性:

size:set容器的元素个数

Set实例的方法

add(value):添加元素

delete(value):删除元素

clear():清空容器

has(value):判断某值是否存在

Set实例可以接收一个数组作为参数
let set =new Set(array)

ii.Map

Map:也是一个容器,元素成键值对存在,键唯一,值不唯一。

获取Map实例

let map =new Map()

let map =new Map(array)

Map实例的方法

set(key,value):添加元素

get(key):通过key获取到value

delete(key):通过key删除键值对

clear():清空容器

has(key):判断某键值对是否存在

6,es5如何实现基础,es实现基础的方式。
es5实现基础通过原型链和构造函数实现
function Person(name, age) {
    this.name = name
    this.age = age
  }
  Person.prototype.setName = function (name) {
    this.name = name
  }

  function Student(name, age, price) {
    Person.call(this, name, age) //得到父类型的属性
    this.price = price
  }
  Student.prototype = new Person()  //得到父类型的方法
  Student.prototype.constructor = Student
  Student.prototype.setPrice = function (price) {
    this.price = price
  }

es6通过extends实现

class person{
.......
}

class son extends person{
........
}

需要重写父类非私有构造方法

7,setTimeout Promise Async/Await
区别
setTimeout,定时器,接收两个参数,一个是回调函数,一个是时间,在运用的时候,遇到setTimeout,会把它放到宏任务堆里面。
Promise:本身是一个立即执行函数,相当于,器本身就是一个同步代码,但是配合then,catch,finally使用的时候,then,catch,finally的函数体是一个微任务、
Async/Await可以实现异步代码同步化,异步同步化过程大致为,当知道到Async/Await函数的时候,拿到执行权,下面还有同步代码的时候,执行权被同步代码获取,此时‘协程’会将同步代码的执行权剥夺跳给它前面的Async/Await函数,Async/Await函数函数执行完之后,它下面的同步代码才会获取到执行权。
8, Async/Await 如何通过同 步的方式实现异步
Async/Await 是一个generate 函数立即执行函数, 利用 generate 函数的特性把异步的 代码写成 同步 的形式。
9,简述一下 Generator 函数(协程,周末才有空搞定这个东西)
10,JS异步解决方案的发展历程以及优缺点。
回调函数
优点:解决了同步的问题(整体任务执行时长);
缺点:回调地狱,不能用 try catch 捕获错误,不能 return ;
Promise
优点:解决了回调地狱的问题;
缺点:无法取消 Promise ,错误需要通过回调函数来捕获;
Generator
特点:可以控制函数的执行。整体任务执行时长,性能不好。
Async/Await 优点:代码清晰,不用像 Promise 写一大堆 then 链,处理了回调地狱的问题;
缺点: await 将异步代码改造成同步代码,如果多个异步操作没有依赖性而使
await 会导致性能上的降低;
11,为什么 Vuex mutation Reduxreducer 中不能做异步操作?
纯函数,给定同样的输入返回同样的输出,可预测性。
12,Vue 中,子组件为何不可以修改父组 件传递的 Prop,如果修改了,Vue 是如何监
控到属性的修改并给出警告的。
因为vue的数据是单向流动的,只能是从父组件流动到子组件。如果修改,vue会通过setter在值改变之前给出警告。
13,如何解决前端跨域问题
开发阶段:vue使用代码,
如果到生产阶段后端都不能处理掉跨域,使用jsonp
(面试时这么说:开发的时候,后端来不及处理跨域的话,使用vue代理(或其他方式),之后到了生成阶段的话,前端是不做跨域处理,这个需要交给后端来弄,前端处理跨域不安全,容易被恶意脚本攻击,实在不行就用jsonp了,只需要映入ajax就行了)
14,讲讲模块化编程和组件化编程。
15,从输入 URL 到页面加载的全过程
1. 浏览器获取用户输入,等待 url 输入完毕,触发 enter 事件;
2. 解析 URL ,分析协议头,再分析主机名是域名还是 IP 地址;
3. 如果主机名是域名的话,则发送一个 DNS 查询请求到 DNS 服务器,获 得主机 IP 地址;
4. 使 用 DNS 获 取 到 主 机 IP 地 址 后 , 向 目 的 地 址 发 送 一 个 (http/https/protocol )请求,并且在网络套接字上自动添加端口信息 http 80 https 443);
5. 等待服务器响应结果;
6. 将响应结果(html )经浏览器引擎解析后得到 Render tree ,浏览器将 Render tree 进行渲染后显示在显示器中,用户此时可以看到页面被渲染。 9. 简述 HTTP2.0 HT
16.this关键字
this指向调用函数的对象,如果函数没有具体对象调用,this将指向window。箭头函数没有this,它会向上级作用域找对象(包裹住箭头函数最近的对象)并指向该对象。
17.闭包
一个函数嵌套另一个函数,且被嵌套函数有变量引用嵌套函数的变量,此时在被嵌套函数内就形成闭包。
闭包的使用场景:组件开发(特别是发布订阅者模式),实现for循环。
18.作用域
作用域:就是变量的作用范围,分为局部作用域和全局作用域(es6增加了块作用域let和const),全局作用域在整个文档有效,局部作用域在某个局域有效。
作用域链:就是变量的查找过程,首先在当前作用域查找,找不到,往上层找,知道找不到报错:
is not defined
19.执行上下文和执行上下文栈
A。执行上下文
首先知道代码按照位置分为全局代码和局部代码,而执行上下文栈就是在代码运行过程中,对全局代码和局部代码的处理过程。对全局代码的处理过程就是全局执行上下文,对局部代码的处理过程就是局部执行上下文
i全局执行上下文
  在执行全局代码前将window确定为全局执行上下文
  对全局数据进行预处理
   
  var定义的全局变量==>undefined, 添加为window的属性
    function声明的全局函数==>赋值(fun), 添加为window的方法
    this==>赋值(window)
开始执行全局代码
ii. 函数执行上下文
在调用函数, 准备执行函数体之前, 创建对应的函数执行上下文对象(虚拟的, 存在于栈中)
对局部数据进行预处理
   
形参变量==>赋值(实参)==>添加为执行上下文的属性
arguments==>赋值(实参列表), 添加为执行上下文的属性
var定义的局部变量==>undefined, 添加为执行上下文的属性
function声明的函数 ==>赋值(fun), 添加为执行上下文的方法
this==>赋值(调用函数的对象)
 开始执行函数体代码
B. 执行上下文栈
1. 在全局代码执行前, JS引擎就会创建一个栈来存储管理所有的执行上下文对象
2. 在全局执行上下文(window)确定后, 将其添加到栈中(压栈)
3. 在函数执行上下文创建后, 将其添加到栈中(压栈)
4. 在当前函数执行完后,将栈顶的对象移除(出栈)
5. 当所有的代码执行完后, 栈中只剩下window
C.在清空栈的过程中会有找出宏任务和微任务
我之前有写过,但是写不出来比这篇文章更好的文章了,
20. 介绍下观察者模式和订阅 - 发布模式的区 别,各自适用于什么场景
观察者模式中主体和观察者是互相感知的,发布 - 订阅模式是借助第三方来实现 调度的,发布者和订阅者之间互不感知。一对多时使用观察者模式,多对多时使用订阅 - 发布模式。
观察者模式:这篇文章有讲到过和使用过
订阅发布者模式:之前写的这边文章有用过

21.数组相关

JavaScript小面试~数组相关的方法和运用(学习笔记)_任性的代码的博客-CSDN博客

22.手写call、apply和bind

i.手写call

call的用法:用于改变this的指向,第一个参数为需要指向的this对象,其余参数表示的是函数参数:

function.call(thisArg, arg1, arg2, ...)
<script>
    function fn(){
        console.log(this.name);
    }
    let obj={
        name:'小智'
    }
    fn.call(obj)
</script>

通过call将this指向obj对象,可以使用obj对象的name属性:

 首先call()是Function原型对象里面的,如果要重写,需要在Function原型对象添加一个新的方法,这里取名:newCall()

Function.prototype.newCall=function(){}

其次,在我们使用call进行this指向的时候,相当在对象内部添加了函数并调用该对象内部的属性。然后使用对象调用方法

let obj={
        name:'小智',
        fn:function(){
            console.log(this.name);
        }
    }
obj.fn()

按照上面的思路,手写的call方法为:

<script>
    Function.prototype.newCall=function(obj){
        console.log(this);
    }
    function fn(){
        console.log(this.name);
    }
    let obj={
        name:'小智',
    }
    fn.newCall(obj)
</script>

我们打印this:

 发现this指向的是fn,这是因为这个fn调用了newCall,name就没有打印出来。此时我们就需要将该this指向obj对象:过程就是将fn添加到obj属性中并调用,即将this赋值给obj自定义属性内(主要需要删除添加的属性,主要是obj原则上不能被修改):

最后需要解决的问题是参数问题和this为空的问题,最终代码:

<script>
    Function.prototype.newCall=function(){
        let array=Array.from(arguments)
        let newArguments=[]
        array[0]= array[0]||window
        array[0].p=this
        for(let i=1;i<array.length;i++){
            newArguments.push(array[i])
        }
        array[0].p(...newArguments)
        delete array[0].p
    }
    function fn(a,b,c,d){
        console.log(this.name,a,b,c,d);
    }
    let obj={
        name:'小智',
    }
    fn.newCall(null,'小妹妹','小弟弟','小哥哥','大哥哥')
</script>

结果为:

 ii.手写apply

这里和call差别不大,只是函数的参数入参的时候是一个数组,可以说是比call更简单:

<script>
    Function.prototype.newApply=function(){
        let array=Array.from(arguments)
        array[0]= array[0]||window
        array[0].p=this
        array[0].p(...array[1])
        delete array[0].p
    }
    function fn(a,b,c,d){
        console.log(this.name,a,b,c,d);
    }
    let obj={
        name:'小智',
    }
    fn.newApply(null,['小妹妹','小弟弟','小哥哥','大哥哥'])
</script>

iii.手写bind

bind使用方式和call类似,bind是将this指向哪个函数,而call的实现思路是将函数指向哪个对象。即对象调用函数的方式。

bind的实现原理:生成一个指向对象的函数

<script>
    Function.prototype.newBind=function(obj){
        let _this=this
        let argOne=Array.from(arguments)
        let array=argOne.shift()
        return function(){
            let argTwo=Array.from(arguments)
            //使用call或者applay修改this指向,可以手写
            _this.apply(obj,[...argOne,...argTwo])
        }
    }
    function fn(a,b,c,d,e){
        console.log(this.name,a,b,c,d,e);
    }
    let obj={
        name:'小智',
    }
    //链式调用
   fn.newBind(obj,'小妹妹','小弟弟','小哥哥','大哥哥')()
</script>

23.JavaScript模块化编程,什么是模块化?你对模块化编程的理解

模块化就是一组特定的函数组,原始写法就是把功能写在一个个函数内,需要的时候就调用。但是为了方便管理,一般情况我们需要把特定函数懂放在一个对象内,在es6之后,可以把具有类似功能的写到一个js文件中,然后通过deport导出来(import导入),需要的时候就使用。

posted @ 2021-09-14 22:27  waywardcode  阅读(37)  评论(0)    收藏  举报