js原生

如何判断两个对象是不是相等?

  1. 【转成字符串判断】(不支持函数,正则), JSON.stringife(obj);JSON.parse(str)
  2. 插件:
    1. loadsh: 不支持函数和DOM节点比较
  3. 【循环遍历】

堆和栈的区别?

1)相同点:

堆和栈是两种数据结构,都是一种按序排列的数据结构,只能在一端(称为栈顶top) 对数据项进行插入和删除。堆栈是个特殊的存储区,主要功能是暂时存放数据和地址。

2)不同点

堆:堆内存一般是由语言运行环境分配管理,如python虚拟机控制内存和运行环境

栈:

堆(heap) 栈(stack)
定义 数据存储结构 定义一个变量时,计算机会在内存中开辟一块存储空间来存放这个变量的值,这块空间叫做栈
时效性 持久化 临时
Context(执行上下文) 全局 局部
内存分配 手动申请/释放,若不释放,程序结束时可能由OS回收 自动申请/释放(出栈时),系统分配是有大小限制的,超过就会内存溢出报错
数据存放类型 对象类型 基本数据类型
特点 / 先进后出

栈示意图(先进后出):

javascript - 图1

  1. let obj={
  2. name:'girl friend'
  3. }
  4. b=obj;
  5. b.name='strange';
  6. console.log(obj);

javascript - 图2
堆示意图:

  1. var arr = [1,2,3];
  2. brr = arr;
  3. brr[0] = 10;
  4. alert( arr[0] );

javascript - 图3

堆栈关系:

【栈】左侧:栈中存放引用地址,这个地址在计算机中叫做引用变量

【堆】右侧:堆中存放对象,多个引用地址会指向同一个对象,JS节省存储空间

javascript - 图4

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引擎执行过程: 宏任务(同步任务) —-> 微任务 —> 宏任务(异步任务)

javascript - 图5

柯里化函数的例子,并说明柯里化的好处?

柯里化是一种模式,把一个具有多个参数的函数被分解成多个函数,当被串联调用时,这些函数将一次累加一个所需的所有参数。这种技术有助于使用函数式编写的代码更容易阅读和编写。需要注意的是,要实现一个函数,它需要从一个函数开始,然后分解成一系列函数,每个函数接受一个参数。

优点:

  • 【预置共有参数】
  • 【拆分大的函数】每个函数执行一个独立的任务,能过串联的方式拼接起来
  1. // 预置共用参数
  2. function add(x){
  3. return function (y){
  4. return x+y;
  5. }
  6. }
  7. // 预置参数
  8. let add1=add(1)
  9. // 二次传参,互不影响
  10. console.log(add1(10)); // 11
  11. console.log(add1(20)); // 21

使用场景:

  • 使代码便于理解 //react-redux,connect方法, 将component要用到的state切面和action注入到它的property中,达到ui组件,和容器组件分离的目的。
    let Container=connect(mapStateToProps,mapDispatchToProps)(Component);
  1. let { Map }=Immutable;
  2. let obj={
  3. a:1,
  4. b:2
  5. }
  6. //克隆出一个
  7. let map=Map(obj);
  8. //调用immutable中的map对象下的set方法来修改指定key的value
  9. let map1=map.set('a',10);
  10. let map2=map1.set('a',20);
  11. //两个对象修改互不影响
  12. 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; 会导致判断不准确
  1. //最通用的方法
  2. var arr = [];
  3. function isArray(str){
  4. return Object.prototype.toString.call(str) == "[object Array]";
  5. }
  6. console.log(isArray(arr));//ie所有版本均支持;

哪些操作会造成内存泄漏?

内存泄漏原因:不再用到的内存,没有及时释放,就叫做内存泄漏;

  • 全局变量缓存数据 (无法被垃圾回收机制收集)
  • 定时器没有清除
  • 闭包的循环引用
  • js错误引用dom元素 (dom虽然删除,但是引用还在内存中)

js垃圾回收机制?

  • 引用计数 (如果没有引用指向该对象,对象将被垃圾回收机制回收,缺点:在循环引用的情况下,存在局限性)
  • 标记清除 (处理循环引用的问题,和引用计数本质相同,可达内存被标记,其余的被当作垃圾回收)

什么是作用域链?

作用域链就是【变量】和【函数】和可访问范围,

控制着变量和函数的可见性与生命周期,在js中变量的作用域有全局作用域和局部作用域 ;

什么是闭包,如何使用,闭包有什么问题?

闭包是由函数创建的一个词法作用域,里面的变量被引用后(外部函数引用,前提得先return出来),可以在这个词法环境之外使用;

函数嵌套函数,能够读取【其他函数】内部变量的【函数】;

闭包一定得return才能读取内部的值;

闭包的原理就是作用域链

用途

  1. 创建内部变量,使得这些变量不能随意被外部修改,但是又可以通过指定的函数接口修改
  2. 减少全局变量
  3. 让这些值始终保持在内存中

闭包的问题:

  • 耗性能:闭包会使函数内的变量一直保持在内存当中,

解决:在退出函数之前,将不使用的函数变量全部删除;

//使用自执行函数写法

  1. //定义一个闭包
  2. let secretFn=(function (){
  3. //变量secrent只有secretFn内的getSecret, setSecret 才可以访问,外部无法访问
  4. let secrent='100';
  5. let getSecret=function (){
  6. return secrent;
  7. }
  8. let setSecret=function (newSecrent) {
  9. secrent=newSecrent;
  10. }
  11. return {
  12. getSecret,
  13. setSecret
  14. }
  15. })()
  16. console.log(secretFn.getSecret());
  17. secretFn.setSecret(200);
  18. console.log(secretFn.getSecret());
  19. console.log(secretFn.secrent); //Type error 无法访问

//使用new写法

  1. //定义一个闭包
  2. let SecretFn=function (){
  3. //变量secrent只有secretFn内的getSecret, setSecret 才可以访问,外部无法访问
  4. let secrent='100';
  5. this.getSecret=function (){
  6. return secrent;
  7. }
  8. this.setSecret=function (newSecrent) {
  9. secrent=newSecrent;
  10. }
  11. }
  12. const secretOne=new SecretFn();
  13. console.log(secretOne.getSecret());
  14. secretOne.setSecret(200);
  15. console.log(secretOne.getSecret());
  1. function F() {
  2. let a = 1
  3. window.G = function () {
  4. console.log(a)
  5. }
  6. }
  7. F()
  8. G() // 1, G能访问到F函数的变量,他就是闭包

如何获取闭包函数内的变量?

  1. 函数嵌套,闭包
  2. 函数返回值,return

js有哪几种数据类型?

js一共七种类型;

基本类型(6):String,Boolean,Number,Undefined,Null,Symbol(创建唯一的值)

引用类型(1):Object(Function,Array, Date, RegExp)

js如何判断数据类型?

  1. typeof :不能判断array,object,function;
  2. instanceof :可以使用可以用来判断数组; [1] instanceof Array 返回 true
  3. toSring Object.prototype.toString.call([]) 返回true
  4. constructor [].constructor===Array 返回true

js有哪些内置对象?

object是js中所有对象的父对象;

数据封闭类对象:object,array,boolean,number,string

其他对象:function,arguments,math,date,RegExp,error

js如何实现继承?

  1. 通过原型链(prototype)来实现
  2. 通过构造函数继承-(在子函数中使用)
  3. es6的class,配合extend来实现继承
  4. 拷贝继承(创建一个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);

深拷贝指拷贝后的对象与原来的对象相互独立,修改任何一方,其他一方都不受影响;

深拷贝主要有

  1. es6的 spread展开,可以深拷贝数组,对象;
  2. for循环依次读取复制
  3. 先通过JSON.stringify把对象转化成字符串,再通过 JSON.parse把字符串转换成对象 (如果对象中包含函数,正则表达式会丢失;对象有循环引用会报错;)
  4. 外部库: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标签

javascript - 图6

2)DOM事件级别

DOM级别分为四级,由于DOM1中没有事件相关的内容,即没有DOM1级事件,所以DOM事件分为3个级别,分别是:DOM 0/2/3

  • DOM0:el.onclick=function(){} // btn
  • DOM2: el.addEventListener(eventName,callback,useCapture)
    • click事件

3)事件代理

请参考:什么是事件委托/事件代理?

4)事件对象

event

  • event.preventDefault() //阻止默认行为
  • event.stoppropagation() //阻止事件冒泡
  • event.target & event.currentTarget //目标对象,target指向事件真正的发出者,currentTarget指向监听事件者

5)铺获与冒泡的顺序问题

  • 绑定多个DOM事件,先注册先执行

参考资料: 浏览器事件系统

什么是事件委托/事件代理?

事件委托就是利用事件冒泡,只指定一个事件处理程序,就可以管理某一类型的所有事件,利用父级去触发子级的事件;

优点:

  • 节省内存占用,减少事件注册
  • 新增子对象时,无需再次对其绑定事件,适合动态添加元素

缺点(局限性):

  • focus,blur 事件本身没有事件冒泡机制,所以无法委托
  • mousemove,mouseout,需要不断通过位置去计算定位,对性能消耗高,不适合事件委托
  • 层级过多,冒泡过程中可能被某层阻止掉(建议就近委托)
  1. <script>
  2. // jquery当中的事件委托,支持动态添加的元素
  3. $('#ui').on('click','li',function (event){
  4. console.log(event.target.innerHTML);
  5. })
  6. </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用法的四种情况:

  1. 纯粹的函数调用
  2. 作用对象方法的调用
  3. 作为构造函数调用
  4. apply调用

this的指向原则:

  1. this永远指向一个对象
  2. this指向完全取决于函数调用的位置

参考:

彻底搞懂JavaScript中的this指向问题

Javascript 的 this 用法

apply,call区别?

apply,call作用都是改变this的指向,只是参数写入的不同

apply把参数放入一个数组当中 ;

call把参数依次写入;

  1. let params={
  2. a:20
  3. }
  4. function fun(value1,value2){
  5. console.log(this.a,value1,value2);
  6. }
  7. //apply,call功能一样,都是为了改变this的指向,参数的写入方式有不同
  8. fun.apply(params,[11,12]);
  9. fun.call(params,11,12);