js原生
如何判断两个对象是不是相等?
- 【转成字符串判断】(不支持函数,正则), JSON.stringife(obj);JSON.parse(str)
- 插件:
- loadsh: 不支持函数和DOM节点比较
- 【循环遍历】
堆和栈的区别?
1)相同点:
堆和栈是两种数据结构,都是一种按序排列的数据结构,只能在一端(称为栈顶top) 对数据项进行插入和删除。堆栈是个特殊的存储区,主要功能是暂时存放数据和地址。
2)不同点
堆:堆内存一般是由语言运行环境分配管理,如python虚拟机控制内存和运行环境
栈:
| 堆(heap) | 栈(stack) | |
|---|---|---|
| 定义 | 数据存储结构 | 定义一个变量时,计算机会在内存中开辟一块存储空间来存放这个变量的值,这块空间叫做栈 |
| 时效性 | 持久化 | 临时 |
| Context(执行上下文) | 全局 | 局部 |
| 内存分配 | 手动申请/释放,若不释放,程序结束时可能由OS回收 | 自动申请/释放(出栈时),系统分配是有大小限制的,超过就会内存溢出报错 |
| 数据存放类型 | 对象类型 | 基本数据类型 |
| 特点 | / | 先进后出 |
栈示意图(先进后出):

let obj={name:'girl friend'}b=obj;b.name='strange';console.log(obj);
堆示意图:
var arr = [1,2,3];brr = arr;brr[0] = 10;alert( arr[0] );
堆栈关系:
【栈】左侧:栈中存放引用地址,这个地址在计算机中叫做引用变量
【堆】右侧:堆中存放对象,多个引用地址会指向同一个对象,JS节省存储空间
js的执行过程?
js特点:异步,单线程;
一、语法分析
js脚本代码块加载完毕后,进入语法分析,判断 语法是不中正确,如果不正确,向外抛出 语法错误(syntaxError), 停止该js代码块的执行,然后继续查找并加载下一个代码块;如果语法正确,则进入预编译阶段
二、预编译阶段
前置知识:
js运行环境:
- 全局环境:js代码加载完毕后,代码进入全局环境)
- 函数环境:函数调用执行时,进入该函数环境,不同函数则函数环境不同
- eval :不建议使用,有安全、性能问题
预编译阶段过程如下:
函数调用栈:
- 形成执行上下文:进入不同环境创建相应的执行上下,可创建多个
- 形成函数调用栈:js引擎以栈的形式处理执行上下文 (栈底永远全局执行上下文,栈顶永远是当前执行上下文)
创建执行上下文:
- 创建变量对象
- 建立作用域链
- 确定this指向
三、执行阶段:
可参考: 宏任务与微任务,事件循环event loop介绍
推荐文章:
js中的宏任务与微任务,事件循环event loop?
宏任务(macro-task):
同步任务 :js引擎按主线程按顺序执行的任务,一个一个任务执行,形成一个执行栈(函数调用栈)
eg:console.log(11); let a=10;
异步任务 :不直接进入js引擎主线程,而是满足条件时触发,相关的线程将该任务推进 任务队列,等待js引擎主线程上的任务执行完毕,再读取执行中的任务
eg:ajax, dom事件,setTimeout
微任务(micro- task)
微任务是在es6和node环境中出现的一个任务类型
eg:promise,process.nextTick
js引擎执行过程: 宏任务(同步任务) —-> 微任务 —> 宏任务(异步任务)

柯里化函数的例子,并说明柯里化的好处?
柯里化是一种模式,把一个具有多个参数的函数被分解成多个函数,当被串联调用时,这些函数将一次累加一个所需的所有参数。这种技术有助于使用函数式编写的代码更容易阅读和编写。需要注意的是,要实现一个函数,它需要从一个函数开始,然后分解成一系列函数,每个函数接受一个参数。
优点:
- 【预置共有参数】
- 【拆分大的函数】每个函数执行一个独立的任务,能过串联的方式拼接起来
// 预置共用参数function add(x){return function (y){return x+y;}}// 预置参数let add1=add(1)// 二次传参,互不影响console.log(add1(10)); // 11console.log(add1(20)); // 21
使用场景:
- 使代码便于理解 //react-redux,connect方法, 将component要用到的state切面和action注入到它的property中,达到ui组件,和容器组件分离的目的。
let Container=connect(mapStateToProps,mapDispatchToProps)(Component);
let { Map }=Immutable;let obj={a:1,b:2}//克隆出一个let map=Map(obj);//调用immutable中的map对象下的set方法来修改指定key的valuelet map1=map.set('a',10);let map2=map1.set('a',20);//两个对象修改互不影响console.log(map1.get('a'),map2.get('a')); //10,20
判断一个变量是不是数组的有哪几种方法?
- Object.prototype.toString.call(arg) === ‘[object Array]’ //目前最准确的方法
- Array.isArray(arg) //有兼容性问题,IE8下不支持
- arg instanceof Array //不能跨iframe使用
- arg.constructor===Array //不能跨iframe使用,constructor可以改写, a.constructor=Object; 会导致判断不准确
//最通用的方法var arr = [];function isArray(str){return Object.prototype.toString.call(str) == "[object Array]";}console.log(isArray(arr));//ie所有版本均支持;
哪些操作会造成内存泄漏?
内存泄漏原因:不再用到的内存,没有及时释放,就叫做内存泄漏;
- 全局变量缓存数据 (无法被垃圾回收机制收集)
- 定时器没有清除
- 闭包的循环引用
- js错误引用dom元素 (dom虽然删除,但是引用还在内存中)
js垃圾回收机制?
- 引用计数 (如果没有引用指向该对象,对象将被垃圾回收机制回收,缺点:在循环引用的情况下,存在局限性)
- 标记清除 (处理循环引用的问题,和引用计数本质相同,可达内存被标记,其余的被当作垃圾回收)
什么是作用域链?
作用域链就是【变量】和【函数】和可访问范围,
控制着变量和函数的可见性与生命周期,在js中变量的作用域有全局作用域和局部作用域 ;
什么是闭包,如何使用,闭包有什么问题?
闭包是由函数创建的一个词法作用域,里面的变量被引用后(外部函数引用,前提得先return出来),可以在这个词法环境之外使用;
函数嵌套函数,能够读取【其他函数】内部变量的【函数】;
闭包一定得return才能读取内部的值;
闭包的原理就是作用域链
用途
- 创建内部变量,使得这些变量不能随意被外部修改,但是又可以通过指定的函数接口修改
- 减少全局变量
- 让这些值始终保持在内存中
闭包的问题:
- 耗性能:闭包会使函数内的变量一直保持在内存当中,
解决:在退出函数之前,将不使用的函数变量全部删除;
//使用自执行函数写法
//定义一个闭包let secretFn=(function (){//变量secrent只有secretFn内的getSecret, setSecret 才可以访问,外部无法访问let secrent='100';let getSecret=function (){return secrent;}let setSecret=function (newSecrent) {secrent=newSecrent;}return {getSecret,setSecret}})()console.log(secretFn.getSecret());secretFn.setSecret(200);console.log(secretFn.getSecret());console.log(secretFn.secrent); //Type error 无法访问
//使用new写法
//定义一个闭包let SecretFn=function (){//变量secrent只有secretFn内的getSecret, setSecret 才可以访问,外部无法访问let secrent='100';this.getSecret=function (){return secrent;}this.setSecret=function (newSecrent) {secrent=newSecrent;}}const secretOne=new SecretFn();console.log(secretOne.getSecret());secretOne.setSecret(200);console.log(secretOne.getSecret());
function F() {let a = 1window.G = function () {console.log(a)}}F()G() // 1, G能访问到F函数的变量,他就是闭包
如何获取闭包函数内的变量?
- 函数嵌套,闭包
- 函数返回值,return
js有哪几种数据类型?
js一共七种类型;
基本类型(6):String,Boolean,Number,Undefined,Null,Symbol(创建唯一的值)
引用类型(1):Object(Function,Array, Date, RegExp)
js如何判断数据类型?
- typeof :不能判断array,object,function;
- instanceof :可以使用可以用来判断数组; [1] instanceof Array 返回 true
- toSring Object.prototype.toString.call([]) 返回true
- constructor [].constructor===Array 返回true
js有哪些内置对象?
object是js中所有对象的父对象;
数据封闭类对象:object,array,boolean,number,string
其他对象:function,arguments,math,date,RegExp,error
js如何实现继承?
- 通过原型链(prototype)来实现
- 通过构造函数继承-(在子函数中使用)
- es6的class,配合extend来实现继承
- 拷贝继承(创建一个extendFun的方法)
es5的继承和es6继承的区别?
es5继承本质:
- 先创建子类的实例对象
- 再将父类的方法挂载到this上; parent.apply(this);
es6的继承本质 (es6通过class定义类,能过extend继承类,子类必须在construct 方法中调用super,否则新建实例报错,因为子类没有自己的this,而是继承了父类的this, 然后对其进行加工,如不调用super, 子类得不到this对象)
- 先创建父类的实例对象this (所以先调用父类的super方法)
- 然后再用子类的构造函数修改this
前端路由实现原理?
前端实现路由有两种方式:
1) history模式
主要借助html5的两个api,
window.history.pushState(“/detail”) //添加历史记录
window.history.replaceState(“/detail”) //替换当前历史记录
两个 API 都会操作浏览器的历史记录,而不会引起页面的刷新。
2) hash模式
实现的api:
监听哈希变化触发的事件( hashchange) 事件,
window.location 处理哈希的改变时不会重新渲染页面
性能优化:
如何进行性能优化?
减少网络请求(大小,数量)—-网络会有延迟,大部分浏览器一性能获取6-8个请求:
- 资源合并/代码压缩:js,图片(webP,base64),css
- 降低CPU消耗
- 减少DOM操作:缓存DOM查询,合拼DOM插入,事件节流
- 缓存:保持名字不变,如果要改变可以使用hash来改变
- 加速服务端资源返回的速度:
- cdn(千牛,bootcdn)
- 懒加载(主要处理图片,默认图片代码真实图片)
- SSR后端渲染
图片懒加载的实现原理?
原理:img src中放入一张很小的图片,把真实的图片放在img的属性中,data-src, 通过监听页面滚动过程中,图片距离顶部的高度来设置页面是否显示;
一般可以使用
- jquery.lazyload.js //到了可视区才加载
- lazysizes.js //没有到可视区才显示功能,不依赖jquery
注意事项:
- 使用节流函数进行性能优化
什么是重排什么是重绘?
DOM的结构属性改变就会触发重排或者重绘;
只改变DOM节点对象的css的颜色,不改变大小,其他排版属性就属于重绘;
visibility:hidden,隐藏dom节点只触发重绘,display:none隐藏dom节点触发重排和重绘;
改变padding会触发:重排+重绘
什么是深拷贝和浅拷贝,如何实现深拷贝?
js中数据类型分为2类,引用数据类型(array,object,function),和其他数据类型(string,number,boolean,null,undefind);
深拷贝指拷贝后的对象与原来的对象相互独立,修改任何一方,其他一方都不受影响;
深拷贝主要有
- es6的 spread展开,可以深拷贝数组,对象;
- for循环依次读取复制
- 先通过JSON.stringify把对象转化成字符串,再通过 JSON.parse把字符串转换成对象 (如果对象中包含函数,正则表达式会丢失;对象有循环引用会报错;)
- 外部库:jquery extend, 使用标准库 lodash的 cloneDeep
注意事项:
Object.assign 只能深拷贝第一层,深层的还是浅拷贝;
什么是事件节流和函数防抖的区别,和实际运用?
相同点:为了限制函数执行的次数 ,导致的性能浪费;
不同点:
事件节流throttle:指定时间周期内,只能执行一次;
运用
- 滚动到底部触发事件
函数防抖 debounce:指定时间周期后,才能执行此函数, 如果这周期内重复触发,就会重新开始计算
- 输入框实时搜索
- window resize
长列表优化?
思路:虚拟列表
- 只显示部分数据
- 通过监听滚动条的位置(向上滚动/向下)来进行,来进行对数组进行动态替换
- 顶部使用padding来代替
介绍下PWA,为什么service worker 可以提高性能 ?
定义:pwa渐近式增弹网页应用
PWA特点:
- 拥有桌面入口,可安装
- 原生应用界面: //在manifest文件中配置,自定义桌面图标/导航栏颜色
- 可离线访问 ,需要Https环境 //本地环境localhost也可以调试
- 支持Push推送
- 后台加载,哪怕页面关闭,pwa仍然可以在后台运行获取数据(有限制) //哪怕chrome关闭
service worker是html5中的api,它在web worker的基础上加了持久缓存和网络代理能力,结合cache api面向提供了js来操作浏览器缓存的能力。service worker接管网页请求,做为中间层;
service worker特点:- 拥有独立的执行线程,单独的作用域范围,单独的运行环境,有自己独立的context上下文。
- 由于有独立线程,service worker不能直接操作页面DOM。但可以通过事件机制来处理,例如使用postMessage
js事件
js事件机制?
javascript是事件驱动型语言,网页上的任意操作(键盘,鼠标)会产生一个“事件”(event),当事件发现时,可以对事件进行响应,具体如何响应某个事件由事件处理函数完成。
1)事件流
事件流描述的是从页面中接受事件的顺序
DOM事件流的三个阶段:
- 捕获阶段:先调用捕获阶段的处理函数 btn.addEventListen(‘click’),事件从window对象自上而下向目标节点传播的阶段;
- 目标阶段:调用目标阶段的处理函数 ;
- 冒泡阶段:调用冒泡阶段的处理函数,事件从目标节点自下而上向window对象传播的阶段;
bug说明 :下图中缺少:html标签
2)DOM事件级别
DOM级别分为四级,由于DOM1中没有事件相关的内容,即没有DOM1级事件,所以DOM事件分为3个级别,分别是:DOM 0/2/3
- DOM0:el.onclick=function(){} // btn
- DOM2: el.addEventListener(eventName,callback,useCapture)
- click事件
- DOM3: 在DOM2的基础上,增加更多的事件类型
- UI事件:load、scroll
- 焦点事件:blur、focus
- 鼠标事件:dbclick、mouseup
- 键盘事件:keydown、keypress
- …..
- 说明:DOM3级事件允许使用者自定义一些事件
3)事件代理
请参考:什么是事件委托/事件代理?
4)事件对象
event
- event.preventDefault() //阻止默认行为
- event.stoppropagation() //阻止事件冒泡
- event.target & event.currentTarget //目标对象,target指向事件真正的发出者,currentTarget指向监听事件者
5)铺获与冒泡的顺序问题
- 绑定多个DOM事件,先注册先执行
参考资料: 浏览器事件系统
什么是事件委托/事件代理?
事件委托就是利用事件冒泡,只指定一个事件处理程序,就可以管理某一类型的所有事件,利用父级去触发子级的事件;
优点:
- 节省内存占用,减少事件注册
- 新增子对象时,无需再次对其绑定事件,适合动态添加元素
缺点(局限性):
- focus,blur 事件本身没有事件冒泡机制,所以无法委托
- mousemove,mouseout,需要不断通过位置去计算定位,对性能消耗高,不适合事件委托
- 层级过多,冒泡过程中可能被某层阻止掉(建议就近委托)
<script>// jquery当中的事件委托,支持动态添加的元素$('#ui').on('click','li',function (event){console.log(event.target.innerHTML);})</script>
跨域
如何解决跨域的问题?
- CORS: 跨域资源共享,关键在于服务端设置, Access-Contorl-Allow-Origin 后台设置允许跨域访问的url;使用额外的HTTP头告诉浏览器
- nginx反向代理:修改nginx.conf文件
- nodejs中间件代理(webpack本地启动服务使用),在服务端拿到数据后,再把数据返回给前端
- websocket协议跨域
- 通过jsonp(淘汰)
- postMessage,可以实现多个页面之间的通讯(html5中的一个api)
其他不常用
- window.name : 通能过给一个设置window.name, 能过location.href的时候另外一个页面可以获取到相关数据,刷新也能;局限性:只能设置string类型
- document.domain
参考资料:
http://blog.alanwu.website/2020/03/06/crossOrigin/
https://www.jianshu.com/p/835bc9534281
this
bind,call,apply的区别?
共用点:
- 改变函数执行时的上下文,再具体点就是改变函数运行时this的指向
- 三者第一个参数都是this要指向的对象,如果未传入,默认指向全局window
- 三者都可以传入参数
不同点:写法不一样
bind: fn.bind(thisArg,队列or数组)()
call: fn.call(thisArg, arg1, arg2,…)
apply: fn.apply(thisArg, [argsArray])
可以做的几个事:
- 求数组中的最大和最小值;
- 利用call和apply做继承
const arr=[1,2,3]
Max.max.apply(null ,arr);
this的指向问题?
this 是函数运行时所在的环境对象
this用法的四种情况:
- 纯粹的函数调用
- 作用对象方法的调用
- 作为构造函数调用
- apply调用
this的指向原则:
- this永远指向一个对象
- this指向完全取决于函数调用的位置
参考:
apply,call区别?
apply,call作用都是改变this的指向,只是参数写入的不同
apply把参数放入一个数组当中 ;
call把参数依次写入;
let params={a:20}function fun(value1,value2){console.log(this.a,value1,value2);}//apply,call功能一样,都是为了改变this的指向,参数的写入方式有不同fun.apply(params,[11,12]);fun.call(params,11,12);
