前端面经总结(七)

前端面经总结(六)

事件循环

事件循环是为了解决js单线程运行阻塞的问题而产生。在js中所有任务可以分为同步任务(立即执行的任务,一般直接进入主线程中执行)和异步任务(如ajax/setTimeout等)。

同步任务进入主线程(主执行栈),异步任务进入任务队列,主线程内任务执行完毕为空,则去任务队列读取对应的任务,推入主线程执行,以上不断重复的过程即为事件循环。

宏任务/微任务

  • 宏任务:由Node/浏览器发起的任务,每次从宏任务事件队列中获取一个放入执行栈中执行

    • script

    • setTimeout/setInterval

    • I/O

    • UI交互事件

    • postMessage()

    • setImmediate: 遇到setTimeout,先于它执行

  • 微任务:由JavaScript发起的任务,当前任务执行后立刻执行,无需等渲染。

    • Promise

    • process.nextTick

    • Object.observe

常见考题

  1. 基础题
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
console.log(1)//主线程

setTimeout(()=>{//放入宏任务队列
console.log(2)
}, 0)

new Promise((resolve, reject)=>{//主线程
console.log('new Promise')//主线程
resolve()//微任务,放入微任务队列
}).then(()=>{
console.log('then')//微任务
})

console.log(3)//主线程
//输出:1 -> new Promise -> 3 -> then -> 2
  1. 复杂题
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
async function async1() {//函数定义,未调用,不管
console.log('async1 start')//4.主线程
await async2()//5.遇到await,先执行async2,阻塞下面代码console,即加入微任务1
console.log('async1 end')
}
async function async2() {//函数定义,未调用,不管
console.log('async2')//6.执行async2,打印
}
console.log('script start')//1.主线程
setTimeout(function () {//2.宏任务,放入宏任务队列1
console.log('settimeout')
})
async1()//3.调用async1
new Promise(function (resolve) {//7.new Promise主线程
console.log('promise1')//8. 打印
resolve()//9. 微任务,放入微任务队列2
}).then(function () {
console.log('promise2')
})
console.log('script end')//10.主线程,打印

//第一趟主线程输出:script start -> async1 start -> async2 -> promise1 -> script end
//开始微任务,输出:async1 end -> promise2
//微任务执行完毕,执行下一个宏任务,输出:settimeout

//最终输出:script start -> async1 start -> async2 -> promise1 -> script end -> async1 end -> promise2 -> settimeout

执行顺序

  1. 执行一个宏任务,如果遇到微任务就将它放到微任务的事件队列中

  2. 当前宏任务执行完成后,会查看微任务的事件队列,然后将里面的所有微任务依次执行完

  3. 遇到asyncawaitawait会阻塞下面的代码(即加入微任务队列),先执行async外面的同步代码,同步代码执行完,再回到async函数中,再执行之前阻塞的代码

vue双向绑定原理

通过数据劫持结合发布订阅者模式实现

vue通过Object.defineProperty()实现数据劫持,它可以控制一个对象属性的特有操作,例如读写权/枚举等,

MVVM主要包含两方面,数据变化更新视图,视图变化更新数据。其中视图变化更新数据只需要通过事件监听即可。

数据变化更新视图

通过Object.defineProperty()对属性设置一个set函数,当数据改变后触发set函数,故将更新方法写入set即可实现data更新view

  1. 实现监听器Observer,用来劫持监听所有属性,若有变动,则通知订阅者

  2. 实现订阅者Watcher,可收到属性的变化通知并执行相应的函数,从而更新视图

  3. 实现解析器Compile,扫描解析每个节点的相关指令,并根据初始化模版数据以及初始化相应的订阅器

虚拟dom

页面更新可以先全部反映到虚拟dm中,操作内存中的js对象速度会更快,当更新完成后,再将最终的js对象映射到真实的dom中,由浏览器绘制渲染

dom diff

  1. 思想:DOM Diff过程即对比新旧两份VNode的过程。
  • 旧的VNode(即oldVNode)就是数据变化之前视图所对应的虚拟DOM节点

  • 新的VNode是数据变化之后将要渲染的新的视图所对应的虚拟DOM节点

以生成的新的VNode为基准,对比旧的oldVNode

  • 如果新的VNode上有的节点而旧的oldVNode上没有,那么就在旧的oldVNode上加上去;
  • 如果新的VNode上没有的节点而旧的oldVNode上有,那么就在旧的oldVNode上去掉;
  • 如果某些节点在新的VNode和旧的oldVNode上都有,那么就以新的VNode为准,更新旧的oldVNode,从而让新旧VNode相同。
  1. 算法描述
  • 遍历整个虚拟节点树,找出所有节点差异,记录在补丁包中
  • 遍历结束后根据补丁包执行addPatch()逻辑更新视图
  1. 对比算法
  • 创建节点:新的VNode中有而旧的oldVNode中没有,就在旧的oldVNode中创建。先判断节点类型,再根据不同类型调用不同方法创建节点。

    • 调用createElement创建元素节点
    • 调用createComment创建注释节点
    • 调用createTextNode创建文本节点
  • 删除节点:新的VNode中没有而旧的oldVNode中有,就从旧的oldVNode中删除。在要删除节点的父元素上调用removeChild方法删除节点

  • 更新节点:新的VNode和旧的oldVNode中都有,就以新的VNode为准,更新旧的oldVNode

    • 判断节点类型

      • 元素节点:判断VNode中是否有tag标签。判断是否包含子节点

        • 包含子节点:查看oldVNode是否包含子节点,若包含则递归对比更新子节点,若不包含,则判断是空节点还是文本节点,空节点则在新节点VNode里的子节点创建一份插入到oldVNode中,若为文本节点,则将文本清空,将新节点VNode的子节点创建一份插入到oldVNode中。

        • 不包含子节点:若同时又不是文本节点,则说明是空节点,直接清空旧节点oldVNode

      • 注释节点:判断VNodeisComment是否为true。注释节点是静态节点,不包含任何变量,故数据发生任何变化均与他无关,更新节点时直接跳过。

      • 文本节点:不是元素节点和注释节点则为文本节点。更新时对比文本内容是否一致。若不同,则把oldVNode里的文本改成跟VNode的文本一样。若oldVNode不是文本节点,则直接使用setTextNode方法将它改成文本节点

  1. 完整的树比较算法时间复杂度过高,DOM-Diff中使用的算法是只对新旧两棵树中的节点进行同层比较,忽略跨层比较。

dom概念及分类

Dom即HTML的层级结构,有Dom元素树和Dom文档树。

其节点类型有:元素节点、属性节点、文本节点、文档节点、注释节点、实体名称节点、处理指令节点等

获取元素节点

  • document.getElementById: 通过id获取元素节点 
  • getElementsByTagName: 通过标签名称获取元素节点
  • getElementsByClassName: 通过类名获取元素节点
  • getElementsByName: 通过表单元素的name获取元素节点
  • querySelector(“[selector]”): 支持一切css中的选择器,但如果匹配多个,只会返回第一个
  • querySelectorAll(“[selector]”): 返回所有选中的元素

a==2 && a==3

js调用==比较时,会触发类型转换,即两个函数toString()valueOf,每比较一次,调用一次

1
2
3
4
5
6
let a = {
i: 1,
valueOf:()=>{
return a.i++;
}
}

a===2&&a===3

使用Object.defineProperty为对象定义一个属性,此时该属性具有getter方法,即可以在代码中被直接访问到。

1
2
3
4
5
6
var value = 0;
Object.defineProperty(window,'a',{
get: function(){
return this.value ++;
}
})

实现元素拖拽

  1. 使用HTML5的API:dataTransfer

    • 为拖拽的元素设值允许拖拽,赋予dragstart事件将其id转换为数据保存

    • 为容器添加dragover属性添加事件组织浏览器默认事件,允许元素放置,并赋予drop事件进行元素放置

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      <!-- 参数要传入event对象 -->
      <div class="box1" ondragover="allowdrop(event)" ondrop="drop(event)">
      <img src="img/2.jpg" alt="00" draggable="true" ondragstart="drag(event)" id="drag" width="50" height="50">
      <span>我是盒子一</span>
      </div>
      <div class="box2" ondragover="allowdrop(event)" ondrop="drop(event)">
      <span>我是盒子二</span></div>
      <script>
      function allowdrop(e) {
      e.preventDefault();
      }

      function drop(e) {
      e.preventDefault();
      var data = e.dataTransfer.getData("text");
      e.target.appendChild(document.getElementById(data));
      }

      function drag(e) {
      e.dataTransfer.setData("text", e.target.id);
      }
      </script>
  2. 使用原生js

    • 获取鼠标距离元素左边界和上边界的距离

    • 移动后计算元素相对原来位置的相对距离,赋予样式

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      <div class="box1" id="drag">
      <span>我是盒子一</span>
      </div>
      <div class="box2">
      <span>我是盒子二</span></div>
      <script>
      let drag = document.querySelector("#drag");//获取操作元素
      drag.onmousedown = function (e) {//鼠标按下触发
      var disx = e.pageX - drag.offsetLeft;//获取鼠标相对元素距离
      var disy = e.pageY - drag.offsetTop;
      console.log(e.pageX);
      console.log(drag.offsetLeft);
      document.onmousemove = function (e) {//鼠标移动触发事件,元素移到对应为位置
      drag.style.left = e.pageX - disx + 'px';
      drag.style.top = e.pageY - disy + 'px';
      }
      document.onmouseup = function(){//鼠标抬起,清除绑定的事件,元素放置在对应的位置
      document.onmousemove = null;
      document.onmousedown = null;
      };
      e.preventDefault();//阻止浏览器的默认事件
      };
      </script>

实现防抖/节流函数

  1. 防抖函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
function debounce(func,delay){
var timeout;
return function(e){
//清除旧timeout
clearTimeout(timeout);
var context = this, args = arguments;
//新timeout
timeout = setTimeout(function(){
func.apply(context,args);
},delay)
}
}
//第一次触发会立刻执行
const debounceImmediate = (cb, delay = 1000, immediate = true) => {
let timer = null;
return function (...args) {
const context = this;
const execNow = immediate && !timer;
if (timer) clearTimeout(timer);
timer = setTimeout(() => {
cb.apply(context, args);
timer = null;
}, delay);
execNow && cb.apply(this, args);
}
}
  1. 节流函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
function throttle(func, delay){
let timeout;
let start = new Date;
return function(){
let context = this, args = arguments, curr = new Date() - 0;
let remain = delay - (curr - start);
if(timeout)
clearTimeout(timeout);
if(remain <= 0>){
//第一次触发执行
func.apply(context,args);
start = curr;
}else{
//事件结束后也执行一次
timeout = setTimeout(function(){
func.apply(context,args)
timeout = null
},delay);
}
}
}

vue直接赋值修改数组,页面无法同步更新

vue由于js限制,不能同步更新,它会绕开监听器,无法实现动态效果。若需要实时更新,需要使用push()/pop()等方法

vue-router实现原理

2种模式:hash/history

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
 // 根据mode确定history实际的类并实例化    
switch (mode) {
case 'history':
this.history = new HTML5History(this, options.base)
break
case 'hash':
this.history = new HashHistory(this, options.base, this.fallback)
break
case 'abstract':
this.history = new AbstractHistory(this, options.base)
break
default:
if (process.env.NODE_ENV !== 'production') {
assert(false, `invalid mode: ${mode}`)
}}
  • hash:url的hash#开头,基于location.hash实现,值为#后的内容。当hash改变时,页面不会刷新,浏览器也不会请求服务器。hash改变时,触发相应的hashchange事件,操控路由改变,切换内容

  • history:通过使用pushStatereplaceState改变url地址,同时使用onpopState事件,刷新页面向服务器发送请求。需要特别注意的是,调用history.pushState()history.replaceState()不会触发popstate事件。只有在做出浏览器动作时,比如点击后退、前进按钮或者调用JS中的history.back()history.forward()history.go()才会触发该事件。

HashHistory

替换路由有两个方法HashHistory.push()HashHistory.replace()

  • HashHistory.push()将新路由添加到浏览器访问离是栈顶。

    1. $router.push()
    2. HashHistory.push():根据hash模式调用,设置hash并添加到浏览器历史记录,即window.location.hash=xxx
    3. History.transitionTo():监听更新,若更新则调用
    4. History.updateRoute():更新路由
    5. app._route = route:替换路由
    6. vm.render():更新视图
  • HashHistory.replace()替换当前路由

HTML5History

History interface是浏览器历史记录栈提供的接口,通过back()/forward()/go()等方法,读取浏览器历史记录栈的信息,进行跳转操作。HTML5后通过使用pushState()/replaceState()读取、修改浏览器历史记录栈

1
2
window.history.pushState(stateObject, title, URL)
window.history.replaceState(stateObject, title, URL)
  • stateObject: 当浏览器跳转到新状态时,将触发popState事件,该事件携带此参数副本

  • title:所添加记录的标题

  • URL:所添加记录的url

事件冒泡/事件捕获/事件委托

解决页面中事件流的问题,即解决事件发生的顺序问题

1
2
3
4
5
6
7
8
9
10
11
12
13
<div id="div1">
<div id="div2">
<p id="p1">事件</p>
</div>
</div>
<script>
let div1 = document.getElementById('div1');
let div2 = document.getElementById('div2');
let p1 = document.getElementById('p1');
div1.onclick = function (){ console.log("click div1")};
div2.onclick = function (){ console.log("click div2")};
p1.onclick = function (){ console.log("click p1")};
</script>
  • 事件冒泡:事件从最内层的元素开始发生,一直向上传播,直到document对象。如上述代码,事件发生顺序为<p>=><div id="div2">=><div id="div1">。阻止事件冒泡使用event.stopPropagation()方法。取消默认事件使用event.preventDefault()

  • 事件捕获:事件从最外层元素开始发生,直到最具体的元素。如上述代码,事件发生顺序为<div id="div1">=><div id="div2">=><p>

方法addEventListener(event, function, useCapture)用来为特定元素绑定事件处理函数,其参数分别为:没有on的事件类型event/事件处理函数function/控制事件阶段useCapture。其中useCapture默认为false,即在事件冒泡阶段调用事件函数,若为true,即在事件捕获阶段调用事件函数

  • 事件委托:事件目标自身不处理事件,而是把处理任务委托给其父元素/祖先元素或者根元素document

DOM标准事件流的触发的先后顺序为:先捕获再冒泡,即当触发dom事件时,会先进行事件捕获,捕获到事件源之后通过事件传播进行事件冒泡。

常用的冒泡事件为: touchstart/touchmove/tap/```animationstart``等

常见的非冒泡事件(组件的自定义事件若无特殊声明都为非冒泡事件):formsubmit事件,inputinput事件,scroll-viewscroll事件。

typeof null/typeof undefined输出

  • typeof null = object:null表示一个空对象的引用,即一个变量不再指向任何对象地址

  • typeof undefined = undefined:undefined表示一个没有赋值的变量

异步async实现sleep函数

  1. promise解决callback会产生的回调地狱,且不能捕获错误的问题而产生,它实现了链式调用,每次返回都是一个全新的promise。但无法取消promise,错误也需要通过回调函数来捕获。

  2. async/await也解决回调地狱的问题,且不会向promise产生一系列的then链。但他的缺点是await将异步代码改造成同步代码,若多个异步操作没有依赖性,则await会导致性能上的降低

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
function sleep(ms) {
return new Promise(resolve => {
setTimeout(resolve, ms);
})
}

async function fn() {
await sleep(1000);
}

fn()

//promise
function sleep = time=>{
return new Promise(resolve => {
setTimeour(resolve,ms);
})
}
sleep(1000).then(()=>{
console.log("111");
})

instanceof实现

1
2
3
4
5
6
7
8
9
10
11
function fn(a,b){
let type = b.prototype;//类型原型
let obj = a._proto_;
while(true){
if(type === obj)
return true;
obj = obj._proto_;
if(obj === null)
return false;
}
}

jsonp原理及实现

jsonp是json的使用模式,可以让网页从别的域名获取资料,即跨域读取数据。由于同源策略,所以需要使用jsonp

原理:利用script标签的src没有跨域限制来完成

过程:

  1. 前端定义解析函数jsonCallback = function(res){},并把callback名字jsonpCallback传递给服务器
  2. 后端生成JSON数据,并以此JSON数据为入参生成function,function名字为传入的jsonpCallback。function语法为js,返回给客户端
  3. 前端在script标签返回资源时,执行jsonpCallback并通过回调函数的方式拿到数据。

jsonp实现

  1. 客户端jQuery实现
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>JSONP 实例</title>
<script src="https://cdn.static.runoob.com/libs/jquery/1.8.3/jquery.js"></script>
</head>
<body>
<div id="divCustomers"></div>
<script>
$.ajax('https://www.runoob.com/try/ajax/jsonp.php', {
method: 'post',
contentType: 'application/javascript;charset=utf-8',

dataType: 'jsonp', // jsonp方式
jsonp: 'jsoncallback', // 回调函数名-参数名
success: function (result) {
// 回调函数
console.log(result);
}
});
$("#jkl").html(parseInt($("#jkl").html()) + 1);
}
</script>
</body>
</html>
  1. 服务端实现
1
2
3
4
5
6
7
8
9
10
protected void service(HttpServletRequest request, HttpServletResponse resp) throws ServletException, IOException {

String msg = "测试信息";
// jsonp跨域协议
resp.addHeader("content-type", "application/javascript");
String func = request.getParameter("jsoncallback");
PrintWriter pw = resp.getWriter();
pw.print(func + "('" + msg + "');");
pw.flush();
}

jsonp如何处理多个请求

在发送请求前判断当前是否有相同的jsonp请求正在发送,如果发生冲突则取消本次请求,等到上一次请求完成后再重新请求。也就是说把并发的请求转换为穿行执行。可以使用Promise链式调用处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
myjsonp.JSONP({
url: 'http://url',
data: {
test: 1
},
cache: true,
dataType: 'jsonp',
jsonp: 'cb',
jsonpCallback: 'jsonpCallback',
success: function () {

}
})
.then(function () {
// todo
})
.catch(function () {
// todo
})

响应式布局

实现不同屏幕分辨率的终端上浏览网页的不同展示方式,即通过检测视口分辨率,针对不同客户端在客户端做代码处理,来展现不同的布局和内容。常用的是三栏布局等

webpack打包output中filename:[name][chunkhash].js里chunkhash的用处

具体来说webpack是根据入口entry配置文件来分析其依赖项并由此来构建该entry的chunk,并生成对应的hash值。不同的chunk会有不同的hash值。一般在项目中把公共的依赖库和程序入口文件隔离并进行单独打包构建,用chunkhash来生成hash值,只要依赖公共库不变,那么其对应的chunkhash就不会变,从而达到缓存的目的。

map和foreach的区别,好处和优点

  • forEach():针对每个元素执行提供的函数

  • map():创建新的数组,其中每个元素由调用a中每个元素执行提供的函数而来

区别:

  1. forEach会修改原来数组,map得到一个新的数组并返回

  2. forEach执行速度比map慢

  3. forEach适用于不改变数据,仅利用数据时使用,map适用于需要改变数值时使用

防抖和节流分别在什么情况下使用

  1. 防抖:N秒后再执行该事件,若n秒内重复触发,则重新计时。

    • 搜索框搜索输入,用户最后一次输入完再发送请求

    • 手机号/邮箱输入检测

    • 窗口大小resize。窗口调整完在计算窗口大小,防止重复渲染

  2. 节流:N秒内值运行一次,若n秒内重复触发,则只有一次生效

    • 滚动加载,加载更多等
    • 搜索框,搜索联想功能

项目登陆如何实现

  1. 写登陆页面和登陆的路由跳转

  2. 写登陆的请求接口

  3. 使用axios向后端登陆接口发送登陆请求

  4. 跨域在java后端使用cors标签头处理

  5. 后端校验登陆返回一个token或者sessionID

  6. 使用vuex保存登陆状态token到浏览器的sessionStorage,并根据返回内容跳转URL路由

  7. 退出时,清空sessionStorage中的token信息,跳转页面

单点登陆

  1. 用户在认证中心登陆

  2. 登陆成功,认证中心记录用户登陆状态,将token写入认证中心的cookie

  3. 应用系统检查请求中有没有token,没有,则表示没有登陆,页面带着认证中心的cookie跳转至认证中心。

  4. 认证中心通过cookie了解用户是否已经登陆。

  5. 若没有登陆,则返回登陆页面等待登陆。若登陆过了,则生成一个token,拼接在目标url后,回传给系统

  6. 系统拿到token,想认证中心确认token的合法性,防止伪造token。

  7. 确认无误,系统记录用户登陆状态,将token写入当前系统的cookie,进入系统。

  8. 后续用户再次访问系统,会自动带上此token,系统验证token发现已登陆则直接放行。

基本类型和引用类型区别

  1. 基本类型是存储在栈内的简单数据段,其值存在变量访问的位置。故赋值/访问/函数传参都是按值传递。复制时,复制原始值的副本给新变量,从此而至为两个独立的变量,传参同

  2. 引用类型是存储在堆中的对象,其指针存在变量访问的位置。故赋值/访问/函数传参都是按地址传递。复制时,复制原始值的内存地址给新变量,故新变量改变,原始值也会改变,传参亦同。

script的defer和async的区别

  1. script没有deferasync属性,浏览器立即加载并执行相应脚本,此时会阻塞渲染script标签之后的文档

  2. script有async属性,表示后续文档加载和渲染与js脚本加载执行是并行的。

  3. script有defer属性,表示加载后续文档的过程和js脚本加载是并行的,但js脚本的执行要等到文档所有元素解析完成后,DOMContentLoaded事件触发执行之前。

  4. 若有多个defer属性脚本,则他们按照加载顺序执行脚本,若有多个async属性脚本,则他们的加载和执行是紧挨着的,无论声明顺序如何,只要加载完就立刻执行

for in和for of的区别

  1. for in是es5的标准,遍历的是可遍历对象/数组/字符串的key,for of是es6的标准,遍历的是可遍历对象/数组/字符串的value

  2. 代码for(let index in arr)index为字符串型数字,不能进行集合运算。且遍历顺序不一定按照实际数组的内部顺序,for-in也会遍历数组所有可美剧属性,包括原型。若原型方法被遍历出来,通常需要配合hasOwnProperty()方法来判断某个属性是是否是对象的实例属性,来删除原型对象

  3. for-of避开for in的缺陷,且不会遍历到原型的method和name

  4. for-in更适合遍历对象,不建议遍历数组。for-of适合遍历数组。

  5. for-of可用于遍历Map和Set

算法求数组第二大的数

  1. 先排序再输出
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
function sort(arr){
//快排
// let mid = Math.floor(arr.length / 2);
// let middle = arr.splice(mid,1)[0];
// let left = [],right = [];
// for(let i = 0; i< arr.length-1; i++){
// if(arr[i] > middle)
// right.push(arr[i]);
// else
// left.push(arr[i]);

// }
// return sort(left).concat([mid],sort(right));

//冒泡
for(let i = 0;i<arr.length - 1;i++){
for(let j = 0;j<arr.length - 1 - i;j++){
if(arr[j+1] <arr[j])
[arr[j],arr[j+1]] = [arr[j+1],arr[j]];
}
}
}
function findSecondMax(arr){
sort(arr);
console.log(arr);
console.log(arr[arr.length - 2]);
}
findSecondMax([3,1,2]);
  1. 将第一大和第二大的数,放在数组0和1的位置上,若当前数属于第一大,则使用unshift插入到第一位,并删除当前数。若当前数属于第二大,将此数赋值到a[1]。再删除当前数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
findSecondMath(arr){
for(let i = 0;i<arr.length;i++){
if(i === 1 && arr[i] > arr[0]){
arr.unshift(arr[i]);
arr.splice(i+1,1)
}else if(arr[i] < arr[0] && arr[i] > arr[1]){
//属于第二大
arr[1] = arr[i];
arr.splice(i,1);
i--;
}else if(arr[i] > arr[0] && arr[i] > arr[1]){
arr.unshift(arr[i]);
arr.splice(i+1,1);
}
}
return arr[1];
}

算法:括号匹配

利用栈和map匹配

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
function fn(str){
let stack = [];
let valid = {
'(' : ')',
'[' : ']',
'{' : '}',
}
for(let ch of str){
if(["(","{","["].includes(ch)){
stack.push(ch);
}else{
let left = stack.pop();
let right = valid[left];
if(right !== ch){
console.log(false);
break;
}
}
}
if(stack.length === 0)
console.log(true);
}
fn("([([))]")
  • 版权声明: 本博客所有文章除特别声明外,均采用 Apache License 2.0 许可协议。转载请注明出处!
  • © 2020-2024 Aweso Lynn
  • PV: UV: