Appearance
自我介绍
关于这块没有事先准备,介绍了时候很简洁,没有说出面试官想要的内容
面试题
把能记得的写下来
在 js 中一个字母等于多少个字节
一般来说英文是 1 个,中文是两个。
队列与栈的区别
队列 只能在队头做删除操作,在队尾做插入操作.而栈只能在栈顶做插入和删除操作。(先进先出)
栈 就是一个桶,后放进去的先拿出来,它下面本来有的东西要等它出来之后才能出来。(后进先出)
堆:是在程序运行时,而不是在程序编译时,申请某个大小的内存空间。即动态分配内存,对其访问和对一般内存的访问没有区别。用来保存一组无序且唯一的引用类型值,可以使用栈中的键名来取得。
栈内存主要用于存储各种基本类型的变量,包括Boolean、Number、String、Undefined、Null,以及对象和变量的指针。
而堆内存主要负责像Object这种变量类型的储存。
浅拷贝和深拷贝
浅拷贝可以简单理解为,发生在栈中的拷贝行为,只能拷贝基本值和引用值的地址。
深拷贝可以简单理解为,同时发生在栈中和堆中的拷贝行为,除了拷贝基本值和引用值的 地址之外,地址的对象也会发生拷贝。
虚拟列表的实现方式
首先会根据数据列表新建一个数组items,这里数组里边有height和offset。 height即是默认高度或者是自定义高度,offset即是当前item的bottom到数据列表顶部的高度。 依此类推最后一项的offset就是所有数据列表的高度之和。
需要创建一个新的标签,将height设置为最后一个的offset,作为页面高度将其撑开可以进行滚动
组件在初始化时,首先获取当前页面的可视化窗口高度和scrollTop属性的值,目前scrolltop为0, 也就是说目前是从items的第0项开始的topIndex。
那么如何来确定最后一项呢?
就是利用可视化窗口的高度和scrolltop高度之和去查找items里边相近于offset的item的下标项为bottomIndex。
根据topIndex到bottomIndex利用sclice对原数据列表进行裁剪,就可以得到初次需要渲染的数据。
在得到初次渲染数据之后,会建立一个新的div标签容器进行来渲染数据到页面,这里需要用到绝对定位和transform去修改translateY的值。这个translateY的值就是得到的数据的第一项的前一项的offset的值
这里为什么会使用transform的tanslateY去修改位置呢,而不是直接修改top? 因为直接修改top会触发页面重新布局,会整体看起来并没有那么丝滑。改变transform或opacity不会触发浏览器重新布局(reflow)或重绘(repaint),只会触发复合(compositions)。而改变绝对定位会触发重新布局,进而触发重绘和复合。transform使浏览器为元素创建一个 GPU 图层,但改变绝对定位会使用到 CPU。 因此translate()更高效,可以缩短平滑动画的绘制时间。
这里translateY的值为什么使用的是当前第一项的前一项的值呢? 因为当前的offset存的值是当前item的bottom到列表顶部的距离,如果设置了当前项的offset就会被顶上去。显示的第一项就会变成了第二项的
在发生滚动的时候,需要监听滚动事件,再次回到回到页面初始化时候的步骤,获取scrollTop属性的值。去分别查找topIndex和bottomIndex利用slice对原数据进行裁剪即可
react 和 vue 的区别
- 1、监听数据变化的实现原理不同
Vue 通过 Object.definePropert 的 getter/setter 以及一些函数的劫持,能精确知道数据变化
React 默认则是通过 diff 算法,将新的数据和老的数据作对比,然后重新渲染。
2、数据流的不同
Vue 使用的是双向绑定数据流 React 使用的是单向绑定数据流,修改的时候并不能直接修改,在类组件中要使用 setState,函数组件要使用定义的更新的函数操作
3、组合不同的功能的方式
Vue 使用 mixins、React 使用 HOC 高阶组件
4、组件通信方式不同
Vue:
- props/$emit 父子组件通信 父->子props,子->父 $on、$emit 获取父子组件实例 parent、children Ref 获取实例的方式调用组件的属性或者方法 父->子孙 Provide、inject 官方不推荐使用,但是写组件库时很常用
- $emit/$on 自定义事件 兄弟组件通信 Event Bus 实现跨组件通信 Vue.prototype.$bus = new Vue() 自定义事件
- vuex 跨级组件通信 Vuex、$attrs、$listeners Provide、inject
React:
props 父组件向子组件通信
props+回调的方式,使用公共组件进行状态提升。子组件向父组件通信
context 跨级组件通信
非嵌套关系的组件通信: 即没有任何包含关系的组件,包括兄弟组件以及不在同一个父级中的非兄弟组件。
- 可以使用自定义事件通信(发布订阅模式),使用 pubsub-js
- 可以通过 redux 等进行全局状态管理
- 如果是兄弟组件通信,可以找到这两个兄弟节点共同的父节点, 结合父子间通信方式进行通信。
- 也可以 new 一个 Vue 的 EventBus,进行事件监听,一边执行监听,一边执行新增 VUE 的 eventBus 就是发布订阅模式,是可以在 React 中使用的;
5、组件使用不用 Vue 组件分为全局注册和局部注册,在 react 中都是通过 import 相应组件,然后模版中引用;
6、指令不同 多了指令系统,让模版可以实现更丰富的功能,而 React 只能使用 JSX 语法;
7、计算属性和 watch 不同 Vue 增加的语法糖 computed 和 watch,而在 React 中需要自己写一套逻辑来实现
8、Vuex 和 Redux 的区别 从表面上来说,store 注入和使用方式有一些区别。在 Vuex 中,$store被直接注入到了组件实例中,因此可以比较灵活的使用:使用dispatch、commit提交更新,通过mapState或者直接通过this.$store 来读取数据。在 Redux 中,我们每一个组件都需要显示的用 connect 把需要的 props 和 dispatch 连接起来。另外,Vuex 更加灵活一些,组件中既可以 dispatch action,也可以 commit updates,而 Redux 中只能进行 dispatch,不能直接调用 reducer 进行修改。
从实现原理上来说,最大的区别是两点:Redux 使用的是不可变数据,而 Vuex 的数据是可变的,因此,Redux 每次都是用新 state 替换旧 state,而 Vuex 是直接修改。Redux 在检测数据变化的时候,是通过 diff 的方式比较差异的,而 Vuex 其实和 Vue 的原理一样,是通过 getter/setter 来比较的,这两点的区别,也是因为 React 和 Vue 的设计理念不同。React 更偏向于构建稳定大型的应用,非常的科班化。相比之下,Vue 更偏向于简单迅速的解决问题,更灵活,不那么严格遵循条条框框。因此也会给人一种大型项目用 React,小型项目用 Vue 的感觉。
401 和 403 的区别
HTTP 状态码
1xx:指示信息类,表示请求已接受,继续处理
2xx:指示成功类,表示请求已成功接受
3xx:指示重定向,表示要完成请求必须进行更近一步的操作
4xx:指示客户端错误,请求有语法错误或请求无法实现
5xx:指示服务器错误,服务器未能实现合法的请求 常见状态码
200 OK:客户端请求成功
301 Moved Permanently:所请求的页面已经永久重定向至新的 URL
302 Found:所请求的页面已经临时重定向至新的 URL
304 Not Modified 未修改。
403 Forbidden:对请求页面的访问被禁止
404 Not Found:请求资源不存在
500 Internal Server Error:服务器发生不可预期的错误原来缓冲的文档还可以继续使用
503 Server Unavailable:请求未完成,服务器临时过载或宕机,一段时间后可恢复正常
1xx(临时响应)表示临时响应并需要请求者继续执行操作的状态码
- 100 - 继续 请求者应当继续提出请求。服务器返回此代码表示已收到请求的第一部分,正在等待其余部分
- 101 - 切换协议 请求者已要求服务器切换协议,服务器已确认并准备切换
2xx(成功)表示成功处理了请求的状态码
- 200 - 成功 服务器已经成功处理了请求。通常,这表示服务器提供了请求的网页
- 201 - 已创建 请求成功并且服务器创建了新的资源
- 202 - 已接受 服务器已接受请求,但尚未处理
- 203 - 非授权信息 服务器已经成功处理了请求,但返回的信息可能来自另一来源
- 204 - 无内容 服务器成功处理了请求,但没有返回任何内容
- 205 - 重置内容 服务器成功处理了请求,但没有返回任何内容
3xx(重定向)表示要完成请求,需要进一步操作;通常,这些状态代码用来重定向
- 300 - 多种选择 针对请求,服务器可执行多种操作。服务器可根据请求者(user agent)选择一项操作,或提供操作列表供请求者选择
- 301 - 永久移动 请求的网页已永久移动到新位置。服务器返回此响应(对 GET 或 HEAD 请求的响应)时,会自动将请求者转到新位置
- 302 - 临时移动 服务器目前从不同位置的网页响应请求,但请求者应继续使用原有位置来进行以后的请求
- 303 - 查看其它位置 请求者应当对不同的位置使用单独的 GET 请求来检索响应时,服务器返回此代码
- 304 - 未修改 自上次请求后,请求的网页未修改过。服务器返回此响应,不会返回网页的内容
- 305 - 使用代理 请求者只能使用代理访问请求的网页。如果服务器返回此响应,还表示请求者应使用代理
- 307 - 临时性重定向 服务器目前从不同位置的网页响应请求,但请求者应继续使用原有的位置来进行以后的请求
4xx(请求错误)这些状态码表示请求可能出错,妨碍了服务器的处理
- 400 - 错误请求 服务器不理解请求的语法
- 401 - 未授权 请求要求身份验证。对于需要登录的网页,服务器可能返回此响应
- 403 - 禁止 服务器拒绝请求
- 404 - 未找到 服务器找不到请求的网页
- 405 - 方法禁用 禁用请求中指定的方法
- 406 - 不接受 无法使用请求的内容特性响应请求的网页
- 407 - 需要代理授权 此状态码与 401(未授权)类似,但指定请求者应当授权使用代理
- 408 - 请求超时 服务器等候请求时发生超时
- 410 - 已删除 如果请求的资源已永久删除,服务器就会返回此响应
- 413 - 请求实体过大 服务器无法处理请求,因为请求实体过大,超出了服务器的处理能力
- 414 - 请求的 URI 过长 请求的 URI(通常为网址)过长,服务器无法处理
5xx(服务器错误)这些状态码表示服务器在尝试处理请求时发生内部错误。这些错误可能是服务器本身的错误,而不是请求出错
- 500 - 服务器内部错误 服务器遇到错误,无法完成请求
- 501 - 尚未实施 服务器不具备完成请求的功能。例如,服务器无法识别请求方法时可能会返回此代码
- 502 - 错误网关 服务器作为网关或代理,从上游服务器无法收到无效响应
- 503 - 服务器不可用 服务器目前无法使用(由于超载或者停机维护)。通常,这只是暂时状态
- 504 - 网关超时 服务器作为网关代理,但是没有及时从上游服务器收到请求
- 505 - HTTP 版本不受支持 服务器不支持请求中所用的 HTTP 协议版本
vue2 和 vue3 的一个区别
vue3 多另一个 setup 语法糖
响应式数据原理不同:
vue2的话是使用了es5的Object.defineProperty(),通过Object.defineProperty()来劫持各个属性的setter和getter,在数据变动时发布消息给订阅者,触发相应的监听回调
Vue3的话是使用了es6的proxy数据代理,劫持的是它里面的那个整个的一个对象,vue3解决了响应式的一个缺陷比如初始化时的递归遍历会造成性能损失;新增或删除属性时需要用户使用Vue.set/delete这样特殊的api才能生效;对于es6中新产生的Map、Set这些数据结构不支持等问题。 vue3是组合式的api,对ts的支持更加友好
vue2 的需要响应式的数据需要写在 data 中,或者后面通过$set 设置响应式,vue 直接使用 reavtive 和 ref 进行响应式数据绑定
生命周期不同
watch 和 computed 的写法不同
组件传值方式写法不同
keep-alive 的写法不同
Teleport传送门
Fragments片段
自定义渲染器
SFC CSS变量
Suspense
改变原数组的方法
splice、pop、push、shift、unshift、sort,reverse copyWithin、fill
vue 的生命周期
下表包含:Vue2 和 Vue3 生命周期的差异
Vue2(选项式 API) | Vue3(setup) | 描述 |
---|---|---|
beforeCreate | - | 实例创建前 |
created | - | 实例创建后 |
beforeMount | onBeforeMount | DOM 挂载前调用 |
mounted | onMounted | DOM 挂载完成调用 |
beforeUpdate | onBeforeUpdate | 数据更新之前被调用 |
updated | onUpdated | 数据更新之后被调用 |
beforeDestroy | onBeforeUnmount | 组件销毁前调用 |
destroyed | onUnmounted | 组件销毁完成调用 |
beforeCreate阶段:vue实例的挂载元素el和数据对象data都是undefined,还没有初始化。
created阶段:vue实例的数据对象data有了,可以访问里面的数据和方法,未挂载到DOM,el还没有
beforeMount阶段:vue实例的el和data都初始化了,但是挂载之前为虚拟的dom节点
mounted阶段:vue实例挂载到真实DOM上,就可以通过DOM获取DOM节点
beforeUpdate阶段:响应式数据更新时调用,发生在虚拟DOM打补丁之前,适合在更新之前访问现有的DOM,比如手动移除已添加的事件监听器
updated阶段:虚拟DOM重新渲染和打补丁之后调用,组成新的DOM已经更新,避免在这个钩子函数中操作数据,防止死循环
beforeDestroy阶段:实例销毁前调用,实例还可以用,this能获取到实例,常用于销毁定时器,解绑事件
destroyed阶段:实例销毁后调用,调用后所有事件监听器会被移除,所有的子实例都会被销毁
Cookise 遭受攻击怎么办
url 参数使用 encodeURIComponent 方法转义 尽量不是有 InnerHtml 插入 HTML 内容 使用特殊符号、标签转义符。 详情请参考
ts 中 interface 和 type 的区别:
相同点:
- 都可以描述
对象
或者函数
- 都允许拓展(extends) 不同点:
- type 可以声明基本类型,联合类型,元组
- type 可以使用 typeof 获取实例的类型进行赋值
- 多个相同的 interface 声明可以自动合并 使用 interface 描述
数据结构
,使用 type 描述类型关系
闭包
它是一个函数作用域可以访问另外一个函数作用域里面的局部变量,好处呢会延长变量的生命周期和使用范围,但他又一个缺点,就是将内存泄漏,因为函数执行之后不会将变量进行销毁
原型和原型链
在JavaScript中是使用构造函数来新建一个对象的,每一个构造函数的内部都有一个 prototype 属性,它的属性值是一个对象,这个对象包含了可以由该构造函数的所有实例共享的属性和方法。当使用构造函数新建一个对象后,在这个对象的内部将包含一个指针,这个指针指向构造函数的 prototype 属性对应的值,在 ES5 中这个指针被称为对象的原型。一般来说不应该能够获取到这个值的,但是现在浏览器中都实现了 proto 属性来访问这个属性,但是最好不要使用这个属性,因为它不是规范中规定的。ES5 中新增了一个 Object.getPrototypeOf() 方法,可以通过这个方法来获取对象的原型。
当访问一个对象的属性时,如果这个对象内部不存在这个属性,那么它就会去它的原型对象里找这个属性,这个原型对象又会有自己的原型,于是就这样一直找下去,也就是原型链的概念。
原型关系:
- 每一个class都是显式原型 prototype
- 每个实例都有隐式原型_proto_
- 实例的_proto指向对应class的prototype
原型:在js中,每当定义一个对象(函数也是对象)时,对象中都会包含一些预定义的属性,其中每个函数对象都有一个protottype属性,这个属性指向函数原型
原型链:函数的原型链对象constructor默认指向函数本身,原型对象出来有原型属性外,为了实现继承,还有一个原型链指针_proto_,该指针是指向上一层的原型对象,二上一层的原型对象结构依然相似、因此可以利用_proto_一直指向Object的原型对象上,而Object原型对象上用Object.prototype.proto = null表示原型链顶端,如此形成了js的原型链,同时所有的js的对象都有Object的基本防范。
当我们访问对象身上的某一个属性的时候,会先在自身访问,如果自身访问不到的话会通过_proto_上进行访问,还有没有访问到的话,她会在他的构造函数上prototype上进行访问,那么把这一层一层访问的过程,这么个链式结构成为原型链
为什么 Vue2 this 能够直接获取到 data 和 methods ?
通过this直接访问到methods里面的函数的原因是:因为methods里的方法通过 bind 指定了this为 new Vue的实例(vm)。
通过 this 直接访问到 data 里面的数据的原因是:data里的属性最终会存储到new Vue的实例(vm)上的 _data对象中,访问 this.xxx,是访问Object.defineProperty代理后的 this._data.xxx。
为什么js是单线程的
首先在js里面可能会做一些耗时的操作,如果主线程里面去执行的话,可能会影响代码执行,js无非就是在两种环境下运行:node环境和浏览器环境。js是单线程,但是我们的浏览器它是多线程的,我们可以再浏览器的其他线程里面去执行这些耗时的操作,当执行完之后,把这些操作放到事件队列里面,有线程的代码执行完成之后,再去事件队列里面拿东西,再去执行这些零碎。
JavaScript的单线程,与它的用途有关。作为浏览器脚本语言,JavaScript的主要用途是与用户互动,以及操作DOM。这决定了它只能是单线程,否则会带来很复杂的同步问题。比如,假定JavaScript同时有两个线程,一个线程在某个DOM节点上添加内容,另一个线程删除了这个节点,这时浏览器无法以哪个线程为准执行结果。
react的filder数据结构
为什么react有了类组件和Hoc组件还会有函数组件和Hooks
在组件之间复用状态逻辑很难
class就把逻辑写死了,缺乏灵活性。组件是需要大量复用的,比如说你定义了一个div组件为Box,一个页面可能有几百个Box这种组件。有些复用的Box组件可能需要自己的逻辑,这个时候class就不好用了。
复杂组件变得难以理解
难以理解的 class,this指向容易搞混
react的diff算法
react和vue的diff算法那个更好
vue2和vue3的不同
vue2和vue3的diff算法
vue2 Options Api和Vue3 Composition API的区别
移动端一个页面在会触底的时候进行分页加载,先加载10条后面再加载10条,一共就20条,但是随着触底的次数会越来越多,数据也会越来越多,假设你已经滚动到了10万条了数据下,此时页面会很卡多,你会怎么操作
apply、call、bind的区别
- 三者都可以改变函数的 this 对象指向。
- 三者第一个参数都是 this 要指向的对象,如果没有这个参数或参数为 undefined 或 null,则默认指向全局 window。
- 三者都可以传参,但是 apply 是数组,而 call 是参数列表,且 apply 和 call 是一次性传入参数,而 bind 可以分为多次传入。
- bind 是返回绑定 this 之后的函数,便于稍后调用;apply 、call 则是立即执行 。
- bind()会返回一个新的函数,如果这个返回的新的函数作为构造函数创建一个新的对象,那么此时 this 不再指向传入给 bind 的第一个参数,而是指向用 new 创建的实例
注意!很多同学可能会忽略 bind 绑定的函数作为构造函数进行 new 实例化的情况
nextTick的使用
微任务和宏任务的
vue2和vue3的响应式原理
介绍节流防抖原理、区别以及应用
节流
:事件触发后,规定时间内,事件处理函数不能再次被调用。也就是说在规定的时间内,函数只能被调用一次,且是最先被触发调用的那次。
防抖
:多次触发事件,事件处理函数只能执行一次,并且是在触发操作结束时执行。也就是说,当一个事件被触发准备执行事件函数前,会等待一定的时间(这时间是码农自己去定义的,比如 1 秒),如果没有再次被触发,那么就执行,如果被触发了,那就本次作废,重新从新触发的时间开始计算,并再次等待 1 秒,直到能最终执行!
使用场景
:
节流:滚动加载更多、搜索框搜的索联想功能、高频点击、表单重复提交……
防抖:搜索框搜索输入,并在输入完以后自动搜索、手机号,邮箱验证输入检测、窗口大小 resize 变化后,再重新渲染。
js
/**
* 节流函数 一个函数执行一次后,只有大于设定的执行周期才会执行第二次。有个需要频繁触发的函数,出于优化性能的角度,在规定时间内,只让函数触发的第一次生效,后面的不生效。
* @param fn要被节流的函数
* @param delay规定的时间
*/
function throttle(fn, delay) {
//记录上一次函数触发的时间
var lastTime = 0;
return function(){
//记录当前函数触发的时间
var nowTime = Date.now();
if(nowTime - lastTime > delay){
//修正this指向问题
fn.call(this);
//同步执行结束时间
lastTime = nowTime;
}
}
}
document.onscroll = throttle(function () {
console.log('scllor事件被触发了' + Date.now());
}, 200);
/**
* 防抖函数 一个需要频繁触发的函数,在规定时间内,只让最后一次生效,前面的不生效
* @param fn要被节流的函数
* @param delay规定的时间
*/
function debounce(fn, delay) {
//记录上一次的延时器
var timer = null;
return function () {
//清除上一次的演示器
clearTimeout(timer);
//重新设置新的延时器
timer = setTimeout(()=>{
//修正this指向问题
fn.apply(this);
}, delay);
}
}
document.getElementById('btn').onclick = debounce(function () {
console.log('按钮被点击了' + Date.now());
}, 1000);
/**
* 节流函数 一个函数执行一次后,只有大于设定的执行周期才会执行第二次。有个需要频繁触发的函数,出于优化性能的角度,在规定时间内,只让函数触发的第一次生效,后面的不生效。
* @param fn要被节流的函数
* @param delay规定的时间
*/
function throttle(fn, delay) {
//记录上一次函数触发的时间
var lastTime = 0;
return function(){
//记录当前函数触发的时间
var nowTime = Date.now();
if(nowTime - lastTime > delay){
//修正this指向问题
fn.call(this);
//同步执行结束时间
lastTime = nowTime;
}
}
}
document.onscroll = throttle(function () {
console.log('scllor事件被触发了' + Date.now());
}, 200);
/**
* 防抖函数 一个需要频繁触发的函数,在规定时间内,只让最后一次生效,前面的不生效
* @param fn要被节流的函数
* @param delay规定的时间
*/
function debounce(fn, delay) {
//记录上一次的延时器
var timer = null;
return function () {
//清除上一次的演示器
clearTimeout(timer);
//重新设置新的延时器
timer = setTimeout(()=>{
//修正this指向问题
fn.apply(this);
}, delay);
}
}
document.getElementById('btn').onclick = debounce(function () {
console.log('按钮被点击了' + Date.now());
}, 1000);
为什么要把style标签放在head
因为浏览器解析HTML文档是自上而下的,这样<style>
标签里的样式就会作用到body里的元素上。如果<style>
标签写在<body>
标签下面,在这之前的元素的样式就不会生效,会导致页面结构出来了,而CSS还没开始渲染。
为什么script标签放在body后面
如果你希望JavaScript的代码在HTML页面的内容加载完成之前就加载, 则将<script>
标签放在<head>
标签里;如果你希望等到页面所有内容加载完成之后,再来加载JavaScript,则将<script>
标签放在<body>
标签里。如果<script>
标签放在<body>
标签之前,会造成页面阻塞。如果<script>
标签涉及DOM操作,应该放在<body>
标签里面的最底部。
overflow有哪些值,默认值是哪个
overflow属性有四个值:visible (默认), hidden, scroll, 和auto。
行内元素可以设置宽高吗,可以设置padding和margin吗
行内元素不能指定宽度和高度,但是可以设置margin,border,padding。
怎么判断变量obj是否是空对象 {}
判断一个对象是否为空对象,本文给出三种判断方法:
1.最常见的思路,for...in... 遍历属性,为真则为“非空数组”;否则为“空数组”
for (var i in obj) { // 如果不为空,则会执行到这一步,返回true
return true
}
return false // 如果为空,返回false
for (var i in obj) { // 如果不为空,则会执行到这一步,返回true
return true
}
return false // 如果为空,返回false
2.通过 JSON 自带的 stringify() 方法来判断:
JSON.stringify() 方法用于将 JavaScript 值转换为 JSON 字符串。
if (JSON.stringify(data) === '{}') {
return false // 如果为空,返回false
}
return true // 如果不为空,则会执行到这一步,返回true
if (JSON.stringify(data) === '{}') {
return false // 如果为空,返回false
}
return true // 如果不为空,则会执行到这一步,返回true
这里需要注意为什么不用 toString(),因为它返回的不是我们需要的。
var a = {}
a.toString() // "[object Object]"
var a = {}
a.toString() // "[object Object]"
3.ES6 新增的方法 Object.keys():
Object.keys() 方法会返回一个由一个给定对象的自身可枚举属性组成的数组。
如果我们的对象为空,他会返回一个空数组,如下:
var a = {}
Object.keys(a) // []
var a = {}
Object.keys(a) // []
我们可以依靠Object.keys()这个方法通过判断它的长度来知道它是否为空。
if (Object.keys(object).length === 0) {
return false // 如果为空,返回false
}
return true // 如果不为空,则会执行到这一步,返回true
if (Object.keys(object).length === 0) {
return false // 如果为空,返回false
}
return true // 如果不为空,则会执行到这一步,返回true
补充
function checkNullObj (obj) {
if (Object.keys(obj).length === 0) {
return false // 如果为空,返回false
}
return true // 如果不为空,则会执行到这一步,返回true
}
function checkNullObj (obj) {
if (Object.keys(obj).length === 0) {
return false // 如果为空,返回false
}
return true // 如果不为空,则会执行到这一步,返回true
}
或
function checkNullObj (obj) {
return Object.keys(obj).length === 0
}
function checkNullObj (obj) {
return Object.keys(obj).length === 0
}
console.log输出顺序
js
setTimeout(()=>{
console.log(1)
},0)
new Promise((resolve,reject)=>{
console.log(2)
setTimeout(()=>{
console.log(3)
resolve()
},0)
}).then((val)=>{
console.log(4)
})
setTimeout(()=>{
console.log(5)
},0)
console.log(6)
// 输出顺序:
setTimeout(()=>{
console.log(1)
},0)
new Promise((resolve,reject)=>{
console.log(2)
setTimeout(()=>{
console.log(3)
resolve()
},0)
}).then((val)=>{
console.log(4)
})
setTimeout(()=>{
console.log(5)
},0)
console.log(6)
// 输出顺序:
实现原生的reduce方法
js
Array.prototype.myReduce = function(callback,prev) {
//判断遍历开始时候的下标
//存在就是循环以0开始,不存在循环就以1(下标)开始
var initialValue = prev === undefined?1:0;
prev = prev === undefined?this[0]:prev;
for(var i = initialValue;i < this.length;i++) {
prev = callback(prev,this[i],i,this);
}
return prev;
}
Array.prototype.myReduce = function(callback,prev) {
//判断遍历开始时候的下标
//存在就是循环以0开始,不存在循环就以1(下标)开始
var initialValue = prev === undefined?1:0;
prev = prev === undefined?this[0]:prev;
for(var i = initialValue;i < this.length;i++) {
prev = callback(prev,this[i],i,this);
}
return prev;
}
手写useDebounce
防抖:触发高频事件后n秒内函数只会执行一次,如果n秒内高频事件再次被触发,则重新计算时间。
function useDebounce(fn, delay, dep = []) {
const { current } = useRef({ fn, timer: null });
useEffect(function () {
current.fn = fn;
}, [fn]);
return useCallback(function f(...args) {
if (current.timer) {
clearTimeout(current.timer);
}
current.timer = setTimeout(() => {
current.fn.call(this, ...args);
}, delay);
}, dep)
}
function useDebounce(fn, delay, dep = []) {
const { current } = useRef({ fn, timer: null });
useEffect(function () {
current.fn = fn;
}, [fn]);
return useCallback(function f(...args) {
if (current.timer) {
clearTimeout(current.timer);
}
current.timer = setTimeout(() => {
current.fn.call(this, ...args);
}, delay);
}, dep)
}
手写useThrottle
节流:高频事件触发,但在n秒内只会执行一次,所以节流会稀释函数的执行频率。
function useThrottle(fn, delay, dep = []) {
const { current } = useRef({ fn, timer: null });
useEffect(function () {
current.fn = fn;
}, [fn]);
return useCallback(function f(...args) {
if (!current.timer) {
current.timer = setTimeout(() => {
delete current.timer;
}, delay);
current.fn.call(this, ...args);
}
}, dep);
}
function useThrottle(fn, delay, dep = []) {
const { current } = useRef({ fn, timer: null });
useEffect(function () {
current.fn = fn;
}, [fn]);
return useCallback(function f(...args) {
if (!current.timer) {
current.timer = setTimeout(() => {
delete current.timer;
}, delay);
current.fn.call(this, ...args);
}
}, dep);
}
将随机数组排列成一个新数组
给定一个长度为 10 的整数类型的数组,例如 [2, 10, 3, 4, 5, 11, 20],将其排列成一个新数组: [[2, 3, 4, 5], [10, 11], [20]],新数组是个二维数组,子数组由连续的数字组成。
js
function format(arr){
/**
* 数组去重 排序
*/
arr = Array.from(new Set(arr)).sort((a, b) => {
return a - b;
});
/**
* 0-9一组
* 10-19一组 类推
*/
var result = [];
arr.forEach(function (val) {
let index = parseInt(val / 10);
if (!result[index]) {
result[index] = [];
}
result[index].push(val);
})
return result
}
var arr = [2, 10, 3, 4, 5, 11, 20]
format(arr)
function format(arr){
/**
* 数组去重 排序
*/
arr = Array.from(new Set(arr)).sort((a, b) => {
return a - b;
});
/**
* 0-9一组
* 10-19一组 类推
*/
var result = [];
arr.forEach(function (val) {
let index = parseInt(val / 10);
if (!result[index]) {
result[index] = [];
}
result[index].push(val);
})
return result
}
var arr = [2, 10, 3, 4, 5, 11, 20]
format(arr)
实现深拷贝方法
js
function checkType(any) {
return Object.prototype.toString.call(any).slice(8, -1)
}
function clone(any){
if(checkType(any) === 'Object') { // 拷贝对象
let o = {};
for(let key in any) {
o[key] = clone(any[key])
}
return o;
} else if(checkType(any) === 'Array') { // 拷贝数组
var arr = []
for(let i = 0,leng = any.length;i<leng;i++) {
arr[i] = clone(any[i])
}
return arr;
} else if(checkType(any) === 'Function') { // 拷贝函数
return new Function('return '+any.toString()).call(this)
} else if(checkType(any) === 'Date') { // 拷贝日期
return new Date(any.valueOf())
} else if(checkType(any) === 'RegExp') { // 拷贝正则
return new RegExp(any)
} else if(checkType(any) === 'Map') { // 拷贝Map 集合
let m = new Map()
any.forEach((v,k)=>{
m.set(k, clone(v))
})
return m
} else if(checkType(any) === 'Set') { // 拷贝Set 集合
let s = new Set()
for(let val of any.values()) {
s.add(clone(val))
}
return s
}
return any;
}
function checkType(any) {
return Object.prototype.toString.call(any).slice(8, -1)
}
function clone(any){
if(checkType(any) === 'Object') { // 拷贝对象
let o = {};
for(let key in any) {
o[key] = clone(any[key])
}
return o;
} else if(checkType(any) === 'Array') { // 拷贝数组
var arr = []
for(let i = 0,leng = any.length;i<leng;i++) {
arr[i] = clone(any[i])
}
return arr;
} else if(checkType(any) === 'Function') { // 拷贝函数
return new Function('return '+any.toString()).call(this)
} else if(checkType(any) === 'Date') { // 拷贝日期
return new Date(any.valueOf())
} else if(checkType(any) === 'RegExp') { // 拷贝正则
return new RegExp(any)
} else if(checkType(any) === 'Map') { // 拷贝Map 集合
let m = new Map()
any.forEach((v,k)=>{
m.set(k, clone(v))
})
return m
} else if(checkType(any) === 'Set') { // 拷贝Set 集合
let s = new Set()
for(let val of any.values()) {
s.add(clone(val))
}
return s
}
return any;
}
vue3为什么使用proxy
因为object.defineproerty是监听对象的属性的,而proxy是整个对象
ts和js的区别
今天来简单说一下js和ts的区别。
首先,它们都是脚本语言。JavaScript 是轻量级的解释性脚本语言,可嵌入到 HTML 页面中,在浏览器端执行。而TypeScript 是JavaScript 的超集(ts是微软开发的开源编程语言),即包含JavaScript 的所有元素,能运行JavaScript 的代码,并扩展了JavaScript 的语法。(ts包含了js的库和函数,ts上可以写任何的js,调用任何的js库,可以在ts中使用原生js语法)。相比于JavaScript ,它还增加了静态类型、类、模块、接口和类型注解方面的功能,更易于大项目的开发。
区别:
1、TypeScript 引入了 JavaScript 中没有的“类”概念
2、TypeScript 中引入了模块的概念,可以把声明、数据、函数和类封装在模块中。
3、js没有重载概念,ts有可以重载
4、ts增加了接口interface、泛型、类、类的多态、继承等
5、ts对比js基础类型上,增加了 void/never/any/元组/枚举/以及一些高级类型
js有的类型:boolean类型、number类型、string类型、array类型、undefined、null
ts新增的类型:tuple类型(元组类型)、enum类型(枚举类型)、any类型(任意类型)
void类型(没有任何类型)表示定义方法没有返回值 never类型:是其他类型(包括null和undefined)的子类型,代表从不会出现的值这意味着声明never变量只能被never类型所赋值
js变量是没有类型的,即age=18,age可以是任何类型的,可以继续给age赋值为age=”aaa” Ts有明确的类型(即:变量名:number(数值类型)) eg:let age: number = 18
ts需要静态编译,它提供了强类型与更多面向对象的内容。 ts最终仍要编译为弱类型,基于对象的原生的js,再运行。故ts相较java/C#这样天生面向对象语言是有区别和局限的 ts是由微软牵头主导的,其语法风格与概念主要来自C#,理解起来学过java的更容易理解(c#我没学过) ts优势
1、类型化思维方式,使开发更严谨,能帮助开发人员检测出错误并修改,提前发现错误,减少改Bug时间
2、类型系统提高了代码可读性,便于开发人员做注释,维护和重构代码更加容易
3、补充了接口、枚举等开发大型应用时JS缺失的功能
【JS的类型系统存在"先天缺陷",绝大部分错误都是类型错误(Uncaught TypeError)】
4、TypeScript工具使重构更变的容易、快捷。
5、类型安全功能能在编码期间检测错误,这为开发人员创建了一个更高效的编码和调试过程。
setTimeout和setInterval的区别
setTimeout和setInterval都属于JS中的定时器,可以规定延迟时间再执行某个操作,不同的是setTimeout在规定时间后执行完某个操作就停止了,而setInterval则可以一直循环下去。
cookie 设置Samesite
在设置 cookie 属性的时候设置 Samesite ,限制 cookie 不能作为被第三方使用,从而可以避免被攻击者利用。Samesite 一共有两种模式,一种是严格模式,在严格模式下 cookie 在任何情况下都不可能作为第三方 Cookie 使用,在宽松模式下,cookie 可以被请求是 GET 请求,且会发生页面跳转的请求所使用。
useCallback 和 useMemo 第二个参数不传或者空数组 会发生什么
应该是只会记住第一次的内容,后续渲染也是用第一次的结果