- 前端每日知识点分享(总结版)
- 2020年4月20日(typeof能判断出哪些数据类型?)
- 2020年4月21日 (CSS 中盒模型有几种,有什么不同?)
- 2020年4月22日 (new 操作符)
- 2020年4月23日(值类型和引用类型的区别)
- 2020 年4月24日( < !DOCTYPE >的作用)
- 2020年4月25日(语义化标签的理解)
- 2020年4月26日(new 运算符的底层代码实现)
- 2020年4月27日(CSS 对于选择器的解析顺序)
- 2020 年4月28日(CSS 中清除浮动常用的两种方法)
- 2020年4月29日(JS 任务队列)
- 2020年4月30日(GET 和 POST 的区别)
- 2020 年5月1日(HTTP 中常用的一些状态码)
- 2020年5月2日(call,apply,bind 之间的区别)
- 2020年5月3日(比较两个对象的内容是否相等)
- 2020年5月4日(字节笔试分享)
- 2020年5月5日(JS 中的键盘事件)
- 2020年5月6日(简单理解进程和线程)
- 2020年5月7日(学而思网校面试题分享一)
- 2020年5月8日(学而思网校面试题分享二)
- 2020年5月9日(箭头函数和普通函数的区别)
- 2020年5月10日(手写 promise.all 方法)
- 2020年5月10日(手写 Promise.race 方法)
- 2020年5月12日(CSS 中 src 和 href 的区别)
- 2020年5月13日(闭包中的引用基本数据类型的变量存放在栈中还是堆中)
- 2020年5月14日(TCP 和 UDP 的区别)
- 2020年5月15日(变量提升)
- 2020年5月16日(包装对象)
- 2010年5月17日(0.1 + 0.2 真的等于 0.3 吗 )
- 2020年5月18日(CSS 中 opacity,transparent,rgba 设置透明时的区别)
- 2020年5月19日(CSS 中常见的尺寸单位)
- 2020年5月20日(inline 元素设置 padding 之后会生效吗)
- 2020年5月21日(OSI 七层模型)
- 2020年5月22日(解析 url 中的参数)
- 2020年5月23日(原码,反码,补码)
- 2020年5月24日(两个不规则的容器,一个 9 L,一个 4 L,怎么得到 6 L 水)
- 2020年5月25日(数组乱序)
- 2020年5月26日(关于 Node 中的 res.writeHead, res.setHeader, res.write 和 res.end)
- 2020年5月27日(利用 CSS 画三角形)
- 2020年5月28日(如何实现当设置对象的一个属性时另一个属性自动改变)
- 2020年5月29日(如何判断一个属性是在当前实例中还是原型中)
- 2020年5月30日(关于 Object.keys() 和 Object.getOwnPropertyNames())
- 2020年5月31日(获取元素的所有子节点)
- 2020年6月1日(浏览器中对于空格的处理)
- 2020年6月2日(利用 CSS 画 0.5px 宽度的线)
- 2020年6月3日(ES6 中 Symbol 的几个注意点)
- 2020年6月4日(手写 reduce 方法)
- 2020年6月5日(逻辑题:找出开关对应的灯)
- 2020年6月6日(逻辑题:问路)
- 2020年6月7日(逻辑题:药丸称重)
- 2020年6月8日(关于运算符的执行顺序)
- 2020年6月9日(let 不能声明已经声明过的变量)
- 2020年6月10日(防抖)
- 2020年6月11日(节流)
- 2020年6月12日(正则中常用的重复字符)
- 2020年6月13日(正则中常见的元字符)
- 2020年6月14日(正则中与位置相关的锚字符)
- 2020年6月15日(字符串中可以使用正则的四种方法介绍)
- 2020年6月16日(正则表达式方法)
- 2020年6月17日(数组扁平化方法一)
- 2020年6月18日(数组扁平化方法二)
- 2020年6月19日(数组扁平化方法三)
- 2020年6月20日(数组去重方法一)
- 2020年6月21日(数组去重方法二)
- 2020年6月22日(数组去重方法三)
- 2020年6月23日(数组去重方法四)
- 2020年6月24日(冒泡排序)
- 2020年6月25日(选择排序)
- 2020年6月26日(插入排序)
- 2020年6月28日(归并排序)
- 2020年6月28日(快速排序)
- 2020年6月29日(堆排序)
前端每日知识点分享(总结版)
2020年4月20日(typeof能判断出哪些数据类型?)
typeof能判断出哪些数据类型?
typeof能直接判断出 Number,String,Boolean,undefined,Symbol
typeof(null)返回 object,判断数组和对象返回 object,判断函数返回 function
2020年4月21日 (CSS 中盒模型有几种,有什么不同?)
CSS 中盒模型有几种,有什么不同?
CSS 盒模型分为 W3C 标准盒模型和 IE 模型,W3C 标准盒模型中有元素的宽度 width 还有 border,padding, margin,其中 content 的高度和宽度不包含 padding 和 border。
IE 盒模型中也有这几种属性,但是 content 包含 padding 和 margin。
2020年4月22日 (new 操作符)
使用 new 创建对象时,new 运算符做了哪些事?
- 创建一个新对象
- 将构造函数的作用域赋给新对象(this 就指向了这个新对象)
- 执行构造函数中的代码(初始化对象)
- 返回新对象
2020年4月23日(值类型和引用类型的区别)
值类型和引用类型的区别?
值类型是不可变的数据类型,例如 let a = 10;
在内存中的执行过程是先创建一块内存空间存储 10,然后再令 a 指向这块内存空间。如果这时候令 a=20;
会在内存中再开辟一段内存空间里面存放 20,然后将 a 指向这段内存空间,之前的那个内存空间会被销毁。所以值类型是不可变的,要想改变必须将之前的销毁然后创建新的内存空间。
引用类型是可变的数据类型,例如 obj = { x: 1}
在内存中 obj 中存放的是 { x: 1 }
这个对象的地址,这个对象是在堆中存储的,通过 obj.x=3
改变对象的属性,这个时候并不会将之前的对象销毁,因此引用类型是可变的数据类型。
2020 年4月24日( < !DOCTYPE >的作用)
注意 <!DOCTYPE>
并不是 HTML 标签。
总结来说有这两个作用:
1)声明文档的类型,
2)告诉浏览器应该以什么样的标准解析这个文档
浏览器有两个模式,怪异模式和标准模式,声明 <!DOCTYPE html>
作用就是告诉浏览器你即将要处理的是 HTML 文档,并且在渲染文档时要按照标准模式的方式。
MDN 解释
在HTML中,文档类型声明是必要的。所有的文档的头部,你都将会看到"<!DOCTYPE html>
" 的身影。这个声明的目的是防止浏览器在渲染文档时,切换到我们称为“怪异模式(兼容模式)”的渲染模式。“<!DOCTYPE html>
" 确保浏览器按照最佳的相关规范进行渲染,而不是使用一个不符合规范的渲染模式。
2020年4月25日(语义化标签的理解)
语义化标签的理解
按照字面意思理解就是有语义的标签,要做到用最合适的标签做最合适的事,例如要做一个导航栏,要用 Nav 而不是 div。
使用语义化标签有很多好处:
开发者看到文档后能够做到见名知义,有利于团队维护和开发。
能让页面呈现清晰的结构,机器更容易理解,有利于爬虫和搜索引擎的抓取(SEO的优化)
CSS 选择器优先级
!importtant 优先级最高 > 内联样式 > ID 选择器 > 类,属性,伪类 > 元素,伪元素
注意点:不会进位,一万个类选择器也抵不上一个id选择器,相同权重,后写的生效。
2020年4月26日(new 运算符的底层代码实现)
new 运算符的底层代码实现
// func 为传入的构造函数
const new2 = fucntion (func) {
const o = Object.create(func.prototype);
const k = func.call(o);
return k === 'object' ? k : o
}
上面这种写法没有考虑带参数的情况。
2020年4月27日(CSS 对于选择器的解析顺序)
今天的每日一题和大家分享一下 CSS 对于选择器的解析顺序。
以一段代码为例:
body div .hello {
color: #ccc;
}
按照我们正常的思维是浏览器会选取寻找 body,然后再去找 body 下的 div,然后再去找 div 下的 .hello 类。但是浏览器与这个过程正好相反,它会先去找到 .hello 这个类,然后再去验证它有一个父元素是 div,然后 div 再验证它是否有一个父元素是 body。
之所以按照这样的流程是出于性能的考虑,如果是第一种方式,找 body 下的 div 时有可能有特别多的 div,然后再在这些 div 中寻找 .hello 类时是非常耗时的。
2020 年4月28日(CSS 中清除浮动常用的两种方法)
今天和大家分享一下 CSS 中清除浮动常用的两种方法,学累了就来看看吧。
CSS 清除浮动
-
父元素触发 BFC(例如可以采用 overflow:hidden 使父元素触发 BFC)
-
利用伪元素,给父元素的最后面添加一个伪元素,令伪元素 clear: both,这样就会使得左右两边都没有浮动元素,又因为它是父元素的最下面的一个元素,所以它只能在父元素的最下面并且左右都没有浮动元素,这样浮动元素就在它的上面,父元素就不会发生告诉塌陷了。
代码示例:
父元素::after { content: ''; clear: both; /* 使左右两边没有浮动元素 */ display: block; visibility: hidden; height: 0; }
2020年4月29日(JS 任务队列)
今天和大家来一起学习一下 JavaScript 事件循环机制中的任务队列。
任务队列分为两种,一种是micro task,另一种是 macro-task。
macro-task大概包括:script(整体代码), setTimeout, setInterval, setImmediate, I/O, UI rendering。
micro-task大概包括: process.nextTick, Promise, Object.observe(已废弃), MutationObserver(html5新特性)
优先级: micro-task > macro-task;
推荐给大家一个视频,生动形象。 两分钟了解 JavaScript EventLoop https://www.bilibili.com/video/BV1kf4y1U7Ln
2020年4月30日(GET 和 POST 的区别)
今天和大家一起来学习一下面试中经常问的 GET 和 POST 的区别,主要有以下几点。
1)GET 在浏览器回退时是不会再次发送 GET 请求的,POST 会再次发送请求
2)GET 请求会被浏览器主动缓存,而 POST 不会,除非手动设置(在地址栏中输入url的方式只能发送 GET 请求)
3)GET 请求的参数会被完整保留在浏览器历史记录里,而 POST 请求中的参数不会被保留
4)GET 请求在 URL 中传送的参数是有长度限制的,而 POST 没有限制
5)GET 参数通过 URL 传递,POST 数据放在 Request body 中
6)GET 比 POST 安全性低,因为参数直接暴露在 URL 上,所以不能用来传递敏感信息
2020 年5月1日(HTTP 中常用的一些状态码)
今天和大家一起来学习一下 HTTP 中常用的一些状态码。
200 OK 请求成功
206 Partial Content 客户端发送了一个带有 Range 头的 GET 请求,服务端成功响应了该请求。在客户端请求一个视频文件的时候,服务端一般都会返回一个 206,表示将部分资源返回给客户端。
301 Moved Permanently 所请求的资源已被永久地移到别的地方。服务端需要在响应的首部添加一个 Location 属性来表示资源已经被移动到哪个 url 。(Location属性是可选的,但是推荐有)
302 Found 所请求的资源被临时移动到别的地方。服务端需要在响应的首部添加 Location 属性来临时定位那个资源。将来还是应该用老的 url 进行访问。(Location属性是可选的,但是推荐有)
304 Not Modified 请求的资源在本地有并且和服务端上的资源一致并没有被修改。
400 Bad Request 告知客户端发送了一个错误的请求,服务端不能理解
401 Unauthorized 请求未经授权,这个状态码必须和 WWW.Authenticate 报头域一起使用 403 Forbidden 请求的资源被禁止访问。
404 Not Found 请求的资源不存在
500 Internal Server Error 服务器发生不可预期的错误原来的缓冲文档还可以继续使用
503 Server Unavailable 服务器现在无法为请求提供服务,将来可能可以。如果服务器知道将来什么时候可以提供服务,可以在响应的首部添加一个 Retry-After 属性告诉客户端什么时候可以再次提供服务。
2020年5月2日(call,apply,bind 之间的区别)
今天和大家聊一下 call,apply,bind 之间的区别?
先来看一下相同点:
它们三个都是改变函数运行时内部 this 的指向,第一个参数指定的对象就是函数运行时 this 指向的对象。
再来看一下不同点:
call 的第二个参数是参数列表的形式,调用 call 的函数会立即执行。
fn1.call(obj, 1, 2, 3)
apply 的第二个参数是一个数组,调用 apply 的函数也会立即执行。
fn1.apply(obj, [1, 2, 3])
bind 的第二个参数是参数列表的形式,调用 bind 的函数会返回一个函数,而不是立即执行。
fn1 = fn1.bind(obj, 1, 2, 3) // 将 fn1 内部的 this 指向 obj,并且返回绑定后的函数
fn1()
2020年5月3日(比较两个对象的内容是否相等)
由于自己做的一个键盘导航组件中需要用到比较两个对象的内容是否相等,因此根据深拷贝的思想写了一个函数分享给大家,大家可以顺便复习一下深拷贝。(b 站不支持 MarkDown太不方便了,直接粘贴成图片了,代码可以在直播页面上的 GIthub 仓库中获取)
2020年5月4日(字节笔试分享)
今天做了字节的笔试,客观题只有五道选择题,一个问答题,还有三道编程题。选择题考查的外边距重叠,事件循环机制(场景题让你写输出),object.keys 的返回值(只是一个选项),window.postMessage() 跨域,JSONP 跨域只能发送 GET 请求等。
问答题是给你一段代码,让你找出其中的错误,并修正,然后针对这个问题让你写出尽可能多的方案。刚开始没有 Get 到考察的点,后来意识到考察的是 this 并不会遵循作用域链的规则,因此需要 const that = this,然后在子函数中利用 that 才能访问到父函数中的 this。
关于尽可能多的方案,题目中使用两个 class 来实现的,我使用借用构造函数,借助原型链,组合模式这几种都实现了一下。
关于编程题,难度 LeetCode 中等难度上下,平时一定要多刷点题,还有在在线下多练习一下各种形式的输入读取和各种形式的输出,到时候可以把精力都放在算法上。
2020年5月5日(JS 中的键盘事件)
今天和大家分享一下 JS 中的键盘事件如下:
keydown: 当用户按下任意键
时触发,如果按住不放会重复触发此事件。
keypress: 当用户按下 字符键
时触发,如果按住不放会重复触发此事件。
keyup: 当用户释放按键时触发。
和键盘息息相关的还有一个 textInput 事件
textInput: 在文本插入文本框之前触发,发生在 keydown 和 keypress 之前,keyup 之后。
当用户按 字符键 时,事件触发顺序 keydown -> keypress -> textInput -> keyup
当用户按 非字符键 时,事件触发顺序 keydown -> textInput -> keyup
小伙伴们不想学,感觉效率很低的时候,建议不要硬学了,可以去疯狂地玩一次,这样你就会有很深的愧疚感,然后再回来学。
2020年5月6日(简单理解进程和线程)
今天和大家聊一下操作系统中的进程和线程。
进程是资源分配的基本单位,线程是调度的基本单位。
进程包含线程,线程共用进程的资源。
为了形象化理解进程和线程,可以将进程比作工厂,工厂中的工人比作线程。
一个工厂中可以有多个工人,也就是一个进程中可以有多个线程。
多个工人共用工厂的资源,也就是线程共用进程中的资源。
各个工厂之间是是相互独立的,也就是进程之间是相互独立的。进程之间实际上是可以通信的,但是会比较麻烦,例如状态同步等问题。
明天的每日一题中和大家聊一下我们前端比较关心的 chrome 浏览器中的进程。
b 站动态只能发 400 个字,有时真是很无奈。
2020年5月7日(学而思网校面试题分享一)
1)输入一个 url 到渲染完成,整个过程(提示要说 https),介绍一下https, 它是如何实现加密的
2)CSS 盒模型,清除浮动,重排和重绘,如何减少重排,什么改动会造成重排
3)BFC,如何触发 BFC
4)实现水平和垂直居中(文字形式的和div形式的),利用 flex 布局如何实现
5)JS 继承(红宝书中像寄生继承等方式还需要去看)
6)深拷贝和浅拷贝的区别,手撕深拷贝(现在写的代码中没有考虑到时间等对象,还有在遍历时要用 for...in 而不是 for..of )
7)基本数据类型有哪些,基本数据类型和引用数据类型有什么区别,在内存中如何存储的
8)浏览器中缓存分类,强缓存和协商缓存,分别说一下,cookie 和 session 的区别
9)快排,手撕
10)React 生命周期
11)Redux 介绍一下工作流程,源码看过吗
12)如何在 Redux 的工作流程中发送一个异步请求(Redux-Thunk中间件)
13)Vue 父子组件之间的通信
2020年5月8日(学而思网校面试题分享二)
14)Redux 和 Vuex 之间的区别
15)常用的 git 命令,当我说到 git rebase 的时候马上接着问 git rebase 和 git merge 有什么区别
16)回退到某个版本如何实现
17)webpack 中如何加入一个全局变量
18)webpack 如何配置实现懒加载
19)Node 中的 EventLoop
20)如何使用 Node 创建创建一个服务器
21)ES6 中有哪些新特性,用过哪些
22)箭头函数和普通函数的区别
23)React 中 Hooks 如何使用
24)HTTP 状态码(提到 304 的时候马上提问有关浏览器缓存的问题)
25)Promise 是为了解决什么问题
26)数组的遍历方法,some 和 filter 如何实现,reduce 如何使用,如果让你实现 reduce,你会如何实现?
27)自己学的比较深的地方
28)还有没有什么想问的?
2020年5月9日(箭头函数和普通函数的区别)
今天和大家一起来讨论一下箭头函数和普通函数的区别,最近几次面试几乎都会被问到。
1)箭头函数中的 this 指向的是定义时所在的环境,普通函数中的 this 指向的时运行时所在的环境
2)箭头函数中不能使用 arguments
3)箭头函数不能当做构造函数,创建实例
4)箭头函数不能用作 Generator 函数,因为内部不可以使用 yield 命令。
字节面试题分享:
1)自我介绍
2)手写实现 Promise.all 方法
3)算法题,n 级台阶一次只能走一步或者两步,请问有多少种方案
4)TCP 三次握手,四次挥手
5)BFC
6)Generator
7)原型
8)webpack 的实现原理
7)还有没有什么想问的
2020年5月10日(手写 promise.all 方法)
今天和大家分享字节跳动面试中的一道面试题,手写实现 promise.all 方法,关于 promise.all 就不再介绍了,不清楚的小伙伴自行百度吧。
大致思路就是遍历用户传递进来的 promise 数组,然后执行 .then 方法,每执行一次,count 加 1,并将执行结果 push 到 result 数组中,当 count 等于传入的 promise 数组的长度时,说明所有的 promise 都已经成功执行完了,此时将所有 promise 的执行结果 result, resolve 出去即可。
function promiseAll (arr) {
if (!isArray(arr)) {
throw new TypeError('You must pass array')
}
let result = []
let count = 0
arr.map((item, i) => {
item.then((res) => {
result[i] = res
count++
if (count === arr.length) {
resolve(result)
}
}).catch((e) => {
reject(new Error('fail'))
})
})
}
2020年5月10日(手写 Promise.race 方法)
有点发烧去诊所看了一下,小伙伴们多注意身体。今天和大家分享一下 promise.race 方法的实现,这个实现实现起来比 promise.all 稍微简单一些,只要其中有一个成功,那么就执行 resolve。
function promiseRace (arr) {
if (!Array.isArray(arr)) {
console.log('You should pass an array')
}
return new Promise ((resolve, reject) => {
arr.forEach((item, i) => {
item.then(res => {
resolve(res)
}).catch(err => {
reject(err)
})
})
})
}
今天晚上早点睡,大家晚安,多注意身体。
2020年5月12日(CSS 中 src 和 href 的区别)
今天首先和大家分享一下今天下午阿里的面试题,刚开始二话不说就是两道编程题,1. 比较两个对象的内容相等(条件很多)2. 找出一个字符串中重复子串的最大长度。然后又让我自我介绍了一下,之后问我如何学习前端的,然后又问了几个 React 的问题,例如虚拟 DOM,diff 算法,在设计一个组件的时候需要注意什么,React 和之前的 jQuery 等库有什么区别等问题。
然后今天和大家分享一下 CSS 中容易混淆的 src 和 href,
href(hypertext reference)超文本引用
src(source)资源
link
和 a
标签使用 href 属性。
img, style, script, input, iframe
标签使用 src 属性。
接下来用 img 和 a 标签来演示两者的区别:
<img src="./1.png" alt=""/>
<a href="./1.png">点我加载图片</a>
当我们打开页面之后,发现只有一张图片(img 加载出来的),a 标签的图片需要我们点击点我加载图片
才能加载出来。
因此可以看出来,href 指向的资源并不会立即加载出来显示在页面上,它是一个链接,当我们进行某项操作之后(例如 a 标签的点击)它才会去指向的链接加载资源。
src 指向的资源会被立即加载出来。
2020年5月13日(闭包中的引用基本数据类型的变量存放在栈中还是堆中)
今天和大家分享一下闭包引用的变量是保存在哪里。不是闭包中引用的变量,我们知道基本数据类型的的值是存在 栈中的,引用数据类型的地址是存在栈中,数据是存在堆中的。
但是闭包中的基本数据的变量是存在堆中还是栈中呢?
function f1 () {
let a = 1
return function () {
console.log(++a)
}
}
const f = f1()
f() // 2
f() // 3
现在 console.dir(f) 可以看到输出为:
伴随着 f1 的调用,为了保证变量不被销毁(闭包中引用的变量会一直保存在内存中,这是闭包的特点),在堆中有一个 [[scope]] 对象,把变量 a 作为 Scope 的属性给存起来,因此变量 a 并不是保存在栈中的。
详细解释可参考这篇博客: https://blog.csdn.net/weixin_40013817/article/details/103287271
2020年5月14日(TCP 和 UDP 的区别)
日程就先不展示在直播页面上了,有面试了就和大家说一下,明天下午有一个滴滴面试,下周一字节二面。
今天和大家分享一下 UDP 和 TCP 的区别,概括起来主要有以下几点:
TCP 是面向连接的,UDP 是面向无连接, 面向连接,是指发送数据之前必须在两端建立连接,TCP 建立连接的方法是采用 “三次握手” 的方式完成的,UDP 是面向无连接的,想发送数据就发了,不需要先建立链接,想发送数据了应用层将数据传递给传输层的 UDP 协议,UDP 给数据增加一个 UDP 头标识,然后就传递给网络层开始发送了。
UDP 可以进行单播,多播(多对多),广播(一对多),因为 TCP 是面向连接的所以只支持一对一的单播通信。
UDP 是不可靠的,因为它没有三次握手的过程,想发送数据就发送数据而不管通信链路的情况。TCP 是可靠的,因为它会通过三次握手确认通信链路可用才会进行发送。
UDP 没有拥塞控制,不能根据当前链路的情况来调整发送的速率等。TCP 具有拥塞控制,可以根据链路的情况来调整发送的速率和流量等。
2020年5月15日(变量提升)
今天和大家分享一个滴滴面试时出的一个面试题,代码如下:
function sayName() {
console.log(num1)
console.log(num2)
var num1 = 'zhangsan'
let num2 = 'lisi'
}
sayName()
上面代码的执行结果是什么呢?
答案: 第一个 console.log 输出 undefied,第二个 console.log 报错,会提示 num2 并没有被定义。
这是因为用 var 声明的变量会进行变量提升,但是并不会赋值,也就是说只会声明变量 num1,但是里面的值要等到执行完 var num1 = 'zhangsan' 后才有,所以会输出 undefined。
let 声明的变量并不会进行变量提升,在第二个 console.log 中使用 num2 时,num2 还没有被定义,因此会报错。
2020年5月16日(包装对象)
今天再和大家分享一个关于包装对象的面试题。代码如下:
var str = 'codingOrange'
var str1 = String(str)
var str2 = new String(str)
console.log(typeof str1)
console.log(typeof str2)
console.log(str === str1)
console.log(str === str2)
答案依次是: string, object, true, false
之前我们经常使用字符串的方法,思考过明明是基本类型,可还是能够调用方法的原因吗?
答案就是包装对象,str 在调用 split 方法的时候实际上 JS 已经创建了一个包装对象,调用的 split 方法是这个包装对象上的。
var str = 'codingOrange'
var arr = str.split('')
调用 split 方法时,JS 引擎帮我们做的事
var str_obj = new String('condingOrange')
var arr = str_obj.split('')
str_obj = null
可以看到生成的包装对象在调用完 split 方法之后会被立即销毁,因此
var str = 'condingOrange'
str.addr = 'shandong'
console.log(str.addr) // undefined
因为在生成的包装对象立即被销毁了,所以也获取不到 addr 属性了
2010年5月17日(0.1 + 0.2 真的等于 0.3 吗 )
今天和大家分享一个比较有趣的问题,在 JS 中 0.1 + 0.2 并不等于 0.3,可以在浏览器中试一下。
这是因为 JS 采用 IEEE 754 双精度版本(64位),并且只要采用 IEEE 754 的语言都有该问题。
0.1
在二进制表示为 0.1 = 2^-4 * 1.10011(0011),小数算二进制和整数不同。乘法计算时,只计算小数位,整数位用作每一位的二进制,并且得到的第一位为最高位。所以我们得出 0.1 = 2^-4 * 1.10011(0011)
,那么 0.2
的演算也基本如上所示,只需要去掉第一步乘法,所以得出 0.2 = 2^-3 * 1.10011(0011)
。
回来继续说 IEEE 754 双精度。六十四位中符号位占一位,整数位占十一位,其余五十二位都为小数位。因为 0.1
和 0.2
都是无限循环的二进制了,所以在小数位末尾处需要判断是否进位(就和十进制的四舍五入一样)。
所以 2^-4 * 1.10011...001
进位后就变成了 2^-4 * 1.10011(0011 * 12次)010
。那么把这两个二进制加起来会得出 2^-2 * 1.0011(0011 * 11次)0100
, 这个值算成十进制就是 0.30000000000000004
解决方法:
parseFloat((0.1 + 0.2).toFixed(10))
或者在计算浮点时先乘以一个 100000 等比较大的数,在计算完成之后再除以这个数。
2020年5月18日(CSS 中 opacity,transparent,rgba 设置透明时的区别)
我们知道在 CSS 中,opacity,transparent,rgba 这三个都可以实现透明的效果,但是用法上各不相同。
1、opacity用来设置元素的不透明级别,从 0.0 (完全透明)到 1.0(完全不透明)。
2、transparent是颜色的一种,这种颜色叫透明色。
3、rgba(r,g,b,a)
r:红色值;g:绿色值;b:蓝色值。三个颜色值组合在一起就形成最终颜色。
a:alpha透明度。表示像素不透明性的值。像素越不透明,则隐藏越多呈现图像的背景。取值0~1之间,0表示完全透明的像素,1表示完全不透明的像素。
2020年5月19日(CSS 中常见的尺寸单位)
今天和大家分享一下 CSS 中常见的尺寸单位。
绝对单位:
px pixel 像素
相对单位
% 百分比(相对于父元素,例如设置子元素的宽度为 100%,那么子元素的宽度和父元素的宽度一样)
em element meter 根据文档字体计算尺寸
rem root element meter 根据 html 元素字体计算尺寸
ex 文档字符 “x” 的高度
ch 文档字符 “0” 的宽度
vh view height 可视范围高度除以 100
vw view width 可视范围宽度除以 100
vmin view min 可视范围的宽度和高度中较小的那个尺寸
vmax view max 可视范围的宽度和高度中较大的那个尺寸
关于 em 和 rem 的区别,推荐一个之前看过的感觉挺好的视频: https://www.bilibili.com/video/BV1P7411C7EP
2020年5月20日(inline 元素设置 padding 之后会生效吗)
今天和大家分享一个字节二面时被问到的问题,那就是 inline 元素在设置 padding 后会生效吗?
你心里有没有一个非常确定的答案呢?
答案就不说了,小伙伴们自行思考一下吧。
2020年5月21日(OSI 七层模型)
今天开始先和大家说一下昨天问题的答案,首先非常感谢 二次元的永痕 同学提出的看法,他的一些看法是我之前忽略掉的,最终比较合理的解释是: inline 元素设置 padding(无论哪个方向) 时对其本身元素的尺寸是有影响的。但是 padding-top 和 padding-bottom 设置的值,在布局时对它上面和下面的元素是没有影响的。
这段代码就能体现出来上面说的内容。
然后再来说一下今天的知识点:OSI 的七层模型从下到上以此是:
物理层,数据链路层,网络层,传输层,会话层,表示层,应用层
希望小伙伴们发现问题之后,及时纠正,看我的人大多都是前端初学者,咱们看待问题的角度可能比那些工作多年的前辈不太一样,咱们之前比较容易沟通一些,所以希望大家多多指正,共同进步。
2020年5月22日(解析 url 中的参数)
今天和大家分享一些如何通过 JS 解析 url 中的参数,介绍两种方法:
url 参数的形式 ?a=1&b=2
方法一:
先通过 substr 方法去掉开头的 ?
,然后利用 split 方法分成 [a=1, b=2]
然后再通过遍历和 split 方法就可以获取到 a 和对应的值 1,b 和对应的值 2,然后再将其存入数组中即可。这样就可以通过 paraArr.a
就可以获取到它的值 1.
function queryUrlSearch () {
let searArr = {}
let key,value
const arr = location.search.substr(1).split('&')
arr.forEach(item => {
key = item.split('=')[0]
value = item.split('=')[1]
searArr[key] = value
})
return searArr
}
// 测试代码
const res = queryUrlSearch()
console.log(res.a)
方法二:
使用 HTML5 中新提供的 API,URLSerarchParams,接收的参数是 location.search,返回结果后可以通过 get 方法获取到对应参数的值。
const res = new URLSearchParams(location.search)
console.log(res.get('a')) // 输出 1
建议亲自敲一遍哦。
2020年5月23日(原码,反码,补码)
今天面试美团的时候,问了原码,反码和补码,然后又问了在 C 语言中一个 int 类型的变量在内存中是如何存储的,举个例子吧,例如数字 1,
// C 语言中,int 类型的变量占四个字节(32位)
// 原码
0000 0000 0000 0000 0000 0000 0000 0001
// 反码(所有的位取反)
1111 1111 1111 1111 1111 1111 1111 1110
// 补码(反码加1)
1111 1111 1111 1111 1111 1111 1111 1111
在内存中是按补码来存储的,所以全是 1.
关于为什么要用补码简单来说就是为了加减统一,具体介绍可参考 为什么要用补码
2020年5月24日(两个不规则的容器,一个 9 L,一个 4 L,怎么得到 6 L 水)
今天和大家分享一道智力题,有两个不规则的容器,一个 9 L,一个 4 L,请问怎么得到 6 L 水?
1)将 9 L 的容器装满
2)倒入 4 L 的容器,倒掉
3)再次倒入 4 L 的容器,倒掉,这个时候 9 L 的容器内还剩 1 L 水
4)将这 1 L 水倒入 4 L 的容器中
5)再将 9 L 的容器装满,倒入 4 L 的容器并装满,这个时候 9 L 容器内就剩下 6 L 水了
2020年5月25日(数组乱序)
今天和大家分享一下如何将数组乱序。思想就是随机将数组中的某些项进行互换,这个随机性是通过 Math.random 方法来是实现的,它可以返回一个 [0, 1)
的小数,注意是左闭右开。
代码实现:
function arrRandomOrder (arr) {
let len = arr.length
let temp
for (let i = len - 1; i >= 0; i--) {
let index = Math.floor(Math.random() * i)
temp = arr[index]
arr[index] = arr[i]
arr[i] = temp
}
}
// 测试代码
const arr = [1, 2, 3, 4, 5, 6,7]
arrRandomOrder(arr)
console.log(arr)
每次运行这段代码,数组的顺序都会不一样。
大家还有什么其它的方法吗?
2020年5月26日(关于 Node 中的 res.writeHead, res.setHeader, res.write 和 res.end)
res.writeHead, res.setHeader
用来在返回的报文中设置 header
res.setHeader('Content-Type', 'text/plain')
res.writeHead(200, {'Content-Type', 'text/plain'})
res.write,res.end
用来在返回的报文中设置 body(实体)
let obj = {
err: 0
data: 1
}
res.write(JSON.stringify(obj))
res.end(JSON.stringify(obj))
res.end 实际上包含两个过程,首先会调用 res.write 发送数据,然后发送信息告诉服务器这次响应结束。
注意报头一定要在实体发送之前发送,当发送完 body 之后,再使用 res.writeHead 和 res.setHeader 发送的 header 将不会起作用。
另外,无论服务器在处理业务逻辑时是否发生异常,务必在结束时调用 res.end 结束请求,否则客户端将一直处于等待状态。当然也可以通过延迟 res.end 的方式实现客户端和服务端的长连接,但结束时务必关闭连接。
2020年5月27日(利用 CSS 画三角形)
今天和大家分享一下如何利用 CSS 画三角形,我们经常设置边框,不知道大家想过没有,两条边框交界处是什么样的?实际上,两条边框的交界处是一条斜线,这样当我们利用 border-left,border-right,boder-bottom 左右各有一条斜线,这样就会构成一个梯形,然后令元素的宽度为零,这个时候上底就变成 0 了,此时就是一个三角形了。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>画一个三角形</title>
<style type="text/css">
.triangle {
0;
border-left: 10px solid transparent;
border-right: 10px solid transparent;
border-bottom: 10px solid red;
}
</style>
</head>
<body>
<div class="triangle"></div>
</body>
</html>
2020年5月28日(如何实现当设置对象的一个属性时另一个属性自动改变)
针对这个问题,可能大家都会想到在设置一个属性的时候再使用这个属性自动改变另一个属性不就可以了吗?例如下面这样:
let book = {
year: 2004,
version: 1
}
// 当设置年份的时候 version 自动发生变化
book.year = 2006
book.version = book.year - 2004
上面这样做可以,但是封装性并不是那么好,还可以使用 Object.defineProperty()
方法,通过设置对象访问器属性中的 set 属性来实现。
let book = {
_year: 2004,
version: 1
}
Object.defineProperty(book, 'year', { // 不能和对象中已有的属性重名,否则会报堆栈溢出错误
get: function () {
return this._year
},
set: function (newValue) {
this._year = newValue
const year = 2004
if (newValue > 2004) {
this.version = this._year - year
}
}
})
book.year = 2006
console.log(book) // {_year: 2006, version: 2}
console.log(book.year) // 2006
console.log(book.version // 2
注意在设置访问器属性时第二个参数的属性名不能和对象中原有的属性重名,否则会报堆栈溢出的错误。上面代码的意思就是当执行 book.year = 2006
的时候就会执行 set 函数,然后修改 book._year
和 book.version
.
当执行 book.year
的时候就会执行 get 函数,然后获取到 this._year
的值。
2020年5月29日(如何判断一个属性是在当前实例中还是原型中)
在说这个之前先说一下 in
和 hasOwnProperty
的使用,使用 in
时只要实例能够访问到的属性都会返回 true,使用 hasOwnProperty
时只有当这个属性在实例上时才会返回 true。
function People (name) {
this.name = name
}
People.prototype.say = () => {
console.log('hello')
}
const zhangsan = new People('zhangsan')
console.log('name' in zhangsan) // true
console.log('say' in zhangsan) // true
console.log(zhangsan.hasOwnProperty('name')) // true
console.log(zhangsan.hasOwnProperty('say')) // false
那么结合这两者就可以确定一个属性是在当前实例还是原型上,如下所示
function isPrototypeProperty (obj, prop) {
return !(obj.hasOwnProperty(prop)) && (prop in obj)
}
// 测试代码
console.log(isPrototypeProperty(zhangsan, 'name')) // false
console.log(isPrototypeProperty(zhangsan, 'say')) // true
2020年5月30日(关于 Object.keys()
和 Object.getOwnPropertyNames()
)
两者都是用来获取实例自身的属性的,并且返回值都是由实例的属性组成的数组,区别在于 Object.keys()
只能获取到实例自身可枚举的属性([[enumerable]] 为 true
),而 Object.getOwnPropertyNames()
能够获取到实例自身所有的可枚举和不可枚举的属性。
let obj = {
a: 1,
b: 2
}
Object.defineProperty(obj, 'a', {
enumerable: false
})
let arr1 = Object.keys(obj)
console.log(arr1) // ['b']
let arr2 = Object.getOwnPropertyNames(obj)
console.log(arr2) // ['a', 'b']
2020年5月31日(获取元素的所有子节点)
例如要获取到 ul 元素下的所有的 li 元素。
<ul>
<li>item1</li>
<li>item2</li>
<li>item3</li>
</ul>
<script>
const ul = document.getElementsByTagName('ul')[0]
const ulChildNodes = ul.childNodes
console.log(ulChildNodes) // NodeList(7) [text, li, text, li, text, li, text]
</script>
这个时候会发现有 7 个子节点,而不是 3 个子节点,这是因为标签与标签之间有空格导致的。
<ul><li>item1</li><li>item2</li><li>item3</li></ul>
<script>
const ul = document.getElementsByTagName('ul')[0]
const ulChildNodes = ul.childNodes
console.log(ulChildNodes) // NodeList(3) [li, li, li]
</script>
改成这种形式之后,发现就只有 3 个子节点了。
因此在遍历子元素时需要判断一下类型,如果 NodeType 为 1 说明是 Element 类型,说明是 li 元素。
// 遍历子元素
// 首先将类数组转换为数组
ulChildNodes = Array.from(ulChildNodes)
ulChildNodes.forEach((item, index) => {
// nodeType === 1 表示是 Element 节点
if (item.nodeType === 1) {
console.log(item)
}
}
会依次输出三个 li 元素。
HTML 5 中新增加了一个 children 属性,可以获取到所有的元素子节点而且不包含其它的节点类型。
<ul>
<li>item1</li>
<li>item2</li>
<li>item3</li>
</ul
<script>
const ul = document.getElementsByTagName('ul')[0]
let ulChildNodes = ul.children
console.log(ulChildNodes) // HTMLCollection(3) [li, li, li]
</script>
只会输出三个 li 元素,而不包含其它的节点类型。
2020年6月1日(浏览器中对于空格的处理)
默认情况下浏览器将会将文字前后的空格忽略,然后文字之间的空格保留一个(即使有多个空格)
普通的空格(按空格键),制表符(tab 键)和回车换行(回车键)都是空格,在浏览器中将这些都当做空格看待。
<div class="inner">
<a> hell
o </a>
</div>
显示结果
可以看到只有文字中间的多个空格显示成了一个空格,文字前后的空格都被浏览器忽略了。
在 CSS 中可以通过 white-space
属性对空格进行处理:
1.white-space: normal
表示浏览器以正常方式处理空格。
2.white-space: nowrap
所有文本显示为一行,不会换行。
3.white-space: pre
所有空格和换行符都保留了
4.white-space: pre-wrap
文首的空格、内部的空格和换行符都保留了,超出容器的地方发生了折行。
5.white-space: pre-line
保留换行符
设置 white-space 为 pre 之后就会保留所有的回车换行和空格等
<style>
.inner a {
white-space: pre
}
</style>
<div class="inner">
<a> hell
o </a>
</div>
2020年6月2日(利用 CSS 画 0.5px 宽度的线)
画 0.5px 宽度的线是面试中经常会被问到的问题,今天和大家一起来学习一下。
比较常用的是通过 transform: scale(0.5);
来实现.
.border1px {
background-color: #000;
height: 1px;
margin-bottom: 30px;
}
.border_transform {
background-color: #000;
height: 1px;
transform: scaleY(0.5);
transform-origin: 50% 100%; // 不写这条语句在 chrome 中会变成模糊的线和正常的线不同
margin-bottom: 30px;
}
<div class="border1px"></div>
<div class="border_transform"></div>
当然还可以通过 svg,设置 initial-scale 等方式,详细介绍可参看这篇文章 怎么画一条0.5px的线。
然后除了线之外,可能还会问你如何画一个 0.5px 的边框,这个一般情况下可以利用一个子元素,令其宽度和高度为 200%,border 为 1px solid #000
, 然后再设置 transform: scale(0.5),父元素为相对定位,子元素为绝对定位,并且设置子元素的 top 和 left 各为 -50% 来实现。
<div class="container">
<div class="inner"></div>
</div>
.container {
position: relative;
height: 100px;
100px;
background-color: pink;
}
.inner {
position: absolute;
top: -50%;
left: -50%;
height: 200px;
200px;
border: 1px solid #000;
transform: scale(0.5);
transform-origin: center;
}
2020年6月3日(ES6 中 Symbol 的几个注意点)
Symbol 出现的原因就在于解决对象的属性容易重名的问题,使用 Symbol 后可以创造出独一无二的值。Symbol 是第六种简单数据类型(null, undefined, number, boolean, string),既然是简单数据类型因此在创建一个 Symbol 类型的值时就不能用 new 运算符。
使用示例:
let sym1 = Symbol()
let sym2 = Symbol('s')
Symbol 可以有一个 string 类型的参数,也可以没有,因为无论有没有它们生成的 Symbol 类型的值都是不相同的。
let sym3 = Symbol()
sym1 === sym3 // false
可以看到两者并不相等,但是为了便于区分每个 Symbol 最好还是带着相应的字符串。
Symbol 类型的值作为对象的属性时,必须用方括号括起来,因为如果不括起来那么就是字符串了。
let obj = {
sym1: 1, // 属性是一个字符串
[sym1]: 2 // 属性是一个 Symbol 类型的值
}
obj.sym1 // 1
obj[sym1] // 2
Symbol 类型的值只可以显式地转换为字符串,不能进行隐式类型转换。
let s1 = `user: ${sym2}` // 报错,因为不能进行隐式类型转换
let s2 = sym2.toString() // 'Symbol(s)' 注意里面的字符串已经没有引号了
Symbol 类型的值可以转换为 Boolean 值,任何 Symbol 值都可以转换为 true。
除此之外,Symbol 的值不能进行任何的类型转换。
2020年6月4日(手写 reduce 方法)
首先说一下 reduce 方法的基本用法,该方法接收一个函数和一个初始值作为参数,这个函数有四个参数,previousValue,currentValue,index 和 array。
传入函数的执行结果会赋值给 previousValue,然后再遍历下一个数组元素,再次执行传入的函数。
reduce 方法在求数组和的时候非常方便。
const arr = [1, 2, 3]
arr.reduce((res, item) => {
return res + item
}, 0)
掌握了基本使用之后吗,面试中会让你手动实现 reduce 方法。
Array.prototype.myReduce = function (fn, initValue) {
const self = this
let res = initValue
self.forEach((item, index) => {
res = fn (res, item, index, self)
})
return res
}
// 测试代码
const arr = [1, 2, 3]
arr.myReduce((res, item) => {
return res + item
}, 0)
2020年6月5日(逻辑题:找出开关对应的灯)
题目
有三个开关屋内有三盏灯,只允许进一次屋,请问如何判断出哪一个开关对应哪一盏灯?
解答
可以先将其中一盏打开两分钟后,关闭,然后打开另外一盏灯,现在进入屋内,可以确定屋内亮的那盏灯和外面打开开关的那个开关对应。然后再去摸一下灯的温度,就可以确定这盏灯对应外面的哪一个开关,这样三个就都确定了。
关于这方面的题目,需要大胆假设,脑洞大开。
2020年6月6日(逻辑题:问路)
问题
现在有两个(东西)路口,路口有两个人,你现在需要去问路,其中一个人说真话,一个人说假话(它们两个人互相知道对方的答案),它们只回答是或者不是,请问怎样在只问一个人一个问题的情况下就能知道哪条路是正确的?
解答
假设现在有东西两个路口,而且假设东边的路口是正确的,如果直接问肯定得不到正确的答案,因为你并不知道谁说真话谁说假话,可以采取反问的形式。
问说真话的人,我选择东边的这个路口,另一个人(假话)会怎样说? ---- 不是
问说真话的认,我选择西边的这个路口,另一个人(假话)会怎样说? --- 是
问说假话的人,我选择东边的这个路口,另一个人(真话)会怎样说? -- 不是(注意说假话的人此时故意将说真话人的答案说反,这一点要注意,说假话的人无论问什么都会说假话)
问说假话的人,我选择西边的这个路口,另一个人(真话)会怎样说? -- 是(注意说假话的人此时故意将说真话人的答案说反,这一点要注意,说假话的人无论问什么都会说假话)
从上面的结果中可以看出来,当那个人回答 不是
的时候,说明答案是正确的,当那个人回答 是
的时候说明那个人回答是错误的。
2020年6月7日(逻辑题:药丸称重)
问题
有20瓶药,其中 19 瓶中每粒药丸的质量为 1.0 克,另外一瓶的质量为 1.1 克,现在有一个电子秤,在只允许称量一次的情况下,请问怎样找出那个每粒药丸 1.1 克的那瓶药?
解答
可以将这 20 瓶药进行编号,并且从第一瓶中取出 1 粒,第二瓶中取出 2 粒,第三瓶中取出 3 粒 ...... 第 20 瓶中取出 20 粒。
假设现在每一粒药丸的质量都为 1.0 克,那么总质量为 210 克,现在假设称出来是 210.1 克,多出来 0.1 克,说明 1.1 克的药在第 1 瓶。如果现在称出来的质量为 211.1 克,多出来 1.1 克,说明 1.1 克的药在第 11 瓶。
2020年6月8日(关于运算符的执行顺序)
首先给大家下面这段代码,对象 a,b 的输出是什么呢?
var a = {k1: 1}
var b = a
a.k3 = a = {k2: 2}
正确答案应该是
a = {
k2: 2
}
b = {
k1: 1,
k3: {
k2: 2
}
}
注意赋值运算符的执行顺序是从右往左,因此 a.k3 中的 a 是之前的那个 a 指向的地址,并不是 a = {k2: 2} 中的 a 指向的地址。
如果现在你不能理解,那么请看下面的这段代码
var a = {k1: 1}
var b = a
a = {k2: 2}
a.k3 = {k2: 2}
结果:
a = {
k2: 2,
k3: {
k2: 2
}
}
b = {
k1: 1
}
此时 a.k3 中的 a 和 a = {k2: 2} 的 a 指向的是同一个地址。
2020年6月9日(let 不能声明已经声明过的变量)
首先请大家看一下下面这段代码能正确运行吗?如果不能请说明理由,如果能请说出结果。
let a = 1
for (var a = 0; a< 3; a++) {
console.log(a)
}
正确的答案是会报错,因为 var a = 0 存在变量提升,相当于在 let a = 1 的上面声明了一个变量 a,由于 let 不能声明重复的变量,因此会报错。
2020年6月10日(防抖)
防抖指的是在用户停止触发某个事件一段时间后才会去执行相应的事件处理程序,非常的一个场景就是在搜索框中输入内容时,并不会在每次输入内容后都触发按键被按下事件对应的事件处理程序,而是当用户停止输入一段时间之后才会去触发对应的事件处理程序。
防抖程序实现起来也比较简单,当 timer 不为 null 时说明用户在 delay 时间内又触发事件了,所以此时将之前的定时器事件清除,不让其执行事件处理程序。
function debounce (fn, delay) {
let timer = null
return function () {
if (timer) {
clearTimeout(timer)
}
timer = setTimeout(() => {
// 调用用户传递进来的 fn 函数
fn.apply(this, arguments)
console.log(this) // 结果为 <input type="text" id='input'>
// 将 timer 赋值为 null,不能再执行上面的 clearTimeout
timer = null
}, delay)
}
}
2020年6月11日(节流)
昨天和大家分享了防抖,今天再和大家分享一下节流,所谓节流就是当用户在持续触发某个事件的时候,事件处理程序不是一直被触发而是每隔一段时间触发一次,例如我们在页面上拖动某个元素时,绑定的 mousemove 事件对应的事件处理程序就经过了节流的处理,让其每隔一段时间后触发一次,这样无疑就提升了性能,而且选择合适的时间间隔后用户的肉眼也分辨不出来。
在实现上和防抖类似,只是在判断 timer 不为 null 时就 return,不再开启新的定时任务,当上一次定时任务到时间执行之后,再去注册新的定时事件。
function throttle (fn, delay) {
let timer = null
return function () {
if (timer) {
return
}
timer = setTimeout(() => {
// 外部怎样将参数传递进来呢?arguments 有值吗?
fn.apply(this, arguments)
// timer 为 null 时,启动一个新的定时任务
timer = null
}, delay)
}
}
2020年6月12日(正则中常用的重复字符)
接下来的几天和大家复习一下正则中的一些知识点,今天分享一下正则中常用的重复字符。
正则表达式中的重复字符:
重复字符 | 含义 |
---|---|
{n,m} | 匹配前一项至少n次,但不超过m次 |
{n,} | 匹配前一项n次或更多次 |
{n} | 匹配前一项n次 |
? | 匹配前一项0次或1次,等价于 {0,1} |
+ | 匹配前一项1次或多次,等价于 {1,} |
* | 匹配前一项0次或多次,等价于 {0,} |
2020年6月13日(正则中常见的元字符)
今天和大家分享一下正则中常见的一些元字符。
字符类 | 匹配 |
---|---|
[...] | 方括号内的任意字符 |
[^...] | 不在方括号内的任意字符 |
. | 除换行符和其它Unicode行终止符之外的任意字符 |
w | 任何ASCII字符组成的单词,等价于 [a-zA-Z0-9] |
W | 任何不是ASCII字符组成的单词,等价于 [^a-zA-Z0-9] |
s | 任何Unicode空白符 |
S | 任何非Unicode空白符 |
d | 任何ASCII数字,等价于 [0-9] |
D | 除了ASCII数字之外的任何字符,等价于 [^0-9] |
2020年6月14日(正则中与位置相关的锚字符)
今天和大家分享一下正则中与位置相关的一些锚字符。
字符 | 含义 |
---|---|
^ | 匹配字符串的开头,在多行匹配(有修饰符m)中匹配每一行的开头 |
$ | 匹配字符串的结尾,在多行匹配(有修饰符m)中匹配每一行的结尾 |
匹配一个单词的边界,简言之,就是位于字符 w 和 W 之间的位置,或位于字符 w 和字符串的开头或者结尾之间的位置(但需要注意, [] 匹配的是退格符) |
|
B | 匹配非单词边界的位置 |
(?=p) | 零度正向先行断言,要求接下来的字符要与p匹配,并且匹配的结果不包含p,如 var result = 'JavaScript:a'.match(JavaScript(?=:)) 匹配结果: result[0]='JavaScript' JavaScript 后面要紧跟着是 ':' 才能匹配成功,并且结果中不包含 ':' |
(?!p) | 零度负向先行断言,要求接下来的字符不与p匹配 |
(?<=p)(提案) | 后行断言,要求前面的字符要与p匹配,并且匹配的结果中不包含p,如 var result = /(?<=$)d+/.exec('$100*80'); // result=['100'] |
(?<!p) | 后行否定断言,要求前面的字符不能与p匹配,并且匹配结果中不包含p,如 var result= /(?!$)d+/.exec('$100*80'); // result=['80'] |
2020年6月15日(字符串中可以使用正则的四种方法介绍)
今天和大家分享一下字符串中可以使用正则的四种方法。
String方法 | 用法 |
---|---|
search() | 参数 :一个正则表达式 ,若不是,会先通过RegExp构造函数将其转换成正则表达式 返回值 :第一个与之匹配的子串的起始位置,若匹配失败返回-1 注意 :不支持全局搜索,忽略g修饰符 示例 :'JavaScript'.search(/Script/); // 4 |
replace() | 参数 :第一个是正则表达式,第二个是要进行替换的字符串 返回值 :替换后的字符串 注意 :如果第一个参数不是正则表达式,不会进行转换 示例 :'javascript'.replace(/w+/,'JavaScript'); // JavaScript |
match() | 参数 :一个正则表达式,若不是,会先通过RegExp构造函数将其转换成正则表达式 返回值 :非全局匹配时:一个数组,第一个元素是与正则表达式相匹配的字符串,余下的元素是与圆括号(分组)内的子表达式匹配的字符串 全局匹配时:一个数组,元素由正则表达式相匹配的字符串组成,也就是只有非全局匹配时返回数组的第一个元素 注意 :非全局匹配时,返回的数组带有两个属性,input:要匹配的字符串 index:匹配成功的子串的起始位置 示例 :var result = '1+2=3'.match(/d+/); //result[0]='1' result.index=0 result.input='1+2=3' var result = '123'.match(/(1)d+/); // result[0]='123' result[1]='1'result.index=0 result.input='123' 全局匹配时 :var result = '123'.match(/(1)d+/g); // result[0]='123' 没有index,input等属性,也没有result[1] |
split() | 参数 :可以是一个字符串也可以是一个正则表达式 返回值 :将一个字符串拆分为一个子串组成的数组 示例 :'1, 2, 3'.split(','); // ['1', '2', '3'] '1, 2, 3'.split(/s*,s*/); //['1','2','3'] |
2020年6月16日(正则表达式方法)
昨天和大家分享了字符串中可以使用正则的方法,今天和大家分享一下正则表达式的两个方法。
方法 | 用法 |
---|---|
exec() | 参数 :一个字符串 返回值 :无论是否是全局匹配,都返回一个数组,并且第一个元素是与正则表达式相匹配的字符串,余下的元素是与圆括号内的子表达式相匹配的字串 注意 :无论是否是全局匹配返回的数组都带有index和input属性 示例 :var result = /(1)d+/.exec('123'); // result[0]='123 result[1]='1' result.index=0 result.input='123' var result = /(1)d+/g.exec('123); //result[0]='123' result[1]='1' result.index=0 result.input='123' 全局匹配 |
test() | 参数 : 一个字符串 返回值 :如果包含正则表达式的一个匹配结果,返回true,否则返回false 示例 :/d+/.test('123'); // true /d+/.test('abc'); // false |
2020年6月17日(数组扁平化方法一)
所谓数组扁平化指的就是将一个多维数组变成一维数组的形式,例如
let arr = [1, [2, 3, [4, 5]]]
// 经过数组扁平化之后变为
[1, 2, 3, 4, 5]
对于 arr 调用 toString 方法后会得到
‘1, 2, 3, 4, 5’
因此我们可以利用 split 方法再将其转换为数组,然后再将其变成 Number 类型即可。
代码如下:
// toString & split
function flatten (arr) {
return arr.toString().split(',').map(item => {
return parseInt(item)
})
}
明天再和大家分享数组扁平化的其它方法。
2020年6月18日(数组扁平化方法二)
今天和大家继续分享数组扁平化的方法,利用递归结合 reduce 方法,如果遍历的每一个元素中还有数组,那么就进行递归将这些数组再进行扁平化操作。
代码如下:
function flatten(arr) {
return arr.reduce((result, item)=> {
// 递归终止条件 Array.isArray(item) ? flatten(item) : item
return result.concat(Array.isArray(item) ? flatten(item) : item);
}, []);
}
2020年6月19日(数组扁平化方法三)
今天和大家继续分享数组扁平化的方法,利用数组的 some 方法,数组的 some 方法作用是遍历数组的每一项直到回调函数中返回的是 true。下面的代码中,some 方法中的回调函数的作用是判断数组中遍历的每个元素是不是数组,并将判断结果返回。
some 方法遍历到的元素要么是数组要么不是数组。
如果是数组,sone 方法的回调返回 true,some 遍历就截止,进入 while 循环利用扩展运算符将数组进行扁平化,注意扩展运算符只能拆一层,也就是:
let arr = [1, [2, [3, 4]]]
...arr // [1, 2, [3, 4]]
此时会利用改变后的 arr 执行 some 方法。
如果不是数组,some 方法回调的返回值是 false,会继续执行 some 方法,此时不会进入 while 循环。
完整代码
function flatten (arr) {
while(arr.some(item => Array.isArray(item))) {
arr = [].concat(...arr);
}
return arr;
}
2020年6月20日(数组去重方法一)
今天和大家分享一些数组去重,第一种方法就是利用 ES6 中新增的 Set 类型,它有一个特点就是它的实例中的元素不能有重复的,利用这一点就可以实现数组去重。
function unique {
return [...new Set(arr)]
}
上述方法有一个缺点就是对于引用数据类型没有办法去重,例如 {a: 1}
和 {a: 1}
并不能去重。
2020年6月21日(数组去重方法二)
今天和大家分享数组去重的第二种方法,利用数组的 indexof,我们知道如果某个元素不在这个数组中的话那么就会返回 -1,利用这一点进行数组去重。
function unique (arr: number[]): number[] {
let res = []
arr.forEach(element => {
if (res.indexOf(element) === -1) {
res.push(element)
}
})
return res
}
上述方法有一个缺点就是对于引用数据类型和 NaN 没有办法去重,例如 {a: 1}
和 {a: 1}
并不能去重。
2020年6月22日(数组去重方法三)
今天和大家分享数组去重的第三种方法,利用对象属性的唯一性。
function unique (arr: number[]): number[] {
let res: number[] = []
let obj: object = {}
arr.forEach((element: number) => {
if (!obj[element]) {
res.push(element)
obj[element] = 1
}
})
return res
}
这种方法可以去重对象字面量和 NaN。
2020年6月23日(数组去重方法四)
今天和大家分享一下数组去重的第四种方法,遍历两次数组,如果遇到相同的元素就从数组中删除一个。
function unique(arr) {
for (let i = 0; i < arr.length; i++) {
for (let j = i + 1; j < arr.length; j++) {
if (arr[i] === arr[j]) {
arr.splice(j, 1)
j--
}
}
}
return arr
}
上述方法有一个缺点就是对于引用数据类型和 NaN 没有办法去重,例如 {a: 1}
和 {a: 1}
并不能去重。
2020年6月24日(冒泡排序)
看到冒泡排序可能大家都会觉得比较简单,在算法面试中直接让你写冒泡排序的可能性不大,但是它排序的过程是我们在解决有些算法问题中需要的.
它排序过程实际上是挨个将最大的元素的挪到最后面,这也是冒泡排序的由来。
例如 LeetCode164. 最大间距 就可以利用冒泡排序的思想。
function bubleSort (arr) {
for (let i = 0, len = arr.length; i < len - 1; i++) {
for (let j = 0; j < len - i - 1; j++) {
if (arr[j] > arr[j + 1]) {
[arr[j], arr[j + 1]] = [arr[j + 1], arr[j]]
}
}
}
return arr
}
2020年6月25日(选择排序)
插入排序也是一种比较简单的排序方法,大体思想就是依次找出所有元素中最小的元素放在数组的最前面,它的思路很重要,可以应用在一些算法问题上,例如 LeetCode 41. 缺失的第一个正数。
function selectSort (arr) {
for (let i = 0, len = arr.length; i < len; i++) {
let indexMin = i
for (let j = i; j < len; j++) {
if (arr[j] < arr[indexMin]) {
indexMin = j
}
}
if (i !== indexMin) {
[arr[i], arr[indexMin]] = [arr[indexMin], arr[i]]
}
}
return arr
}
2020年6月26日(插入排序)
对于一个数组而言,如果只有一个元素,那么肯定是有序的,这个时候再增加一个元素,并将这两个元素进行排序,那么这两个元素也是具有顺序,如果再增加一个元素,将这三个元素进行排序,那么这三个元素就是有序的了。
例如现在有两个元素 1, 3
,现在又有一个元素 2,需要插入到前面已经排好序的数组中,只需要插入到 1 和 3 之间即可。
因此插入排序的思想就是遍历整个数组,当前遍历到的元素插入到前面已经有序的数组中合适的位置,使其也变为有序的,直到数组全遍历完。
function insertSort (arr: number[]): number[] {
for (let i = 0, len = arr.length; i < len; i++) {
let j: number = i
while(j > 0 && arr[j] < arr[j - 1]) {
[arr[j], arr[j - 1]] = [arr[j - 1], arr[j]]
j--
}
}
return arr
}
2020年6月28日(归并排序)
归并排序的大体思路是先将数组进行拆分,知道每个数组中只有一个元素,然后再进行合并,合并的过程中会有排序的过程,这样当所有的数组全都合并完成后数组也就变成有序的了。
function mergeSort (arr: number[]): number[] {
let len: number = arr.length
if (len === 1) {
return arr
}
let mid: number = Math.floor(arr.length / 2)
let left: number = arr.slice(0, mid)
let right: number = arr.slice(mid, len)
return merge (mergeSort(left), mergeSort(right))
function merge (left: number[], right: number[]): number[] {
let res: number[] = []
let i: number = 0
let j: number = 0
while (i < left.lenth && j < right.length) {
if (left[i] > right[j]) {
res.push(right[j++])
} else {
res.push(left[i++])
}
}
while(i < left.length) {
res.push(left[i++])
}
while(j < right.length) {
res.push(right[j++])
}
return res
}
}
2020年6月28日(快速排序)
快速排序是面试中比较喜欢考察的,大体思想就是选一个主元,主元的位置并不是唯一的,一般来说选择数组的中间位置。然后再定义一个 left 指针从数组的左侧开始寻找比主元大的元素,定义一个 right 指针,从数组的右侧开始寻找比主元小的元素,如果同时都有那么就交换这个两个元素,当left > right 时停止。然后将数组分成左右两个子数组(分离的位置由partition函数得到),继续递归上面的过程。
function quickSort (arr, left, right) {
// 划分左右数组的索引
let index
if (arr.length > 1) {
index = partition(arr, left, right)
if (left < index - 1) {
quickSort(arr, left, index - 1)
}
if (right > index) {
quickSort(arr, index, right)
}
}
function partition (arr, left, right) {
// 设置主元
let pivot = arr[Math.floor((left + right) / 2)],
i = left,
j = right
// 比较左右指针处的元素和主元的大小
while (i <= j) {
while (arr[i] < pivot) {
i++
}
while (arr[j] > pivot) {
j--
}
if (i <= j) {
[arr[i], arr[j]] = [arr[j], arr[i]]
i++
j--
}
}
return i
}
}
2020年6月29日(堆排序)
我们要构建出的堆的特点是父节点大于左右子节点的值,那么整个堆的根节点就是最大值,这个时候将根节点拿出来和最后一个节点进行互换,并且将根节点 push 到一个数组中,并且将堆的大小减一。在经过互换之后还需要再次构建堆,然后再进行互换,再将根节点 push 到结果数组中,并且将堆的大小减 1。循环执行这个过程,直到堆的大小为 1.这个时候结果数组中存储的就是排好序的数组了。
function heapSort (arr) {
let heapSize = arr.length
buildHeap(arr)
while (heapSize > 1) {
[arr[heapSize - 1], arr[0]] = [arr[0], arr[heapSize - 1]]
heapSize--
heapify(arr, heapSize, 0)
}
function buildHeap (arr) {
let heapSize = arr.length
// 从下往上建立堆,第一个需要建立堆的节点是,arr.length/2
for (let i = Math.floor(heapSize / 2); i >= 0; i--) {
heapify(arr, heapSize, i)
}
}
function heapify (arr, heapSize, i) {
let left = i * 2 + 1,
right = i * 2 + 2,
maxIndex = i
// 递归出口
if (i >= heapSize) {
return
}
if (left < heapSize && arr[maxIndex] < arr[left]) {
maxIndex = left
}
if (right < heapSize && arr[maxIndex] < arr[right]) {
maxIndex = right
}
if (maxIndex !== i) {
[arr[maxIndex], arr[i]] = [arr[i], arr[maxIndex]]
heapify(arr, heapSize, maxIndex)
}
}
}