HTML+CSS
- 什么是BFC?如何触发?有何特点?如何解决margin塌陷
- BFC,又称块级格式化上下文,是一个完全独立的空间,可以让空间中的子元素不会影响外边的布局
- 解决了脱离文档流时,高度塌陷;margin边距重叠;两栏布局
- css如何出来溢出?说一下overflow不同值的区别
- hidden、scroll、auto、visible
- 三栏布局有什么实现方式?
- 浮动布局、绝对定位、flex布局、table布局、grid网格布局
- css calc属性作用是什么?主要用于解决什么问题?
- 进行算法运算、单位换算、转换字体大小
- 当我们处理css变量时;为了得到一个新的值;用于不同单元之间的计算;避免重复相同的操作计算
- 有一个固定的长宽div,怎么实现在屏幕上垂直水平居中
- flex:justify-content,align-item;postion:absolute left: 50% top: 50% transform: translate(-50%,-50%);
- 描述一下渐进增强(progressive enhancement)和优雅增强(graceful degradation)
- 渐进增强就是在一个基本功能完成的基础上,往高级方向进行发展兼容,为了就是得到更好的用户体验
- 优雅增强就是在一个完整的功能上,逐步兼容低版本的浏览器
- iframe有哪些优点?缺点?用它来解决什么问题
- 优点:模块分离,便于更改;可增加代码的 重用;减少重载整个页面
- 缺点:会增加页面的请求次数以及http请求
- 描述下css盒子模型
- 内到外:内容、内边距、边框、外边框
- 分类:标准盒模型、怪异盒模型
- html5特性
- 语义化标签、video、audio、canvas、新增本地存储、webworker、websocket
- css3特性
- 选择器(:first-of-type)、新样式(border-radius、box-shadow、border-image)、背景属性(background-clip)、文字(word-wrap)、颜色 (rgba、hsla)、transtion、transform 转换、animation 动画、渐变、
flex弹性布局、Grid栅格布局
- 选择器(:first-of-type)、新样式(border-radius、box-shadow、border-image)、背景属性(background-clip)、文字(word-wrap)、颜色 (rgba、hsla)、transtion、transform 转换、animation 动画、渐变、
- css中选择器的优先级
- * 、div svg|a 、#id 、.cls、 [attr=value]、 :hover、 ::before
- div.abc[class="123"]
- 重绘和回流
- 重绘:是受外观变化而影响的
- 回流:是受布局大小变化而重新排列生成
- 回流必将引起重绘,重绘不一定会引起回流
- 浏览器的渲染流程
- 解析 HTML Source,生成 DOM 树。
- 解析 CSS,生成 CSSOM 树。
- 将 DOM 树和 CSSOM 树结合,去除不可见元素(很重要),生成渲染树( Render Tree )。
- Layout (布局):根据生成的渲染树,进行布局( Layout ),得到节点的几何信息(宽度、高度和位置等)。
- Painting (重绘):根据渲染树以及回流得到的几何信息,将 Render Tree 的每个像素渲染到屏幕上。
javacript
-
JavaScript的基本数据类型
- string、number、null、undifined、Symbol、Bigint、Boolean
-
JavaScript的引用数据类型
- Object(对象)、Array(数组)、Function(函数)、Date(日期时间类型)、RegExp(正则表达式)
-
如何判断javascript的数据类型
-
typeof ,需要注意null检测为object
-
instanceof,主要用在引用类型的判断
function myInstanceof(Fn, obj) { // 获取该函数显示原型 const prototype = Fn.prototype; // 获取obj的隐式原型 let proto = obj.__proto__; // 遍历原型链 while (proto) { // 检测原型是否相等 if (proto === prototype) { return true; } // 如果不等于则继续往深处查找 proto = proto.__proto__; } return false; }
-
-
如何判断空对象?
- 通过JSON.stringify转化为字符串进行比较
- for in 循环,
- Object.keys()方法,返回对象的属性名组成的一个数组,若长度为0,则为空对象(ES6的写法)
- Object.getOwnPropertyNames方法获取对象的属性名,存到数组中,若长度为0,则为空对象
-
0.1+0.2为什么不等于0.3
- 0.1:0.0001 1001 1001 1001......
- 0.2:0.0011 0011 0011 0011 ......
- 0.1+0.2 不等于 0.3 ,因为在 0.1+0.2 的计算过程中发生了两次精度丢失。第一次是在 0.1 和 0.2 转成双精度二进制浮点数时,由于二进制浮点数的小数位只能存储52位,导致小数点后第53位的数要进行为1则进1为0则舍去的操作,从而造成一次精度丢失。第二次在 0.1 和 0.2 转成二进制浮点数后,二进制浮点数相加的过程中,小数位相加导致小数位多出了一位,又要让第53位的数进行为1则进1为0则舍去的操作,又造成一次精度丢失。最终导致 0.1+0.2 不等于0.3
-
强转类型转换、隐式类型转换分别是什么,列举场景说明
-
创建函数的几种方式
- 函数声明式、函数表达式、函数对象等
-
列举宿主对象、内置对象、原生对象并说明其定义
- 宿主对象:就是ECMASript实现的宿主环境提供的对象;如bom、dom
- 原生对象(需要new关键字实例化)与内置对象(global、math)都是独立于宿主环境的、ECMASript实现提供的对象
-
== 和 === 的区别?
- == 只需要判断等号两边的值是否相等,不需要判断类型。值相同则返回true
- === 既要判断两边的值是否相等,也要判断两边的类型是否相等。全等才能返回true
-
null 、undefined的区别
- null == undefinend 相等但不全等
- 在与数值运算的时候,null为0,则undefined为NaN
-
什么情况下会返回undefined值?
- 变量被声明但没有赋值的时候,就会出现undefined
- 对象的某个属性没有复制时,该属性的值为undfined
- 调用函数过程中,应该提供的参数没有提供时,该参数就等于undefined
- 函数没有返回值时,默认返回undefined
-
如何区分数组和对象?
- 通过Array.isArray方法来判断
- 通过instanceof来比较
- 数组上是有length属性的,而对象是没有的
-
多维数组如何降维?
- 通过数组字符串化:array.split
-
如何获取当前的日期
-
什么是类数组,如何将其转化为真实的数组?
- 类数组中会多出calles属性;只有部分object方法,不能调用Array的方法
- slice、Array.from、扩展运算符
-
如何遍历对象的属性
- 通过for in 遍历,但是缺点是
Object.defineProperty的形式来设置对象的描述对象,遍历不出来,其次就是继承的属性会被遍历到 - Object.key()、Object.getOwnPropertynames()、Object.entries()
- 通过for in 遍历,但是缺点是
-
如何给一个按钮绑定两个onclick事件
- addEventListene()
-
变量提升是什么?与函数提升的区别?
- 变量提升是将声明的变量提升到最顶端
- 而函数提升是只针对具名函数进行提升,对于赋值的匿名函数不会
- 区别:就是函数提升要优先于变量提升,且不会被同名变量声明覆盖,但是会被变量赋值后覆盖。而且存在同名函数与同名变量时,优先执行函数
-
什么是作用域链?如何延长?
- 默认情况下,js 代码处于全局作用域(0级),当我们声明一个函数的时候,就会开辟一个局部作用域(1级)。 函数里面也可以声明函数,就会又形成局部作用域(2级),以此类推就会形成作用域链。
- 执行环境的类型只有两种,全局和局部(函数),但是有些语句可以在作用域的前端临时增加一个变量对象,该变量对象会在代码执行后被移除。简而言之,执行以下两个语句时,作用域链都会得到加强
- try-catch 语句的catch 块;会创建一个新的变量对象,包含的是被抛出的错误对象的声明
- with 语句,会将指定的对象添加到作用域链中
-
如何实现数组的随机排序?
- 利用sort+Math.random()
- 创建一个新的数组,从原数组中随机抽取一个元素插入到新数组中,然后返回新数组
- 数组内的元素随机替换位置(类似洗牌算法)
-
dom节点的Attribute和Property有何区别?
- HTML标签中定义的属性和值会保存该DOM对象的attributes属性里面
- DOM有其默认的基本属性,而这些属性就是所谓的**“property”**,无论如何,它们都会在初始化的时候再DOM对象上创建。
- property能够从attribute中得到同步;
- attribute不会同步property上的值;
- attribute和property之间的数据绑定是单向的,attribute->property;
- 更改property和attribute上的任意值,都会将更新反映到HTML页面中;
-
dom结构操作怎样添加、移除、移动、复制、创建和查找节点?
- createDocumentFragment() 创建DOM片段
- createElement() 创建具体元素
- createTextNode() 创建一个文本节点
- appendChild() 添加
- removeChild() 移除
- replaceChild() 替换
- insertBefore() 插入
- getElementsByTagName() 通过标签查找
- getElementsByName () 通过元素name属性
- getElementsByClassName () 通过类名查找
- getElementsById() 通过Id查找
-
什么是事件冒泡,它是如何工作的?如何阻止事件冒泡?
- 当一个元素接收到事件的时候 会把他接收到的事件传给自己的父级,一直到window
- 它是通过有内到外,将事件按顺序触发
- e.stopPropagation() || return false;
-
什么是事件捕获,它是如何工作?
- 与事件冒泡相反,是由外到内,
-
如何让事件先冒泡后捕获?
- 使用事件委托(Event Delegation):将单个事件监听器添加到父元素上,以处理其子元素上的事件。由于事件冒泡会在整个文档中传播,因此在父元素上添加事件监听器可以确保事件先冒泡后获取。
- 使用 setTimeout():将事件处理程序延迟一小段时间再执行,以确保事件有时间传播到父元素。由于事件冒泡是在异步方式下完成的,因此使用
setTimeout()函数可以确保事件先冒泡后获取。
-
JavaScript动画和css3动画有什么区别?
- JavaScript动画是运行在主线程中的,有可能造成阻塞,相对复杂的,同时它动画控制能力强以及效果丰富,兼容性少
- 而css3动画,会在重绘回流中完成;提高动画性能;对动画本身控制能力弱;实现复杂动画,代码过于冗余
-
dom的事件模型
- 事件流:事件捕获阶段->事件目标阶段->事件冒泡阶段
-
事件三要素是什么?
- 事件源:触发(被)事件的元素
- 事件类型:事件的触发方式(例如鼠标点击或键盘点击)
- 事件处理程序:事件触发后要执行的代码(函数形式)
-
获取元素的位置?
- 获取可视区域的大小:clientheight、clientWidth
- 获取目标可视区域在显示器上的坐标位置:offsetTop、offsetLeft
- scrollTop(纵向滚动的距离),scrollLeft(横向滚动的距离)、scrollWidth(内容宽度)、scrollHeight(内容高度)
-
如何绑定事件?如何解除事件?
- 绑定:行内进行绑定;通过元素.on事件=function(){};通过事件监听函数addEventListener
- 解除:元素.on事件= null;对象.removEventListener()
-
对事件委托的理解?
- 给父元素注册事件,委托子元素处理
- 减少对事件的注册,提高的程序的性能
- 1.给目标元素的父元素或者父元素之上的元素绑定事件。
- 2.在父元素或者父元素以上的元素注册的事件 通过事件对象.targe来获取当前点击的那个元素。
- 3.交给子元素去处理。
-
setTimeout和setInterval的区别及用法是什么?
- setTimeout是通过一定时间后执行一次;参数两个(函数体,时间)
- setInterval是通过一定的间隔时间,重复执行;参数两个(函数体,时间)
- 两种定时器的消除方法不同
-
用setTimeout来实现setInterval并实现清楚多个定时器?
function myInterval(func, time) { let ids = []; function fn() { let id = setTimeout(() => { func(); fn(); }, time) ids.push(id); } fn(); return ids; } let id = myInterval(() => { document.write('Hello World'); }, 500) function clearMyInterval(idList) { idList.forEach((id) => { clearTimeout(id); }) } setTimeout(() => { clearMyInterval(id) }, 3000) -
document.write和innerHTML区别?
- docunment.write是会将整个页面进行重绘
- 而innnerHTML只会将部分页面进行重绘
-
元素拖动如何实现,原理是怎样?
-
<body> <div id="rect-1" class="rect"> <header>拖住我</header> </div> </body> <script> const box = document.querySelector(".rect"), header = box.querySelector("header") function move ({movementX,movementY}){ let getStyle = window.getComputedStyle(box); let left = parseInt(getStyle.left); let top = parseInt(getStyle.top); box.style.left = `${left+movementX}px` box.style.top = `${top+movementY}px` }; header.addEventListener('mousedown',()=>{ header.addEventListener('mousemove',move); }) document.addEventListener('mouseup',()=>{ header.removeEventListener('mousemove',move); }) </script> -
原理:监听目标元素被按下移动的鼠标距离,然后设置到目标元素的top和left上,再通过监听鼠标弹起,移除鼠标的移动事件
-
-
什么是重绘?什么 是回流?如何最小化重绘和回流?
- 重绘是指浏览器根据元素的样式进行重新绘制
- 回流是指页面布局发生变化,浏览器需要重新计算元素的位置和大小
- 1.需要对元素进行复杂的操作时,可以先隐藏(display:'none'),操作完成后再现身
- 2.需要创建多个DOM节点时,使用DocumentFragment创建完后一次性的加入document缓存Layout属性值,如:var left=elem.offsetLeft;这样,多次使用left只产生一次回流
- 3.尽量避免使用table布局(table 元素一旦触发回流就会导致table里所有的其他元素回流)
- 4.避免使用css表达式(expresssion),因为每次调用都会重新计算值(包括加载页面)
- 5.尽量使用css属性简写,如:用border 代替 border-width,border-style,body-color
- 6.批量修改元素样式,elem.className和elem.style.cssText代替elem.style.xxx
-
延迟加载的方式有哪些?
- js延迟加载就是等页面加载完成之后在加载JavaScript文件,有助于提高网页的加载速度
- 方式:
- defer属性:表明脚本在执行时,不会影响页面的构造,也就是脚本会被延迟到整个页面都解析完毕之后在执行
- async属性:不让页面等待脚本下载和执行,从而异步加载页面其他内容
- 动态创建DOM方式:将创建DOM的script脚本放置在标签前, 接近页面底部
- 使用setTimeout延迟方法:延迟加载js代码,给网页加载留出更多时间
- 让JS最后加载:把js外部引入的文件放到页面底部,来让js最后引入,从而加快页面加载速度
-
垃圾回收机制有哪些?具体怎么如何执行?
- 垃圾回收算法就是垃圾收集器按照固定的时间间隔,周期性地寻找那些不再使用的变量,然后将其清除或释放内存
- 如何清除:标记清除;引用计数
- 标记清除:首先遍历所有堆内存的所有对象,分别给他们打上标记,然后通过在代码执行过程结束后,对所使用过的变量取消标记。再清除阶段再把具有标记的内存对象进行整体清除,从而释放内存空间。最后通过标记整理算法(标记整理算法会将活着的对象,向一端移动,最后清理掉边界内存),清除掉内存碎片
- 引用计数:
-
什么是内存泄漏?
- 内存泄露是指在js已经分配内存地址的对象由于长时间未进行内存释放或者无法清除,造成了长期占用内存,使得内存资源浪费,最终导致运行的应用响应速度变慢以及最终崩溃的情况
- 造成内存泄漏的原因:
- 过多的缓存。及时清除缓存
- 滥用闭包。尽可能避免使用大量的闭包
- 定时器或回调太多。当不需要setTimeout或setInterval时,定时器没有被清除,定时器的糊掉函数以及其内部依赖的变量都不能被回收,会造成内存泄漏。解决方法:在定时器完成工作时,需要手动清除定时器
- 太多无效的DOM引用。DOM删除了,但是节点的引用还在,导致GC无法实现对其所占内存的回收。解决方法:给删除的DOM节点引用设置为null。
- DOM对象和JS对象相互引用
-
数组遍历的方法有哪些,分别有什么特点,性能如何?
- for循环 (其中将arr.length的长度赋予到临时变量中),则会性能更优
- forEach
- for in
- for of
- map
- every
- some
- reduce
- filter
-
ES5和ES6的区别,ES6新增了什么?
- 新增
- let和const
- 展开运算符
- set对象
- Map对象
- 箭头函数
- 数组新增方法:array.isArray();Array.from();Array.of();arr.find();arr.findIndex();arr.includes();
- 字符串新增方法:str.startswith();str.endswith();str.repeat(times);
- 模板字符串
- babel编译器
- 对象简化方法,
- proxy
- 解构赋值,async,promise
- 新增
-
ES6的继承和ES5的继承有什么区别?
- ES6的继承:在 ES6 中,两个类之间的继承就是通过 extends 和 super 两个关键字实现的。
- ES5的继承:在 ES5 中,构造函数 B 的实例继承构造函数 A 的实例属性是通过 A.call(this) 来实现的
- 第一个是,ES6 中子类会继承父类的属性,第二个区别是,super() 与 A.call(this) 是不同的,在继承原生构造函数的情况下,体现得很明显,ES6 中的子类实例可以继承原生构造函数实例的内部属性,而在 ES5 中做不到
-
var、let、const之前那的区别?暂时性死区如何理解?
- 暂时性死区:就是在没有定义的情况下使用该变量
- var:存在变量提升,可以重复声明,不存在块级作用域
- const:不存在变量提升,不可以重复声明,存在块级作用域
- let:不存在变量提升,变量可以改变,
-
Class、extends是什么,有什么作用?
- class,在面向对象编程中,类是一种创建对象的蓝图和模板。它定义了对象的结构通过成员变量和行为通过方法。通过类你可以创建多个具有相同属性和行为的对象;同时还提供了封装,可以将数据和对这些数据的操作结合在一起。
- Extends,继承是面向对象编程的核心概念,允许你基于另一个类创建一个类。新类继承父类的属性和方法,并可以添加新的属性和方法 或重写继承的方法;继承的主要目的是提供代码的重用,并建立一个自然的分类层次结构
-
什么是js闭包
- 当通过调用外部函数返回的内部函数后,即使外部函数执行完成,但是内部函数引用外部函数的变量,就导致变量依然存放在内存中,我们把这些集合的变量,称为闭包
- 优点:模块化开放(实现共有变量);做缓存;可以封装私有化属性;防止全局变量污染
- 缺点:一旦形成闭包,只有在页面关闭后闭包占用的内存才会被回收,所以造成了内存泄漏
-
说一下类的创建和继承,列举一下你所知道的继承方式
-
类的创建:new一个function,在function的prototype里面增加属性和方法
-
类的继承:
-
原型继承:
function Persion(name){ this.name = name; } Persion.prototype.id = 10 function son (sex){ this.sex = sex; } //son就继承了persion的name属性 son.prototype = new Persion('11') var b = new son() b.name// b.sex// b.id// -
类继承
function Persion(name,age){ this.name = name; this.age = age; } Persion.prototype.id = 10 function Boy(name,age,sex){ Persion.call(this,name,age) this.sex = sex } var b = new Boy('1','2','3') b.name// b.sex// b.id -
混合继承
function Persion(name,age){ this.name = name; this.age = age; } Persion.prototype.id = 10 function Boy(name,age,sex){ Persion.call(this,name,age) this.sex = sex } Boy.prototype = new Persion(); var b = new Boy('1','2','3') b.name// b.sex// b.id // -
寄生组合继承
function Persion(name,age){ this.name = name; this.age = age; } Persion.prototype.id = 10 function Boy(name,age,sex){ Persion.call(this,name,age) this.sex = sex } (function(){ //创建一个空对象 var Super = function(){}; //将实例作为子类的原型 Super.prototype = Persion.prototype; Boy.prototype = new Super(); })() var Boy = new Boy();
-
-
-
如何解决异步回调地狱
-
回调地狱:异步回调函数的层层嵌套
-
Promise是解决异步编程的一种解决方案
const p1 = new Promise((resolve,reject) =>{}) const p2 = new Promise((resolve,reject) =>{}) const p3 = new Promise((resolve,reject) =>{}) p1.then((data) =>{ return p2 }).then((data)=>{ return p3 }).then((data)=>{ console.log(data) })- promise有三种状态:pending、fulfilled、rejected
- pending 到fulfilled,执行resolve() ;pending 到rejected
- 本质是控制异步代码的结果处理的顺序
- Promise的使用:
- 实例化Promise对象:将异步操作放入Promise中
- 调用then()方法:处理异步操作结果
-
异步函数async和await
function getPromise(key){ let p = new Promise((resolve,reject) => { //异步操作 }) return p; } const a = async function (){ let data1 = await getPromise('1') let data2 = await getPromise('2') let data3 = await getPromise('3') }-
async和await异步函数 : 这两个关键字只能用于函数, 所以用的时候一定要放在函数里面用
async关键字: 修饰函数, 表示这个函数内部有异步操作。
await关键字: 等待异步执行完毕
-
使用方式:
- 函数前面使用async修饰
- 函数内部,promise操作使用await修饰
-
-
-
说一下图片的懒加载和预加载
- 预加载:提前加载所需要的图片资源,加载完毕后会缓存到本地,当需要时,可以立马显示出来,以达到在预览的过程中,无需等待直接预览的良好体验
- 缺点是占用较多的后台资源
- 懒加载:需要的时候再去加载资源
- 比较耗性能
- 懒加载主要是监听body或者其他存放图片且滚动的元素scroll事件,每次滚动检查图片是否显示
- 如何检测图片进入可视区域
- 获取可视区高度:window.innerWidth || document.documentElement.clientWidth || document.body.clientWidth
- 当scrollTop+clientHeight >offsetTop
- 预加载:提前加载所需要的图片资源,加载完毕后会缓存到本地,当需要时,可以立马显示出来,以达到在预览的过程中,无需等待直接预览的良好体验
-
mouseover和mouseenter的区别
- mouseover:当鼠标移入元素或其子元素都会被触发事件,所以有一个重复触发,冒泡的过程
- mouseenter:当鼠标移除元素本身(不包含元素的子元素)会触发事件,也就是不会冒泡,
-
new操作符做了哪些事情?
-
新建了一个对象obj;
-
把obj和构造函数通过原型链连接起来
-
将构造函数的this指向obj
function Person(name,age) { this.name = name; this.age = age; } Person.prototype.sayName = function () { console.log(this.name) } function myNew(func,...args){ //创建一个新对象,使用现有的对象来提供新创建的对象的_proto_ const obj = Object.create(func.prototype) let result = func.apply(obj,args) return result instanceof Object ? result : obj } let p = myNew(Person,"huihui",123) console.log(p) // p.sayName //
-
-
改变函数内部this指针的指向函数(bind,apply,call区别),内在分别是如何实现的?
-
call、apply、bind都是用来改变函数的this指向的
-
call和apply改变this的同时,也会把目标函数给执行掉;bind只负责改造this,不作任何执行操作
-
唯独apply在传参的时候,可以以数组形式被传入
-
实现call方法:
Function.prototype.myCall = function(context,...args){ //把函数挂到目标对象上 context.func = this //执行函数 context.func(...args) //删除1中改到目标对象上的函数,把目标对象“完璧归赵” delete context.func } var me = { name :"icon" } function showFullName(surName) { console.log(`${this.name} ${surName}`) } showName.myCall(me, 'lee') -
模拟apply方法
Function.prototype.myApply = function(context,args){ //判断当前传参是否是数组 if(args && !(args instanceof Array)){ throw new TypeError('error') } //如果是null 默认指向window context = context || window //把函数挂到目标对象上 context.func = this //执行函数并且存储上面说的返回值 const result = context.func(args ?[...args] : "") //删除1中挂到目标对象上的函数, delete context.func; //返回结果 return result } -
模拟bind方法
Function.prototype.myBind = function(context, ...args){ //1:保存下当前this const _this = this; //2.返回一个函数 return function F(){ //3:因为返回了一个函数,除了直接调用还可以new F(),所以需要分开判断走 //4:new的方式 if(_this instanceof F){ return new _this(..args,...arguments); } //5:直接调用,这里选择了apply的方式实现 /**对于参数需要注意一下情况:因为bind可以实现类似这样的代码,f.bind(obj,1)(2),所以我们需要将两边的参数拼接起来,于是就有了这样的实现args.concat(...arguments);*/ return _this.apply(context,args.concat(...arguments)); } }
-
-
js的各个位置,比如clientHeight,scrollHeight,offsetHeight,以及scrollTop,offsetTop,clientTop的区别
- client:说的是可视区域的,也就是元素内部,只包含padding
- offset:说的是相对容器的位置,包含padding+border
- scroll:说的是内容层到可视区域的距离
-
异步加载js的方法?
- defer
- async
-
Ajax解决浏览器缓存的问题?
- 使用ajax时,data数据中添加random随机数,
- 服务器设置响应头来控制浏览器缓存,如Cache-Control: no-cache, no-store, must-revalidate
-
节流和防抖
-
防抖节流本质时优化高频代码的一种手段
-
防抖: n 秒后在执行该事件,若在 n 秒内被重复触发,则重新计时
-
节流: n 秒内只运行一次,若在 n 秒内重复触发,只有一次生效
-
电梯第一个人进来后,15 秒后准时运送一次,这是节流
电梯第一个人进来后,等待 15 秒。如果过程中又有人进来,15 秒等待重新计时,直到 15 秒后开始运送,这是防抖
-
代码的实现:
//节流 function throttled(fn, delay) { let timer = null let starttime = Date.now() return function () { let curTime = Date.now() // 当前时间 let remaining = delay - (curTime - starttime) // 从上一次到现在,还剩下多少多余时间 let context = this let args = arguments clearTimeout(timer) if (remaining <= 0) { fn.apply(context, args) starttime = Date.now() } else { timer = setTimeout(fn, remaining); } } } //防抖 function debounce(func, wait, immediate) { let timeout; return function () { let context = this; let args = arguments; if (timeout) clearTimeout(timeout); // timeout 不为null if (immediate) { let callNow = !timeout; // 第一次会立即执行,以后只有事件执行后才会再次触发 timeout = setTimeout(function () { timeout = null; }, wait) if (callNow) { func.apply(context, args) } } else { timeout = setTimeout(function () { func.apply(context, args) }, wait); } } } -
区别:都可以通过setTimeout实现;目的都是降低回调执行频率,节省计算资源
-
不同点:防抖在一段连续操作结束后,处理回调,利用clearTimeout和setTimeout实现;节流在一段连续操作中,每段时间只执行一次,频率较高的事件中使用来提高性能。防抖关注一定时间连续触发的事件,只在最后一次执行,而节流一段时间只执行一次
-
-
eval是做什么的?
- 作用:把字符串参数解析成js代码运行,并返回执行结果
-
对象深拷贝的简单实现?
-
浅拷贝只是简简单单地把栈当中的引用地址拷贝了一份,所以当你修改新拷贝出来的值时,被拷贝的对象也会被你修改掉,而深拷贝会在堆内存当中为新对象建立空间,所以被拷贝对象就不会被无缘无故地修改掉了
-
实现深拷贝:
//Object.assign:是对对象进行深拷贝的,但是它只对最外层进行,也就是说当对象内嵌套有对象的时候,被嵌套的对象进行的还是浅拷贝 //JSON实现的深拷贝:本质就是会自己构建新的内存来存放新对象 //注意:忽略undefined、symbol;不能对function进行拷贝 JSON.parse(JSON.stringify(obj)) //MessageChannel function deepCopy(obj){ return new Promise((resolve) =>{ const {port1,port2} = new MessageChannel(); port2.onmessage = ev =>resolve(ev.data); port1.postMessage(obj); }) } deepCopy(obj).then((copy) =>{ let copyObj = copy; console.log(copyObj,obj) console.log(copyObj == obj) }) //递归实现 (问题出现:不能解决循环引用) function cloneDeepDi(obj){ const newObj = {}; let keys = Object.keys(obj) let key = null; let data = null; for(let i= 0;i<keys.length;i++){ key = keys[i]; data = obj[key]; if(data && typeof data === "object"){ newObj[key] = cloneDeepDi(data) }eles{ newObj[key] = data } } return newObj } //递归实现升级版:判断一个对象的字段是否引用了这个对象或这个对象的任意父级,如果引用了父级,那么就直接返回同级的新对象,反之,进行递归的那套流程 function deepCopy (obj,parent = null){ //创建一个新对象 let result = {}; let keys = Object.keys(obj), key = null, temp = null , _parent = parent; //该字段有父级则需要追溯该字段的父级 while(_parent){ //如果该字段引用了它的父级则为循环引用 if(_parent.originalParent === obj){ return _parent.currentParent; } _parent = _parent.parent; } for(let i=0;i< keys.length;i++){ key = keys[i]; temp = obj[key] //如果字段的值也是一个对象 if(temp && temp === "Object"){ //递归执行深拷贝,将同级的待拷贝对象与新对象传递给parent 方便循环引用 result[key] = deepCopy(temp,{ originalParent:obj, currentParent:result, parent:parent }); }else{ result[key] = temp; } } return result; }
-
-
-
梳理数据类型
- 原始值类型,包括
number、string、boolean、null、undefined - 引用值类型,包括
object、function、array - symbol
- 原始值类型,包括
-
原始值的克隆
- 通过for in 遍历出对象身上的所有属性,但是for in 会遍历原型上的
- 通过hasOwnProperty过滤掉原型上的属性
- 通过typeof除去object、funciton、symbol,剩下都是直接赋值的原始值,包括number、string、Boolean
function deepClone(origin, target, hash = new WeakMap()) { //origin:要被拷贝的对象 // 需要完善,克隆的结果和之前保持相同的所属类 var target = target || {}; // 处理特殊情况 if (origin == null) return origin; //null 和 undefined 都不用处理 if (origin instanceof Date) return new Date(origin); if (origin instanceof RegExp) return new RegExp(origin); if (typeof origin !== 'object') return origin; // 普通常量直接返回 // 防止对象中的循环引用爆栈,把拷贝过的对象直接返还即可 if (hash.has(origin)) return hash.get(origin); hash.set(origin, target) // 制作一个映射表 // 拿出所有属性,包括可枚举的和不可枚举的,但不能拿到symbol类型 var props = Object.getOwnPropertyNames(origin); props.forEach((prop, index) => { if (origin.hasOwnProperty(prop)) { if (typeof (origin[prop]) === "object") { if (Object.prototype.toString.call(origin[prop]) == "[object Array]") { //数组 target[prop] = []; deepClone(origin[prop], target[prop], hash); } else if (Object.prototype.toString.call(origin[prop]) == "[object Object]") { //普通对象 target[prop] = {}; deepClone(origin[prop], target[prop], hash); } else if (origin[prop] instanceof Date) { // 处理日期对象 target[prop] = new Date(origin[prop]) } else if (origin[prop] instanceof RegExp) { // 处理正则对象 target[prop] = new RegExp(origin[prop]) } else { //null target[prop] = null; } } else if (typeof (origin[prop]) === "function") { var _copyFn = function (fn) { var result = new Function("return " + fn)(); for (var i in fn) { deepClone[fn[i], result[i], hash] } return result } target[prop] = _copyFn(origin[prop]); } else { //除了object、function,剩下都是直接赋值的原始值 target[prop] = origin[prop]; } } }); // 单独处理symbol var symKeys = Object.getOwnPropertySymbols(origin); if (symKeys.length) { symKeys.forEach(symKey => { target[symKey] = origin[symKey]; }); } return target; }
-
-
实现一个once函数,传入函数参数只执行一次?
-
思路:
function once(fn){ let ret; return funciton (...args){ if(!fn) return ret; ret = fn(...args); fn = undefined; return ret; } }
-
-
将原生的Ajax封装成promise?
-
//原生ajax //创建xmlhttpRequest对象 let xhr = new XMLHttpRequest(); //配置请求方式 xhr.open('get'); //发送请求 xhr.send(); //调用onreadystatechange函数,针对不同的响应状态进行处理 xhr.onreadstatechange = function(){ if(xhr.readState === 4){ if(xhr.status === 200){ console.log("成功") }else{ console.log("失败") } } } function ajax(url,data = {};type="get"){ return new Promise((resolve,reject)=>{ $.ajax({ type:type, url:url, data:data, datatType:"json", success:function(data){ resolve(data); }, error:function(err){ reject(err); } }) }) } let url = "http://localhost:5000/login"; let data = { uername:"admin", password:"admin" } let promise = ajax(url,data,"get"); promise.then(data=>{ f1(); }).then(data2 =>{ f2(); }).catch(err =>{ console.log(err); })
-
-
JS监听对象属性的改变
-
第一种:使用Object.defineProperty();缺点:无法监听到后续新增的属性
const obj = { name:"liukang", age:25, } Object.keys(obj).forEach((key)=>{ let value = obj[key]; Object.defineProperty(obj,key,{ get:function(){ console.log("我被访问了") return value }, set:function(){ console.log("我被设置了") value = newValue } }) }) -
第二种:使用Proxy。Es6中新增Proxy类,用于帮助我们创建一个代理的;如果我们希望监听一个对象的相关操作,那么我们可以先创建一个代理对象(Proxy对象),之后对该对象的所有操作,都通过代理对象来完成,代理对象可以监听我们想要对原对象进行哪些操作。
const obj = { name:"liukang", age:25, } const objProxy = new Proxy(obj,{ //获取值时的捕获器 get:function(target,key){ console.log('监听到被访问了'); return target[key] }, //设置值时的捕获器 set:function(target,key,newValue){ console.log('监听到被设置了'); target[key] = newValue }, //监听in的捕获器 has:function(target,key){ console.log('监听到对象的in操作'); return key in target }, //监听delete的捕获器 deleteProperty:function(target,key){ console.log('监听到对象的delete') } })
-
-
如何实现一个私有变量,用get可以访问,不能直接访问
-
方式一:
function private(){ let a = "私有变量"; this.getName = function(){ return a; } } let obj = new Private(); console.log(obj.a) //undefined console.log(obj.getName) // "私有变量" -
方式二:类构造器
class private2 { constructor(){ let a ='class私有'; thie.getName = function(){ return a; } } } let p = new private2(); console.log(p.a) //undefined console.log(p.getName()) // "class私有"
-
-
怎么控制一次加载一张图片,加载完后在加载下一张
-
原理:监控图片是否加载完成,就可以了
//方法一: var obj = new Image(); obj.src = "XXXX"; obj.onload = function(){ //加载完成,会执行的函数 } //方法二: let obj = new Image(); obj.src = "..."; obj.onreadystatechange = function(){ if(this.readyState === 'complate') { document.getElementById('mypic').innerHTML = '<img src=" '+this.src +'">' } }
-
-
如何实现sleep的效果
-
sleep函数:是可以使程序暂停执行,等到了指定时间在重新执行,能起到延时的效果。
-
回调函数方式
function sleep(callback,time){ if(typeof callback === 'function'){ setTimeout(callback,time) } } sleep(function(){console.log("1")},1000) -
Promise方式
const sleep = time =>{ return new Promise(resolve => setTimeout(resolve,time)); } sleep(1000).then(() =>console.log("1")) -
Generator 方式
function* sleepGenerator(time){ yield new Promise(resolve => setTimeout(resolve,time)); } sleepGenerator(5000).next().value.then(() => console.log("哇哇哇哇哇")) -
await/async 方式
async function sleep(time){ await new Promise(resolve => setTimeout(resolve,time)); } sleep(5000).then(() => console.log("当我有个大厂梦"))
-
-
Function._ proto _(getPrototypeOf)是什么?
- Function._ proto _ 是获取一个对象的原型,在chrome中 通过_ proto _ 的形式显示。
- Function._ proto _ == Function.prototype
-
箭头函数中this指向
- 箭头函数没有自己的this指向,他的this指向上一级作用域的this
- 箭头函数体内的this对象,就是定义该函数时所在的作用域指向的对象,而不是使用时所在的作用域指向的对象
-
数组常用方法有哪些?
- push:向数组末尾添加数据
- pop:
- slice:截取数组中其中一部分
- splice:截取数组
- shift:向头部删除数据
- unshift:向头部添加数据
- reverse:翻转数组
- concat:合并数组
- sort:排序
- join:数组转字符串
- indexOf:从左查询数I组中有没有给数据,返回第一次出现的索引
- lastIndexOf:从右查询数I组中有没有给数据,返回第一次出现的索引
- forEach:循环遍历
- map:映射数组的
- filter:过滤数组
- every:判断数组是不是满足所有条件
- some:数组中有没有满足条件的
- find:用来获取数组中满足条件的第一个数据
- recude:叠加后的效果
-
数组去重有哪些方法?
- Set + Array.from
- map +has +set
- filter+indexOf
- indexOf
- includes
-
如何去除字符串首尾空格?
- trim()
- 正则表达式:reg = /(^ \s* | \s*$)/g + replace
-
说说你所知道的JS语言特性?
- 1、解释性:javascript是一种解释语言,源代码不需要经过编译,直接在浏览器上运行时被解释。
- 2、跨平台:JavaScript依赖于浏览器本身,与操作环境无关。只要能运行浏览器的计算机,并支持JavaScript的浏览器就可以正确执行。
- 3、安全性:JavaScript是一种安全性语言。它不允许访问本地的磁盘,并不能将数据存入服务器上;不允许对网络文本进行修改和删除,只能通过浏览器实现信息浏览或动态交互。可有效的防止数据丢失。
- 4、简单性:JavaScript是一种脚本编写语言,它采用小程序段的方式实现编程,像其它脚本语言一样,JavaScript同样已是一种解释性语言,它提供了一个简易的开发过程。它的基本结构形式与C、C++、VB、Delphi十分类似。但它不像这些语言一样,需要先编译,而是在程序运行过程中被逐行地解释。它与HTML标识结合在一起,从而方便用户的使用操作。
- 5、动态性:JavaScript是动态的,它可以直接对用户或客户输入做出响应,无须经过Web服务程序。它对用户的反映响应,是采用以事件驱动的方式进行的。所谓事件驱动,就是指在主页中执行了某种操作所产生的动作,就称为“事件”。比如按下鼠标、移动窗口、选择菜单等都可以视为事件。当事件发生后,可能会引起相应的事件响应。
-
如何判断一个数组?
- array.isArray()
- Object.prototype.toString.call(): Object.prototype.toString.call(a) === '[object Array]'
- instanceof:用于检验构造函数的prototype属性是否出现在对象的原型链中的任何位置
- constructor
-
JS的全排列
-
全排列:给定一个字符串,输出该字符串所有排列的可能。如输入“abc”,输出“abc,acb,bca,bac,cab,cba”。
-
function fullpermutate(str){ var result = []; if(str.length > 1){ for(var m = 0; m < str.length;m++){ var left = str[m] var rest = str.slice(0,m) + str.slice(m+1,str.length); var preResult = fullpermutate(rest); for(var i=0;i <preResult.length;i++){ var tmp = left + preResult[i] result.push(tmp); } } }else if(str.length == 1){ result.push(str); } return result; }
-
-
谈谈你所理解的跨域,为什么会出项这个问题呢?如何解决
- 跨域:跨域资源共享,是一种用于当前域的资源被其他域的脚本请求访问的机制,通常由于同源策略,浏览器会禁止这种跨域请求
- 同源安全策略:是一种约定,是浏览器最核心也是最基本的安全功能,如果缺少了同源策略,浏览器很容易受到XSS,CSRF等攻击。
- 同源指的是协议、域、端口三者相同,请求可以正常发送和收到,只是被浏览器拦截了
- 解决跨域:
- JSONP:利用script标签没有跨域限制的特点,通过动态生成script标签,指定src属性的方式加载跨域资源。
- 配置后端CORS:在服务器端进行配置,加一个 Access-Control-Allow-Origin 响应头
- 中间服务器代理
- 正向代理是代理服务器对客户端进行代理,为客户端收发请求,使得真实客户端对目标服务器不可见
- 反向代理是代理服务器对目标服务器进行代理,为目标服务器进行收发请求,使得真实服务器对客户端不可见
-
null == undefined 输出什么? null === undefined 又是什么?
- null == undefined //true :规范中有明确规定,比较时不能将null和undefined转化成其他任何值,并且规定null和undefined是相等的,null和undefined都是无效的值
- null === undefined //fasle :类型不同
-
什么按需加载?
- 按需加载是前端性能优化的一项重要措施,当用户触发了动作,才加载的对应的功能。
- 优势:减少不必要的资源请求,节省流量,如果网速不行,则可能会等好长时间
-
简单介绍下symbol
- symbol:独一无二的值
- 创建symbol对象:const s = Symbol();
- 接收一个参数比较:Symbol('aaa') != Symbol('aaa')
- Symbol.for():Symbol.for('foo') === Symbol.for('foo') //true
- Symbol.keyFor:方法返回一个使用 Symbol.for 方法创建的 symbol 值的 key
-
介绍下promise,底层是如何实现?
-
//定义PromseState类,该类包含了Promise对象的三种状态及其对应的操作方法。 class PromiseState { static PENDING = 'pending'; //等待中 static FULFILLED = 'fulfilled'; //成功 static REJECTED = 'rejected'; //失败 constructor(state) { //构造函数 this.state = state || PromiseState.PENDING; this.value = null; this.reason = null; this.fulfillCallbacks = []; this.rejectCallbacks = []; } //该方法用于将Promise状态设置为fulfilled fulfill(value) { if (this.state === PromiseState.PENDING) { this.state = PromiseState.FULFILLED; this.value = value; for (let i = 0; i < this.fulfillCallbacks.length; i++) { this.fulfillCallbacks[i](value); } } } //该方法用于将Promise状态设置为rejected reject(reason) { if (this.state === PromiseState.PENDING) { this.state = PromiseState.REJECTED; this.reason = reason; for (let i = 0; i < this.rejectCallbacks.length; i++) { this.rejectCallbacks[i](reason); } } } //以下:是关于then和catch方法对Promise状态变化的处理 then(onFulfilled, onRejected) { const nextPromiseState = new PromiseState(); if (this.state === PromiseState.PENDING) { this.fulfillCallbacks.push((value) => { try { const x = onFulfilled(value); resolvePromise(nextPromiseState, x); } catch (e) { nextPromiseState.reject(e); } }); this.rejectCallbacks.push((reason) => { try { const x = onRejected(reason); resolvePromise(nextPromiseState, x); } catch (e) { nextPromiseState.reject(e); } }); } else if (this.state === PromiseState.FULFILLED) { setTimeout(() => { try { const x = onFulfilled(this.value); resolvePromise(nextPromiseState, x); } catch (e) { nextPromiseState.reject(e); } }, 0); } else if (this.state === PromiseState.REJECTED) { setTimeout(() => { try { const x = onRejected(this.reason); resolvePromise(nextPromiseState, x); } catch (e) { nextPromiseState.reject(e); } }, 0); } return nextPromiseState; } catch(onRejected) { return this.then(null, onRejected); } } //定义了resolvePromise方法,主要用于处理Promise对象的值,当该值为Promise对象时,我们会递归调用then()方法,直到获取到一个非Promise类型的值位置,当该值为其他类型时,我们直接使用fulfill方法,将Promise状态设置为fulfilled function resolvePromise(promiseState, x) { if (x === promiseState) { throw new TypeError('Chaining cycle detected for promise'); } if (x && typeof x === 'object' || typeof x === 'function') { let used = false; try { const then = x.then; if (typeof then === 'function') { then.call(x, (y) => { if (used) { return; } used = true; resolvePromise(promiseState, y); }, (r) => { if (used) { return; } used = true; promiseState.reject(r); }); } else { promiseState.fulfill(x); } } catch (e) { if (used) { return; } used = true; promiseState.reject(e); } } else { promiseState.fulfill(x); } } //最后定义Promise类,该类实现了Promise对象的核心逻辑,并暴露then、catch、static方法。对外部使用者而言,只需要使用new Promise()创建一个Promise对象,并调用then()和catch()方法即可。 class Promise { constructor(fn) { if (typeof fn !== 'function') { throw new TypeError('Promise resolver ' + fn + ' is not a function'); } const state = this.state = new PromiseState(); try { fn((value) => { resolvePromise(state, value); }, (reason) => { state.reject(reason); }) } catch (e) { state.reject(e); } } then(onFulfilled, onRejected) { return this.state.then(onFulfilled, onRejected); } catch(onRejected) { return this.state.catch(onRejected); } } Promise.resolve = function (value) { return new Promise((resolve) => { resolve(value); }); } Promise.reject = function (reason) { return new Promise((resolve, reject) => { reject(reason); }); }
-
-
js原型链,原型链的顶端是什么?Object的原型是什么?
- 原型链:由各级对象的_ proto _ 逐级向上引用形成的多级继承关系
- 原型链的顶端是Object.prototype._ proto _ = null
- Object的原型是Objec.prototype;
- 原型链:由各级对象的_ proto _ 逐级向上引用形成的多级继承关系
-
Promise + Generator +Async的使用
-
Promise
//Promise的内部错误使用try catch捕获不到,只能只用then的第二个回调或catch来捕获 //Promise一旦新建就会立即执行,无法取消 ajax('aaa').then(res=>{ return ajax('bbb') }).then(res=>{ return ajax('ccc') }) -
Generator :与函数声明类似,不同的是
function关键字与函数名之间有一个星号,以及函数体内部使用yield表达式,定义不同的内部状态(yield在英语里的意思就是“产出”)。function* gen(x){ const y = yield x + 6; return y; } // yield 如果用在另外一个表达式中,要放在()里面 // 像上面如果是在=右边就不用加() function* genOne(x){ const y = `这是第一个 yield 执行:${yield x + 1}`; return y; } const g = gen(1); //执行 Generator 会返回一个Object,而不是像普通函数返回return 后面的值 g.next() // { value: 7, done: false } //调用指针的 next 方法,会从函数的头部或上一次停下来的地方开始执行,直到遇到下一个 yield 表达式或return语句暂停,也就是执行yield 这一行 // 执行完成会返回一个 Object, // value 就是执行 yield 后面的值,done 表示函数是否执行完毕 g.next() // { value: undefined, done: true } // 因为最后一行 return y 被执行完成,所以done 为 true ---------------------------------------------------- 调用Cenerator函数后,该函数并不执行,返回的也不是函数运行结果么,而是 一个指向内部状态的指针对象,也就是遍历对象(Interator Object)。下一步,必须调用遍历器对象的next方法,使得指针移向下一个状态。 -
Async/await:
Async/await其实就是上面Generator的语法糖,async函数其实就相当于funciton *的作用,而await就相当与yield的作用。而在async/await机制中,自动包含了我们上述封装出来的spawn自动执行函数。async function fetch() { await ajax('aaa') await ajax('bbb') await ajax('ccc') } -
测试题:
console.log('script start') async function async1() { await async2() console.log('async1 end') } async function async2() { console.log('async2 end') } async1() setTimeout(function() { console.log('setTimeout') }, 0) new Promise(resolve => { console.log('Promise') resolve() }) .then(function() { console.log('promise1') }) .then(function() { console.log('promise2') }) console.log('script end') //打印顺序为 script start -> async2 end -> Promise -> script end -> async1 end -> promise1 -> promise2 -> setTimeout //解析 打印顺序应该是: script start -> async2 end -> Promise -> script end -> async1 end -> promise1 -> promise2 -> setTimeout 老规矩,全局代码自上而下执行,先打印出script start,然后执行async1(),里面先遇到await async2(),执行async2,打印出async2 end,然后await后面的代码放入微任务队列,接着往下执行new Promise,打印出Promise,遇见了resolve,将第一个then方法放入微任务队列,接着往下执行打印出script end,全局代码执行完了,然后从微任务队列中取出第一个微任务执行,打印出async1 end,再取出第二个微任务执行,打印出promise1,然后这个then方法执行完了,当前Promise的状态为fulfilled,它也可以出发then的回调,所以第二个then这时候又被加进了微任务队列,然后再出微任务队列中取出这个微任务执行,打印出promise2,此时微任务队列为空,接着执行宏任务队列,打印出setTimeout。
-
-
JS中String 的startwith 和indexOf两种方法的区别
- startwith:用于检测字符串是否以指定子字符串开始;startsWith()只有两个参数,一个是必须的searchValue,即要匹配的字符串,第二个是可选的position匹配开始位置,不添加第二个参数就是从头开始。返回值是布尔值类型 string.startsWith(searchVlue[, position])
- 区别:
- startwith:返回值为布尔值;表示字符串是否以某一子串开头
- indexOf:返回值为字串第一次出现的位置下标,不包含则返回-1
-
JS字符串转数字的方法
- parseInt()、parseFloat()
- Number()
- 乘数字
-
平时 怎么调试JS的
- console.log、debugger
-
怎么获得对象上的属性
- Object.getOwnPropertyNames();|| Object.getOwnPropertySymbols();
- for in
- Object.keys()
- Reflect.ownKeys
-
Async和await具体该怎么用?
-
async function ajax(key){ return 1 } ajax().then(function(res){console.log(res)}) async function fn(){ console.log(1); let result = await new Promise(function(resolve,reject){ setTimeout(function(){ resolve(2) },5000) }) console.log(result) console.log(3) console.log(await (4)) console.log(5) } fn()
-
-
知道哪些ES6,ES7的语法?
- Promise、async/await、let、const、块级作用域、箭头函数、模版字符串、展开运算符、解构赋值等
-
Promise和async/await的关系
- 首先他们都是用来处理异步操作的方法,存在互相补充的关系
- Promise是一种用于处理异步操作的对象,它可以表示一个异步操作的最终完成或失败,并可以在操作完成后获取结果或处理错误。通过Promise,我们可以使用链式调用来处理多个异步操作,使代码更具有可读性。
- async/await是基于Promise的一种语法糖,他提供一种更直观和同步化的方式来编写异步代码。通过使用async关键字声明一个函数为异步函数,并使用await关键字来等待Promise对象的解决,我们可以以同步的方式编写异步操作的代码
- Promise可以在其回调函数中使用async/await语法来进行异步操作。通过将异步操作封装在Promise对象中,我们可以使用async/await来更清晰地表达异步操作的顺序和依赖关系。
- async函数始终返回一个Promise对象,它会自动将函数的返回值包装在一个resolved的Promise中。这意味着我们可以在async函数中使用await来等待其他Promise的完成,并以同步的方式处理它们的结果。
- 使用async/await可以使异步代码的编写更加简洁和可读。它可以避免嵌套的回调函数和过多的.then()调用,使得异步代码更接近同步代码的写法,提高了代码的可维护性和可理解性
-
JS加载过程阻塞,解决方法、
- 场景:默认情况下,浏览器是同步进行加载JavaScript脚本:即渲染引擎遇到script标签就会停下来,等到执行完脚本,再继续向下渲染。如果是外部脚本,还必须加入脚本下载的时间。如果脚本体积过大,下载和执行时间就会很长,因此造成了浏览器堵塞,用户会觉得浏览器很卡,出现短暂空白,没有任何响应,造成不好的体验。
- 方案:
- 改变script标签的位置,最好丢在body标签的最后面,即放在body标签的前面。这种方式不会影响浏览器的DOM渲染,会让页面处理执行完才去执行它
- 同步转异步。浏览器允许脚本异步加载,这种方式可以让script标签继续放在head头部,下面是两种异步加载的语法:async和defer
-
JS对象类型,基本对象类型以及引用对象类型的区别
- 基本数据类型的值是不可变的,任何方法都不发改变一个基本类型的值,当这个变量重新赋值后看起来变量的值是改变了,但是这里变量名只是指向变量的一个指针,所以改变的是指针的指向,该变量是不变的,但是引用类型可以改变
- 基本数据类型不可以添加属性和方法,但是引用类型可以
- 基本数据类型的赋值是简单赋值,如果从一个变量向另一个变量赋值基本类型的值,会在变量对象上创建一个新值,然后把该值复制到为新变量分配的位置上,引用数据类型的赋值是对象引用
- 基本数据类型的比较是值的比较,引用类型的比较是引用的比较,比较对象的内存地址是否相同
- 基本数据类型是存在栈中,引用数据类型同时保存在栈区和堆区
-
轮播的实现原理?假如一个页面上有两个轮播,你会怎么实现?
-
解释一下JS的事件循环
- 事件循环:处理异步操作的机制
- 原理:就是将所有异步操作放入一个事件队列中,然后依次执行队列中的任务,直到队列为空为止
- 在浏览器中分为:
- 宏任务:由浏览器提供的异步任务,比如定时器,DOM时间和网络请求等。
- 微任务:由JavaScript引擎提供的异步任务,比如Promise的回调函数、MutationObserver的回调函数等。
- 渲染阶段
-
localstorage、sessionStorage、cookie的区别
-
解释下HTML5 Drag API
-
一个典型的拖放操作是这样的:用户选中一个可拖拽的(draggable) 元素,并将其拖拽(鼠标不放开)到一个可放置的(droppable) 元素,然后释放鼠标。
-
相关API: dragstart:事件主体是被拖放元素,在开始拖放被拖放元素时触发,。
darg:事件主体是被拖放元素,在正在拖放被拖放元素时触发。
dragenter:事件主体是目标元素,在被拖放元素进入某元素时触发。
dragover:事件主体是目标元素,在被拖放在某元素内移动时触发。
dragleave:事件主体是目标元素,在被拖放元素移出目标元素是触发。
drop:事件主体是目标元素,在目标元素完全接受被拖放元素时触发。
dragend:事件主体是被拖放元素,在整个拖放操作结束时触发
-
-
解释下webworker
- WebWorker是HTML5提供的API,它允许JavaScript运行在后台线程中,独立于主线程,从而实现多线程编程。提高Web应用程序的性能和响应速度
- 特点:
- 独立于主线程
- 可以并行执行
- 不能直接访问DOM和BOM:可以通过postMessage和onmessage事件与主线程进行通信,从而实现数据的传递和交互
- 可以引入外部脚本
-
{}和[]的valueOf和toString的结果是什么?
- {}.toString() //[object object] {}.valueOf() // {}
- [].toString() //"" [].String
- valueOf返回指定对象的原始值;toString返回一个标识改对象的字符串
-
三种事件模型是什么?
- IE事件模型:只支持冒泡事件
- DOM事件模型(原始事件):通过元素绑定事件
- DOM事件模型(冒泡和捕获以及addEventListener)
-
介绍一下V8 隐藏类
-
隐藏类长这样子
Hidden class 2{ public string age; } ↑ | | Hidden class 1{ public string name; if add 'age' transition to class 2 } ↑ | | Hidden class 0{ if add 'name' transition to class 1 } -
其次出现原因是由于javascript是动态编程语言,对象在初始化之后,仍然可以对其属性进行增删操作,才会有了隐藏类的概念
-
作用:为了优化属性的访问速度
-
保证以相同的顺序实例化对象属性,这样可以保证它们共享相同的隐藏类。
-
在初始化属性时不要使用动态属性名,推荐使用
this.name = name这种方式 -
面试题: 左右两边都是定义
obj1和obj2对象,右边的obj2的a属性和b属性换了位置,那么左右两边有什么区别呢?const obj1 = { | const obj1 = { a: 1, | a: 1, b: 2 | b: 2 } | } | const obj2 = { | const obj2 = { a: 1, | b: 2, b: 2 | a: 1 } | } //同样都是生成obj1和obj2,但是左边的结构在创建obj2时复用了执行obj1生成的隐藏类,右侧结构因为a和b的属性定义顺序不相同,则导致了找不到对应的隐藏类,从而要创建出一个新的隐藏类,所以结论就是左侧的代码性能会好于右侧的代码
-
-
AMD和CMD规范的区别?说一下CommonJS、AMD和CMD?
-
谈谈JS的运行机制
- JavaScript是单线程语言
- javascript事件循环
- 宏任务和微任务