JS进阶篇

js的垃圾回收机制是什么原理

  • 垃圾回收机制有两种方法
    • 第一种是标记清除法:当变量进入执行环境时,就标记这个变量为”进入环境”,当变量离开环境的时候,则将其标记为”离开环境”,垃圾收集器在运行的时候会给储存在内存中的所有变量都加上标记,然后它会去掉环境中的标量以及被环境中的变量引用的标记,而在此之后再被加上标记的变量将被视为准备删除的变量,原因是环境中的变量已经无法访问到这些变量了,最后,垃圾收集器完成内存清除工作,销毁那些带标记的值,并回收他们所占用的内存空间
    • 第二种是引用计数法:当声明了一个变量并将一个引用类型赋值给改变量是,则这个值得引用次数就是1,相反,如果包含对这个值引用的变量又取得了另外一个值,则这个值得引用次数就减1,当这个引用次数变成0时,则说明没有办法在访问这个值了,因而就可以将其所占的内存空间给收起来,这样垃圾收集器再下次运行时,它就会释放那些引用次数为0的值所占的内存

哪些操作会造成内存泄露,怎样避免内存泄露

  • 会造成内存泄漏的操作:

    • 意外的全局变量引起的内存泄露
    • 闭包引起的内存泄露
    • 控制台日志
    • 没有清理的DOM元素引用
    • 被遗忘的定时器或者回调
  • 避免内存泄露的操作:

    • 减少不必要的全局变量,或者生命周期较长的对象,及时对无用的数据进行垃圾回收
    • 注意程序逻辑,避免“死循环”之类的
    • 避免创建过多的对象 原则:不用了的东西要及时归还(置为null)

AMD\CMD区别

  • AMD即Asynchronous Module Definition,翻译过来就是异步模块化定义(RequireJS)
  • CMD即 common moudle definition,翻译过来即通用模块定义(SeaJS)
  • RequireJS在主文件里是将所有的文件同时加载,然而SeaJS强调一个文件一个模块。
  • AMD推崇依赖前置,CMD推崇依赖就近。
  • AMD加载完模块后,就立马执行该模块;CMD加载完某个模块后没有立即执行而是等到遇到require语句的时再执行
  • 所以,他们两者的不同导致各自的优点是AMD用户体验好,因为模块提前执行了;CMD性能好,因为只有用户需要的时候才执行。

面向对象的三个特性

  • 封装: 屏蔽内部细节 用户直接调用被封装的功能
  • 继承: 子类拥有父类的所有属性或方法
  • 多态:(js中不存在多态概念)

原型,原型链的理解

所有的构造函数都有一个prototype属性,这个属性也叫 原型对象 构造函数.prototype 所有的构造函数new出来的对象也都有一个原型对象

实现 :对象.__proto__ 原型链就是实例对象和原型之间的链接

  • 原型对象的执行流程:
    • 首先去实例上查找,如果找到了就返回
    • 如果没做查找到,就去改构造函数的原型上查找,如果找到了就返回,如果没找到,就去Object.prototype的原型上查找,找到了就返回,否则返回undefined

继承的方式

  • 通过改变父类的执行环境来实现
  • 通过call
  • 通过apply
  • 原型继承
  • 混合继承
  • es6构造函数

作用域链的理解

作用域链的作用是保证执行环境里有权访问的变量和函数是有序的,作用域链的变量只能向上访问,变量访问到window对象即被终止,作用域链向下访问变量是不被允许的

安全隐患:污染全局环境,或者造成内存泄露的问题,变量的提升

闭包的理解

什么是闭包: 一个函数内部返回一个匿名函数,这个函数就称为闭包,闭包中this指向 window

  • 特点

    • 函数嵌套函数
    • 函数可以引用外层的参数和变量
    • 参数和变量不会被垃圾回收机制回收
  • 闭包的缺点:常驻内存,会增大内存使用量,使用不当很容易造成内存泄露。

  • 为何要使用闭包:为了设计私有方法和变量,避免全局变量污染 希望一个变量长期驻扎在内存中

下面这个ul,如何点击每一列的时候alert其index(闭包解决方式)

<ul id="test">
    <li>这是第一条</li>
    <li>这是第二条</li>
    <li>这是第三条</li>
</ul>

方法一:

//将i属性绑定到标签对象中的index
var liItems=document.getElementById('test').getElementsByTagName('li');
for(var i=0;i<liItems.length;i++)
{
    liItems[i].index=i;
    liItems[i].onclick=function(){
        alert(this.index);
    };
}

方法二:

//将i属性通过参数传递至function作用域中,立即执行函数在下一次循环之前先将i绑定至作用域
var liItems=document.getElementById('test').getElementsByTagName('li');
for(var i=0;i<liItems.length;i++)
{
     liItems[i].onclick=(function(a){
        return function() {
            alert(a);
        }
    })(i);
}

方法三(es6的let产生暂时性死区,与声明的变量所在的块级作用域(for循环内)都不会造成闭包,var只受function的作用域影响,let受所有带‘{}‘大括号的作用域影响):

//通过let
var liItems=document.getElementById('test').getElementsByTagName('li');
for (let i = 0; i < liItems.length; i++) {
    liItems[i].onclick = function () {
        alert(i);
    }
}

高内聚低耦合的理解

  • 高内聚:模块内部高内聚 。一个系统有多个模块组成,在划分模块式,要把功能关系紧密的放到一个模块中,这就叫做高内聚

  • 低耦合:功能关系远的放到其它模块中。模块之间的联系越少越好,接口越简单越好,这叫做低耦合,也称为细线通信

TCP和UDP的最完整的区别

  • 基于连接与无连接
  • TCP要求系统资源较多,UDP较少
  • UDP程序结构较简单
  • 流模式(TCP)与数据报模式(UDP);
  • TCP保证数据正确性,UDP可能丢包
  • TCP保证数据顺序,UDP不保证

JS处理异步的方式

  • 利用回调函数(es5常用方法)
  • 用async和await来处理异步(es7-8中新增)
  • promise(es6新增)
  • 发布/订阅 我们假定,存在一个"信号中心",某个任务执行完成,就向信号中心"发布"(publish)一个信号,其他任务可以向信号中心"订阅"(subscribe)这个信号,从而知道什么时候自己可以开始执行。这就叫做"发布/订阅模式"(publish-subscribe pattern),又称"观察者模式"(observer pattern)(和事件原理一样)
  • 事件 触发一个事件也可以作为异步的处理 当触发某个事件再来执行某件事(js底层解决异步常用方法)

深拷贝浅拷贝的理解

  • 浅拷贝:只是复制当前的对象,该对象内部的引用(Object,Array等堆内存数据)不能复制

  • 深拷贝:对对象内部的引用均复制,是创建一个新的实例

简言之:是否复制了子对象,修改了克隆后的对象属性值,影响到原对象-浅拷贝 不影响-深拷贝

常见的HTTP请求返回状态码

  • 200成功
  • 304请求浏览器缓存的内容
  • 400语义有误,当前请求无法被服务器理解
  • 401当前请求需要用户验证
  • 404未找到
  • 403服务器已经理解请求,但是拒绝执行它
  • 500服务器错误
  • 503服务器端暂时无法处理请求

1开头的(信息类):表示接收到请求并且继续处理,用于指定客户端应相应的某些动作 2开头的(响应成功):表示动作被成功接收,理解和接受。 3开头的(重定向):为了完成指定的动作,必须接受进一步处理,用于已经移动的文件并且常被包含在定位头信息中指定新的地址信息 4开头的(客户端错误类):请求包含错误语法或不能正确执行 5开头的(服务器端错误):服务器遇到错误,无法完成请求

html页面怎么解析的?它加载顺序是什么?

  • 用户输入网址(假设是个html页面,并且是第一次访问),浏览器向服务器发出请求,服务器返回html文件
  • 浏览器开始载入html代码,如果发现标签内有一个标签引用外部CSS文件
  • 浏览器又发出CSS文件的请求,服务器返回这个CSS文件
  • 浏览器继续载入html中部分的代码,并且CSS文件已经加载完成了,开始渲染页面
  • 如果浏览器在代码中发现一个标签引用了一张图片,向服务器发出请求。此时浏览器不会等到图片下载完,而是异步渲染后面的代码
  • 服务器返回图片文件,由于图片占用了一定面积,影响了后面段落的排布,因此浏览器需要回过头来重新渲染这部分代码
  • 如果浏览器发现了一个包含一行Javascript代码的script标签,会立即运行它
  • 如果Javascript脚本执行了浏览器隐藏掉代码中的某个style(style.display=”none”),浏览器就得重新渲染这部分代码
  • 如果这时用户点了一下界面中的“换肤”按钮,Javascript让浏览器换了一下<link>标签的CSS路径,那么浏览器就会向服务器请求了新的CSS文件,重新渲染页面

总结:最好将无论内部或是外部JS文件放到所有html内容之后,这样会令用户感觉页面加载速度变快了,否则如果将所有外部文件(包括css和JS)引用都放到中,意味着必须等到全部的JS代码都被下载解析和执行完毕后,才能开始呈现页面的内容(当浏览器遇到),这样会导致呈现页面时出现明显的延迟,二延迟期间的浏览器窗口将是一片空白。

谈谈web攻击技术

  • XSS攻击
  • CSRF攻击
  • 网络劫持攻击
  • 控制台注入代码
  • 钓鱼

同步和异步的区别

  • 同步:阻塞的,A需要等待B完成任务后再开始任务
  • 异步:非阻塞的,A,B同时开始任务

打个简单的比方:Ajax请求数据渲染页面操作,需要使用同步方式渲染,因为js执行的时间很短,几乎可以忽略不计,而Ajax请求需要等待时间,所以,需要等待Ajax请求完毕,收到响应信息后再渲染页面

解释a = b||c,fn&&fn(),a=(b,c),a?b:c的作用或含义

a = b||c:和if(!b) {a=c}效果一致,如果b存在,把b的值赋给a,否则把c的值赋给a(短路求值,提升效率) fn&&fn(): 和上面的效果一样,但条件相反,如果fn不存在,则不执行,否则将执行 (短路求值,提升效率) a=(b,c): 这里是逗号运算符的用法之一,先执行运算符左侧的操作数,然后再执行右侧的操作数,最后返回右侧操作数的值,即a=c a?b:c: 条件表达式(三元表达式),若a为真,返回b,若a为假,则返回c

对if语句的优化

  • 把次数多的条件和执行结果放到最前面
  • 减少第一次无用的判断,可以用嵌套判断
  • 判断语句禁止出现三次嵌套

对switch的理解

  • switch的括号里面放的是一个变量

  • case相对应的值是关于这个变量的一个值

  • switch里面的这个变量和case里面这个变量不会进行隐式类型的一个转换,而是进行了恒等比较。所以一定要注意这个变量和这个case里 面的值是不是一个类型

  • 关于switch里面的case会有一个穿透效果,这个效果有的时候会给我们带来好处(详情请看最后一个案例),有的时候会给我们带来坏处, 如果不需要这种穿透效果的时候加break

  • wicth里面如果这个变量没有匹配到case里面这个值,那么就需要返回一个信息。所以在case的末尾一定要加上一个default;这样既给用 户的体验比较完美,另一方面对代码的今后维护也有很大的帮助

  • 比较的值是固定值

if和swicth的应用场景

  • if:

    • 具体的值进行判断
    • 区间的判断
    • 对运算的结果是boolean类型表达式进行判断 true false
  • switch:

    • 对具体的值进行判断
    • 值的个数是固定的

对于几个固定的值判断,建议使用switch 语句。因为switch 语句会将具体的答案都加载进内存,效率相对高一点 基于代码的可读性:如果条件较少时,if-else容易阅读,而条件较多时switch更容易阅读

do-while循环的使用及while的区别

do-while() 无论条件是否成立至少执行一次,和while规则一样,唯一不同的是do{}while会先执行一次先执行后判断

while和for的区别

for循环是知道了循环次数,while是不知道循环次数 for限定了循环次数 while是条件循环

break和continue return的区别

continue只是中止本次循环,接着开始下一次循环 ,只能出现在循环中 break用于完全结束一个循环,跳出循环体 不在执行break下面的代码,只能出现在选择或者循环中 return作用是返回函数的值,不在执行return下面的代码,只能出现函数中

函数的作用

  • 减少代码的编写(代码重复利用)
  • 隐藏处理细节,便于今后的修改和维护
  • 控制执行时机

对参数的理解

参数分为:形参和实参

有了参数以后可以使函数变的更加灵活 形参和实参要一一对应 如果对应的形参没有传值,那么值是undefined

对arguments的了解

  • 函数内部自带的一个对象
  • 存储的是所有的实参
  • 可以使用[ ]及下标访问arguments中的内容 arguments[0] 访问第一个实参
  • 可以使用 arguments.length 确定传入实参的个数
  • 最常用的用途:判断传入参数的个数(根据参数个数做不同的事情)

请说一下js的编译和执行

js的预编译:把var 和 function 定义的变量提升到script的最上方 赋值语句不会被提升,哪怕等号后面是一个function

js执行:代码从上往下执行

递归与循环的区别

递归算法:

  • 优点:代码简洁、清晰,并且容易验证正确性。
  • 缺点: 它的运行需要较多次数的函数调用,如果调用层数比较深,每次都要创建新的变量,需要增加额外的堆栈处理,会对执行效率有一定影 响,占用过多的内存资源。

递归算法解题的运行效率较低。在递归调用的过程中系统为每一层的返回点、局部变量等开辟了栈来储存。递归次数过多容易造成栈溢出等

注意:递归就是在过程或函数里调用自身; 使用递归策略时要注意的几个条件:必须有一个明确的递归结束条件,称为递归出口。递归需要有边界条件、递归前进段和递归返回段。当边界条件不满足时,递归前进。当边界条件满足时,递归返回。

循环算法:

  • 优点:速度快,结构简单。
  • 缺点:并不能解决所有的问题。有的问题适合使用递归而不是循环。如果使用循环并不困难的话,最好使用循环

Ajax 是什么? 如何创建一个Ajax?

AJAX全称是Asychronous JavaScript And Xml(异步的 JavaScript 和 XML) 它的作用是用来实现客户端与服务器端的异步通信效果,实现页面的局部刷新,早期的浏览器并不能原生支持ajax,可以使用隐藏帧(iframe)方式变相实现异步效果,后来的浏览器提供了对ajax的原生支持 其主要通过XMLHttpRequest(标准浏览器)、ActiveXObject(IE浏览器)对象实现异步通信效果 实现方式(gitee上的案例):

var xhr =null;//创建对象
if(window.XMLHttpRequest){
    xhr = new XMLHttpRequest();
}else{
    xhr = new ActiveXObject("Microsoft.XMLHTTP");
}
xhr.open(“方式”,”地址”,”标志位”);//初始化请求
xhr.setRequestHeader(“”,””);//设置http头信息
xhr.onreadystatechange =function(){}//指定回调函数
xhr.send();//发送请求

Ajax的优缺点

  • 优点:

    • 通过异步模式,提升了用户体验
    • 优化了浏览器和服务器之间的传输,按需获取数据,减少不必要的数据往返,减少了带宽占用
    • Ajax在客户端运行,承担了一部分本来由服务器承担的工作,减少了大用户量下的服务器负载。
  • 缺点:

    • ajax不支持浏览器back按钮
    • 安全问题 AJAX暴露了与服务器交互的细节
    • 对搜索引擎的支持比较弱
    • 破坏了程序的异常机制。

一个页面从输入 URL 到页面加载显示完成,发生了什么?

  • 当发送一个 URL 请求时,不管这个 URL 是 Web 页面的 URL 还是 Web 页面上每个资源的 URL,浏览器都会开启一个线程来处理这个请求,同时在远程 DNS 服务器上启动一个 DNS 查询。这能使浏览器获得请求对应的 IP 地址。
  • 浏览器与远程 Web 服务器通过 TCP 三次握手协商来建立一个 TCP/IP 连接。该握手包括一个同步报文,一个同步-应答报文和一个应答报文,这三个报文在 浏览器和服务器之间传递。该握手首先由客户端尝试建立起通信,而后服务器应答并接受客户端的请求,最后由客户端发出该请求已经被接受的报文。
  • 一旦 TCP/IP 连接建立,浏览器会通过该连接向远程服务器发送 HTTP 的 GET 请求。远程服务器找到资源并使用 HTTP 响应返回该资源,值为 200 的 HTTP 响应状态表示一个正确的响应。
  • 此时,Web 服务器提供资源服务,客户端开始下载资源。
  • 后续HTML页面解析参照上面的“html页面怎么解析的?它加载顺序是什么?”

JQuery一个对象为何可以同时绑定多个事件

低层实现方式是使用addEventListner或attachEvent兼容不同的浏览器实现事件的绑定,这样可以给同一个对象注册多个事件

对页面某个节点的拖曳

  • 给需要拖拽的节点绑定mousedown, mousemove, mouseup事件
  • mousedown事件触发后,开始拖拽
  • mousemove时,需要通过event.clientX和clientY获取拖拽位置,并实时更新位置
  • mouseup时,拖拽结束
  • 需要注意浏览器边界的情况
function mouseMove(ele, parent) {
    ele.addEventListener('mousedown', moveHandler);
    ele.style.position = 'absolute'
    function moveHandler(e) {
        if (e.type === 'mousedown') {
            parent.ele = this;
            parent.point = {
                x: e.offsetX,
                y: e.offsetY
            }
            parent.addEventListener('mousemove', moveHandler);
            parent.addEventListener('mouseup', moveHandler);
        } else if (e.type === 'mousemove') {
            this.ele.style.left = e.x - this.point.x + "px";
            this.ele.style.top = e.y - this.point.y + "px";
        } else if (e.type === 'mouseup') {
            parent.removeEventListener("mousemove", moveHandler);
            parent.ele = null;
            parent.point = null;
        }
    }
}

new操作符具体干了什么

  • 创建一个空对象,并且 this 变量引用该对象,同时还继承了该函数的原型。
  • 属性和方法被加入到 this 引用的对象中。
  • 新创建的对象由 this 所引用,并且最后隐式的返回 this

以下是模拟操作:

new TestObj('str')=function(){
    let obj={};  //创建一个空对象
    obj.__proto__=TestObj.prototype;
    //把该对象的原型指向构造函数的原型对象,就建立起原型了:obj->Animal.prototype->Object.prototype->null
    return TestObj.call(obj,arguments);// 绑定this到实例化的对象上
}

前端开发的优化问题

  • 减少http请求次数:CSS Sprites, JS、CSS源码压缩、图片大小控制合适;网页Gzip,CDN托管,data缓存 ,图片服务器。
  • 前端模板 JS+数据,减少由于HTML标签导致的带宽浪费,前端用变量保存AJAX请求结果,每次操作本地变量,不用请求,减少请求次数
  • 用innerHTML代替DOM操作,减少DOM操作次数,优化javascript性能。
  • 当需要设置的样式很多时设置className而不是直接操作style。
  • 少用全局变量、缓存DOM节点查找的结果。减少IO读取操作
  • 避免使用CSS Expression(css表达式)又称Dynamic properties(动态属性)。
  • 图片预加载,将样式表放在顶部,将脚本放在底部 加上时间戳。
  • 避免在页面的主体布局中使用table,table要等其中的内容完全下载之后才会显示出来,显示比div+css布局慢。

fetch和Ajax有什么不同

  • XMLHttpRequest 是一个设计粗糙的 API,不符合关注分离(Separation of Concerns)的原则,配置和调用方式非常混乱,而且基于事件的异步模型写起来也没有现代的 Promise,generator/yield,async/await 友好
  • fetch 是浏览器提供的一个新的 web API,它用来代替 Ajax(XMLHttpRequest),其提供了更优雅的接口,更灵活强大的功能。
  • Fetch 优点主要有: 语法简洁,更加语义化,基于标准 Promise 实现,支持 async/await

如何编写高性能的Javascript

  • 使用 DocumentFragment 优化多次 append
  • 通过模板元素 clone,替代 createElement
  • 使用一次 innerHTML 赋值代替构建 dom 元素
  • 使用 firstChild 和 nextSibling 代替 childNodes 遍历 dom 元素
  • 使用 Array 做为 StringBuffer ,代替字符串拼接的操作
  • 将循环控制量保存到局部变量
  • 顺序无关的遍历时,用 while 替代 for
  • 将条件分支,按可能性顺序从高到低排列
  • 在同一条件子的多( >2 )条件分支时,使用 switch 优于 if
  • 使用三目运算符替代条件分支
  • 需要不断执行的时候,优先考虑使用 setInterval

定时器setInterval有一个有名函数fn,setInterval(fn,500)与setInterval(fn(),500)有什么区别?

第一个是重复执行每500毫秒执行一次,后面一个只执行一次。

简述一下浏览器内核

浏览器内核又可以分成两部分:渲染引擎和 JS 引擎。它负责取得网页的内容(HTML、XML、图像等等)、整理讯息(例如加入 CSS 等),以及计算网页的显示方式,然后会输出至显示器或打印机。JS 引擎则是解析 Javascript 语言,执行 javascript 语言来实现网页的动态效果。

JavaScript的属性元数据有哪些?

  • writable:true属性值可修改
  • enumerable:true属性可枚举
  • configurable:true属性可重新配置
  • writable:false属性值不可修改
  • enumerable:false属性不可枚举
  • configurable:false 属性不可重新配置

懒加载(瀑布流)的实现原理

意义:懒加载的主要目的是作为服务器前端优化,减少请求数或延迟请求数实现原理:先加载一部分数据,当触发某个条件时利用异步加载剩余的数据,新得到的数据,不会影响有数据的显示,同时最大程度上减少服务器的资源消耗

  • 实现方式:
    • 延迟加载,使用setTimeOut或setInterval进行加载延迟
    • 符合某些条件,或触发了某些事件才开始异步下载
    • 可视区加载

js实现数组去重

双层循环,外层循环元素,内层循环时比较值, 如果有相同的值则跳过,不相同则push进数组

class MyArray extends Array {
    constructor() {
        super(...arguments)
    }
    distinct() {
        var myArr = this,
            list = []
        for (var i = 0; i < myArr.length; i++) {
            for (var j = i + 1; j < myArr.length; j++) {
                if (myArr[i] === myArr[j]) {
                    j = ++i;
                }
            }
            list.push(myArr[i]);
        }
        return list;
    }
}
var _arr = new MyArray(4, 5, 6, 7, 7, 7, 1, 1, 1, 2, 2, 2, 5, 8, 5, 2, 4, 4, 4, 6, 9);
console.log(_arr.distinct()); //[7, 1, 8, 5, 2, 4, 6, 9]

利用对象的属性不能相同的特点进行去重

class MyArray extends Array {
    constructor() {
        super(...arguments)
    }
    distinct() {
        var myArr = this,
            list = [],
            obj = {}
        for (var i = 0; i < myArr.length; i++) {
            obj[myArr[i]] || (obj[myArr[i]] = 1,
                list.push(myArr[i])) //如果能查找到,证明数组元素重复了
        }
        return list;
    }
}
var _arr = new MyArray(4, 5, 6, 7, 7, 7, 1, 1, 1, 2, 2, 2, 5, 8, 5, 2, 4, 4, 4, 6, 9);
console.log(_arr.distinct()); //[4, 5, 6, 7, 1, 2, 8, 9]

Set数据结构,它类似于数组,其成员的值都是唯一的

function dedupe(array) {
    return Array.from(new Set(array));
}
console.log(dedupe([1, 1, 2, 3])) //[1,2,3]

实现快速排序和冒泡排序

快速排序 :选取位置在数组中间的一个数,然后比它小的放在left[]的一个新数组里面,比他大的放在right[]的一个新数组里面,以此类推,重复执行这个过程,利用递归的思想,直至执行到left[]right[]里面都只有一个数

冒泡排序:两两比较,前面的比后面的大,则换位。第一轮list.length-1次,挑出最大的;第二轮list.length-1-1次,挑出第二大的。以此往复

class MyArray extends Array {
    constructor() {
        super(...arguments)
    }
    quickSort(list) { //快速排序
        var myArr = this,
            listConfig = {
                midItem: myArr[parseInt(myArr.length / 2)],
                leftList: new MyArray(),
                rightList: new MyArray()
            }
        if (myArr.length <= 1) {
            return myArr
        };
        for (var i = 0; i < myArr.length; i++) {
            myArr[i] < listConfig.midItem ? listConfig.leftList.push(myArr[i]) : myArr[i] > listConfig
                .midItem ? listConfig.rightList.push(myArr[i]) : '';
        }
        return listConfig.leftList.quickSort().concat([listConfig.midItem], listConfig.rightList
            .quickSort()); //递归
    }
    bubbleSort() { //冒泡排序
        for (var i = 0; i < this.length - 1; i++) {
            for (var j = 0; j < this.length - 1 - i; j++) {
                if (this[j] > this[j + 1]) {
                    var item = this[j];
                    this[j] = this[j + 1];
                    this[j + 1] = item;
                }
            }
        }
        return this
    }
}
var quickSortArray = new MyArray(19, 15, 18, 17, 11, 21, 14, 61, 13, 10, 25);
var bubbleSortArray = new MyArray(9, 5, 8, 7, 1, 2, 4, 6, 3, 10, 25);
console.log(quickSortArray.quickSort()); //[10, 11, 13, 14, 15, 17, 18, 19, 21, 25, 61]
console.log(bubbleSortArray.bubbleSort()); //[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 25]

谈谈节流和防抖,如何实现

节流:使频繁执行的函数,定时执行,高频率函数执行时,使执行率减少,每n秒才能执行一次,打个比方:每隔1秒钟,会执行5次滚动条滚动事件,我只让它每一秒执行一次(案例:网站中的返回顶部)

防抖:使频繁执行的函数,延时执行,高频率函数执行时,n秒内只执行一次,在事件内多次执行会延时,打个比方:用户在输入框中输入字符,当用户一直在输入时,我们做个延时,当用户输入完毕后会有一段时间停顿,若这个停顿时间大于我们的我们延时时间,我们就进行下一步操作,反之则不进行并且一直延时(案例:搜索引擎搜索输入框)

区别:对于高频率执行函数,节流是每隔规定时间都会执行一次,防抖是只在规定时间外的最后一次执行

var count = 0
class OptimizeEvent {
    constructor() {}
    throttle(fn, time) { //节流
        var canDo = true
        return function (e) {
            if (!canDo) {
                return false
            }
            canDo = false
            setTimeout(() => {
                fn.call(this)
                canDo = true
            }, time)
        }
    }
    debounce(fn, time) { //防抖
        var _timer = null
        return function () {
            if (_timer) {
                clearTimeout(_timer)
                _timer = null
            }
            _timer = setTimeout(fn, time)
        }
    }
}
var _event = new OptimizeEvent()
inputBox.addEventListener('input', _event.debounce(function () {
    showBox.textContent = inputBox.value
}, 1000))
document.addEventListener('scroll', _event.throttle(function () {
    console.log(count++)
}, 1000))

谈谈深拷贝的实现

深拷贝相对浅拷贝不同的是,深拷贝内所有引用类型属性值都是在新开辟的内存地址,被拷贝的原数据发生改变时不会影响复制后的对象。

  • 常见方法
    • JSON.parse(),JSON.stringify()
    • jQury的$.extend(true,{},obj)
    • lodash的_.cloneDeep
function deepClone(org, tag) {
    var tag = tag || {}; //初始化要复制的对象
    var name = Object.getOwnPropertyNames(org); //获取该对象的属性名,以字符串数组返回
    for (var i = 0; i < name.length; i++) { //遍历对象
        var desc = Object.getOwnPropertyDescriptor(org, name[i]); //获取对象的属性描述对象,无引用关系,返回另一个对象,改变时原对象不发生变化(复制的关键)
        if (typeof desc.value === 'object' && desc.value !== null) { //若遍历的每一项非空且为对象,则为引用值,则进行下一步
            var obj = desc.value.toString() === '[object Object]' ? {} : []; //判断是数组还是对象
            Object.defineProperty(tag, name[i], { //设置对象属性值,前三个的值是返回true或false
                configurable: desc.configurable, //是否可删除可替换
                enumerable: desc.enumerable, //是否可枚举可遍历
                writable: desc.writable, //是否可写入
                value: obj //对象的值
            });
            copyObj(desc.value, obj); //再次执行函数
        } else {
            Object.defineProperty(tag, name[i], desc); //否则直接将该对象的属性值进行复制(原始值)
        }
    }
    return tag;
}

常用的对象方法有哪些

  • 添加或更改对象属性 Object.defineProperty(object, property, descriptor)
  • 添加或更改多个对象属性 Object.defineProperties(object, descriptors)
  • 访问属性 Object.getOwnPropertyDescriptor(object, property)
  • 以数组返回所有属性 Object.getOwnPropertyNames(object)
  • 以数组返回所有可枚举的属性 Object.keys(object)
  • 访问原型 Object.getPrototypeOf(object)
  • 阻止向对象添加属性 Object.preventExtensions(object)
  • 如果可将属性添加到对象,则返回 true Object.isExtensible(object)
  • 防止更改对象属性(而不是值) Object.seal(object)
  • 如果对象被密封,则返回 true Object.isSealed(object)
  • 防止对对象进行任何更改 Object.freeze(object)
  • 如果对象被冻结,则返回 true Object.isFrozen(object)

实现以下输出

var a = ?
if (a == 1&& a == 2 && a == 3) {
    console.log('回答正确')
}
// 打印回答正确
class ConsoleA {
    constructor(){
        this.num = 0
        this.valueOf()
    }
    valueOf(){
        return this.num++
    }
}
var a = new ConsoleA()
    if (a == 1&& a == 2 && a == 3) {
        console.log('回答正确')
    }
    // 打印回答正确

因为直接调用a==1会进行隐式类型转换从而调用object的valueOf函数

思考以下代码输出,为什么?

var num = 9
switch (num) {
    default:
        console.log('default')
    case 1:
        console.log(1)
    case 2:
        console.log(2)
        break
}

输出 default 1 2 一般情况下default放在switch最后,作为类似于else的作用,而写在switch最上面就相当于if(true),而switch的特点是进入case之后不break跳出来就会一直执行而不做条件判断,所以后面case中的代码都会运行

请问以下两次检测对象constructor是否拥有属性名1的结果分别是什么?

1 in Object(1.0).constructor;
Number[1] = 123;
1 in Object(1.0).constructor;
  • A:false、true

解析:

首先可以认识一下 "in" :用于检查对象(数组)及其原型链中是否含有某属性,如

const arr = [0,2]
1 in arr // true

这里表示的是arr这个数组中存在 1 这个属性,即arr[1]

回归正题: Object(1.0) 相当于 new Number(1.0) ,new Number(1.0).constructor就拿到了Number构造函数,此时Number上还没有 1 这个属性,所以使用 in 返回false,而下一步中增加了这个属性,所以返回true

下面这段程序的显示结果是?

var x = new Boolean(false);
if (x) {
  alert('hi');
}
var y = Boolean(0);
if (y) {
  alert('hello');
}
  • A:hi

  • 解析:

Boolean(0)返回一个布尔类型的false 而new Boolean(false)返回一个布尔对象

以下哪些对象是Javascript内置的可迭代对象?

Array Map String Object

  • A:Array Map String

  • 解析:

    • ES6中对可迭代的定义:一种数据结构存在Symbol.iterator属性,就表示可迭代 在阮一峰的博客中还提到了以下可迭代的数据类型
      • Array
      • Map
      • Set
      • String
      • TypedArray
      • 函数的 arguments 对象
      • NodeList 对象

以下代码执行后,输出结果为

let x = 10;
let foo = () => {
    console.log(x);
    let x = 20;
    x++;
}
foo();
  • A:抛出ReferenceError

  • 解析:使用let定义变量会形成 "暂时死区" 即foo作用域内获取的x就是等于20的那个,然而使用let去定义变量,变量不会得到提升,所以在定义前获取x会报错

请问以下JS代码会输出什么

var a = 10;
(function a() {
    a = 20;
    console.log(a);
})()
  • A:输出函数a的内容

  • 解析: 立即执行函数中的a访问到的是函数a,当函数为IIFE的函数表达式时,与使用const类似(即无法修改这个值),与函数声明( function fun(){} )不相同,当执行a=20时,无法修改a的值

以下代码执行后,输出结果为

var a = 10;
function a(){}
console.log(typeof a)
  • A:输出 "number"

  • 解析:我的理解是var会将声明提升到作用域的最高点,function会在js编译时加载在内存中 可以简单理解为:函数声明大于变量声明大于变量赋值,所以原式转换成了下面这段代码

function a(){}
var a;
a = 10;
console.log(typeof a)

以下JavaScript代码,在浏览器中运行的结果是

var foo = {n:1};
(function(foo){
    console.log(foo.n);
    foo.n = 3;
    var foo = {n:2};
    console.log(foo.n);
})(foo);
8console.log(foo.n);
  • A: 1 2 3

  • 解析:根据var的变量提升,可以得到以下代码;

var foo = {n:1};
(function(foo){
    var foo = foo // 这一步是参数(形参)的声明,赋值
    var foo // 此操作优先级没有参数高,所以不生效
    console.log(foo.n);// 此时foo指代参数,n为1
    foo.n = 3; // 参数引用了全局的foo,所以全局的foo此时已经是3了
    foo = {n:2}; // 给参数重新赋值,相当于指向了一块新内存
    console.log(foo.n); // 打印新内存的值:2
})(foo);
console.log(foo.n);

请问以下JS代码的输出结果以及变量i的值是?

var i = 100;
function foo() {
    bbb: try {
        console.log("position1");
        return i++;  }
    finally {
        break bbb;
    }
    console.log("position2");
    return i;
}
foo();
  • A:position1、position2、101

  • 解析:在try中执行return不会打断finally的执行,但是没有打断i++执行,因此执行完try中的语句后,通过break bbb跳出到foo中的bbb级代码块,继续打印下面的position2

请问以下JS代码输出的结果是什么?

let obj = {
  num1: 117
}
let res = obj;// -----------1
obj.child = obj = { num2: 935 };// -----------2
var x = y = res.child.num2;// ----------3
console.log(obj.child);
console.log(res.num1);
console.log(y);
  • A:undefined、117、935

  • 解析:这道题主要考察两点:

1.引用类型赋值

2.连续赋值机制

下面我对1-3步做一个分析:

第1步在栈中新建res变量使其引用地址指向obj,即与其共用一个堆地址(这一步可以看做是题目中对第二步的一个伏笔);

第2步是一个连续复制,赋值从右往左看,理解为以下代码,因为在赋值时obj指向了新的堆地址,obj.child中的obj已经不是赋值后的obj了,可以把它暂时看作是res

obj = { num2: 935 };
res.child = obj;

那么此时 res 就是{child: {num2:935}, num1: 117 },obj 就是{ num2: 935 };

第3步,连续复制,理解为以下代码

window.y = res.child.num2;
var x = window.y

此时res.child.num2是935,所以x和y都是935

在浏览器控制台中执行以下代码,输出的结果是

function test() {
    var n = 4399;
    function add(){
        n++;
        console.log(n);
    }
    return {n:n,add:add}
}
var result = test();// ------1
var result2 = test();// ------2
result.add();// ------3
result.add();// ------4
console.log(result.n);// ------5
result2.add();// ------6
  • A:4400 4401 4399 4400

  • 解析:这道题考察闭包和函数作用域,首先咱得理解的是通过步骤1和2,产生的result和result2是两个不同对象(两个对象指向的堆地址不是一个,虽然内容一样),其次要了解的是test函数和add函数之间产生了闭包关系,所以执行3和4步骤后,n发生了变化,然而值得注意的是,此时的result.n却没有发生变化,这是因为在test函数作用域中,n作为一个基本类型的值随着函数执行完后随着return返回出来了,此时在add中修改的n与result.n已没有关联了。为了更好理解上述说法,我修改了代码,得出以下代码:

function test() {
    var n = {num:4399};
    function add(){
        n.num++;
        console.log(n);
    }
    return {n:n,add:add}
}
var result = test();
var result2 = test();
result.add() // {num: 4400}
result.add() // {num: 4401}
console.log(result.n) // {num: 4401}

这样写应该会有助于理解

言归正传,执行第5步时就是打印最早传入的n(4399),第六步由于上面解析中提到的步骤1和2,产生的result和result2是两个不同对象,所以,可以理解为result2也来了一次步骤3

贡献者: mankueng