git提交规范
feat: 新功能(feature)
fix: 修补bug
docs: 文档(documentation)
style: 格式(不影响代码运行的变动)
refactor: 重构(即不是新增功能,也不是修改bug的代码变动)
chore: 构建过程或辅助工具的变动
revert: 撤销,版本回退
perf: 性能优化
test:测试
improvement: 改进
build: 打包
ci: 持续集成
feat: 新功能(feature)
fix: 修补bug
docs: 文档(documentation)
style: 格式(不影响代码运行的变动)
refactor: 重构(即不是新增功能,也不是修改bug的代码变动)
chore: 构建过程或辅助工具的变动
revert: 撤销,版本回退
perf: 性能优化
test:测试
improvement: 改进
build: 打包
ci: 持续集成
《你不知道的JavaScript》系列二——–this
绑定详解
this
的误解不管是JavaScript新手开发者还是之前已经接触过其他语言的开发者,都很容易将this理解为指向函数自身。下面简单的例子将证明this远远不止字面意思的这么简单:
1 | function foo(num){ |
从上面代码的输出可以看出从字面意思来理解this是错误的,那如果我增加的count属性和预期的不一样,那我增加的count哪去了?看看下图就一目了然了
需要明确的是,this在任何情况下都不指向函数的词法作用域。在JavaScript内部,作用域确实是与对象类似,可见的标识符都是它的属性,但是作用域“对象”无法通过JavaScript代码访问,它存在与JavaScript引擎内部————《你不知道的JavaScript》
1 | function foo(){ |
这段代码试图使用this来联通foo()和bar()的词法作用域,从而让bar()可以访问foo()作用域里面的变量a。这是不可能实现的,我们不能使用this来引用一个词法作用域内部的东西。
每当你想要把词法作用域和this的查找混合使用时,一定要提醒自己,这是无法实现的
this
到底是什么当一个函数被调用时,会创建一个活动记录(执行上下文)。这个记录会包含函数在哪里被调用(调用栈)、函数的调用方法、传入的参数等信息。this
就是记录的其中一个属性,会在函数执行的过程中用到。
调用栈:为了到达当前执行位置所调用的所有函数
调用位置:在当前正在执行的函数的前一个调用中
this
的四条绑定规则独立函数调用时使用默认绑定规则,绑定到全局对象(非严格模式)或者undefined(严格模式)
1 | function foo(){ |
分析上述代码:
①:在代码中,foo()是直接使用不带任何修饰的函数引用进行调用的,一次只能使用默认绑定,无法使用其他规则。
②:上述代码执行在非严格模式下,因此this绑定到了全局对象上
③:this.a被解析为全局变量a,打印2
Tips:只有在非严格模式下调用foo(),默认绑定才能绑定到全局对象
当调用位置有上下文对象,或者说是否被某个对象拥有或者包含时,考虑隐式绑定。
这个说法非常抽象,具体还是需要通过代码来进行讲解:
1 | function foo(){ |
在上述代码中,调用位置会使用obj上下文来引用函数,因此隐式绑定规则会把函数调用中的this绑定到这个上下文对象,因此this.a
和obj.a
是一样的
Tips:对象属性引用链中只有最顶层或者说最后一层会影响调用位置
1 | function foo(){ |
隐式丢失是非常常见的this绑定问题,也就是说被隐式绑定的函数丢失绑定对象,应用默认绑定,将this绑定到全局对象(严格模式)或者undefined(非严格模式)上;
1 | function foo(){ |
虽然bar是obj.foo的一个引用,但是实际上,它引用的是foo函数本身,因此此时的bar()其实是一个不带任何修饰符的函数调用,因此应用了默认绑定。值得注意的是,参数传递就是一种隐式赋值,因此在回调函数中,绑定的this也是全局对象。
1 | function foo(){ |
使用call()、apply()直接指定this的绑定对象,称之为显式绑定。
显式绑定的两个应用:
观察以下代码,无论之后如何调用函数bar,它总会手动在obj上调用foo
1 | function foo(){ |
由于硬绑定是一种非常常用的模式,所以在ES5中提供了内置的方法Funtion.prototype.bind
,bind()会返回一个硬绑定的新函数,它会将参数设置为this的上下文。
第三方库的许多函数,以及JavaScript语言和宿主环境中许多新的内置函数,都提供了一个可选的参数,通常称之为“上下文”(context),作用和bind()一样,确保回调函数可以使用指定的this
1 | function foo(){ |
使用new来调用函数,会自动执行下面的操作:
1 | function foo(a){ |
使用new来调用foo()时,我们会构建一个新对象并将它绑定到foo()调用的this上。
优先级:new绑定>显式绑定>隐式绑定>默认绑定
如果将null或者undefined作为this的绑定对象传入call、apply或者bind中时,这些值会被忽略,应用默认绑定规则
1 | function foo(){ |
赋值表达式p.foo = o.foo;
的返回值是目标函数的引用,因此调用位置是foo()而不是p.foo()或者o.foo。因此此处应用的是默认绑定
箭头函数不适用this的四种绑定规则,而是根据外层作用域来决定this,并且箭头函数的绑定无法修改,new也不行!
1 | function foo(){ |
函数是否在new中调用?如果是的话this绑定的是新创建的对象
var bar = new foo();
函数是否通过call、apply显式绑定或者bind硬绑定?如果是的话,this绑定的是指定对象
var bar = foo.call(obj2);
函数是否在某个上下文对象中调用(隐式绑定)?如果是的话,this绑定的是上下文对象
var bar = obj1.foo()
如果以上都不是的话,使用默认绑定。在严格模式下,绑定到undefined,非严格模式下,绑定到全局对象
var bar = foo()
需要注意的是有些调用,特别是赋值,可能会在无意中使用默认绑定标准。并且箭头函数不会使用四条标准的绑定规则,而是根据当前的词法作用域来决定this。
1 | const object = { |
1 | function Pet(name) { |
1 | const object = { |
如何调用logMessage
函数,让它打印 "Hello, World!"
?
1 | const object = { |
1 | const object = { |
1 | var length = 4; |
1 | var length = 4; |
'Hello, World!'
隐式调用,此时的this绑定object上
'Fluffy'
和 'Fluffy'
当函数作为构造函数new Pet('Fluffy')
调用时,构造函数内部的this
等于构造的对象
Pet
构造函数中的this.name = name
表达式在构造的对象上创建name
属性。
this.getName = () => this.name
在构造的对象上创建方法getName
。而且由于使用了箭头函数,箭头函数内部的this
值等于外部作用域的this
值, 即 Pet
。
调用cat.getName()
以及getName()
会返回表达式this.name
,其计算结果为'Fluffy'
。
undefined
object.logMessage作为回调函数实际上引用的是logMessage函数本身,因此此时的logMessage()其实是一个不带任何修饰符的函数调用,因此应用了默认绑定。
显式绑定or硬绑定
1 | message: 'Hello, World!' |
'Hello, World!'
和 'Goodbye, undefined!'
当调用object.greet()
时,在greet()
方法内部,this
值等于 object,因为greet
是一个常规函数。因此object.greet()
返回'Hello, World!'
。
但是farewell()
是一个箭头函数,箭头函数中的this
值总是等于外部作用域中的this
值。
farewell()
的外部作用域是全局作用域,它是全局对象。因此object.farewell()
实际上返回'Goodbye, ${window.who}!'
,它的结果为'Goodbye, undefined!'
。
4
callback()
是在method()
内部使用常规函数调用来调用的。由于在常规函数调用期间的this
值等于全局对象,所以this.length
结果为window.length
。
第一个语句var length = 4
,处于最外层的作用域,在全局对象 window
上创建一个属性length
。
3
最近重新翻读《你不知道的JavaScript》有感,进行系列记录,本文适合已经对闭包和模块有些许理解的同学阅读~
下面复习一下闭包
闭包的定义:当函数可以记住并访问所在的词法作用域时,就产生了闭包;闭包使得函数即使是在当前词法作用域之外执行也可以继续访问定义时的词法作用域。
1 | function CoolModule(){ |
观察以上代码我们可以发现:
综上所述,模块模式需要两个条件:
将上述的模块定义封装进一个友好API里,代码很优雅,我觉得很值得学习
1 | var MyModules = (function Manager() { |
直接看上面的代码可能会有点难以理解,下面我搭配debug跟大家一起看看~
从上图可以看到调用了get函数同时也持有了get函数对原始定义作用域的引用(可以看到私有的变量modules及我们添加在上面的bar和foo)这就是闭包的效果,同时暴露公共API(define
和get
)得以对隐藏且私有的内部数据变量进行访问或修改。
其次,该段代码的核心modules[name] = impl.apply(impl, deps)
可以做到使用模块中与传递进来的依赖队列同名的依赖模块传递到函数中,实现函数调用。
相信我,以上代码看着简单写起来可不简单哦~不仅要熟悉掌握闭包的使用还要熟悉模块模式该设计模式,所以建议大家认真看看,最好自己动手写写ヾ(•ω•`)o
虚拟列表赶紧用起来,轻轻松松解决超多重复DOM节点造成的卡顿~
以下有三种不同级别的虚拟列表,分别针对生成的重复DOM节点是固定高度、不同高度和动态变化高度~
虚拟列表的原理其实就是以下几条:
①一个外层盒子提供滚动事件
②外层盒子中装的第一个是platform,一个空盒子,这个空盒子的高度是列表如果真实渲染应该有的高度,作用是为了撑开外层盒子,提供滚动条
②外层盒子中装的第二个是展示列表盒子,这个盒子中放置所有现在应该出现在页面上的列表项和前后缓冲区。该盒子采用绝对定位,top值根据滚动位置实时改变,让展示列表不论怎么滚动一直出现在页面上
④酌情给一些在页面展示之前之后的缓冲区,防止因为用户滚动过快而造成的空白
1 | <template> |
与固定高度不同,列表项的高度是不固定的,所以会出现以下这些难点:
①无法通过页面高度除以列表项高度得到应当展示的数量,也就是展示列表的长度
②无法通过滚动了的高度scrollTop除以列表项高度得到此时应该展示的列表项Index
③无法直接通过ListData的长度乘以列表项高度得到platform的高度
对于以上难点我们的解决方案:
①设置一个预告高度,用于计算页面展示的数量,该预估高度建议偏小,避免出现页面展示数量不够的情况
②设置一个position数组,计算并存储每一个列表项的top\bottom\height值,通过比较scrollTop和列表项的position可以得到此时应该展示的列表项Index
③通过position数组获取最后一个列表项的bottom值,即为platform的高度
1 | <template> |
这种情况可能出现在比如列表项因为太长而设置了展开/收缩按钮,此时列表项的高度是动态发生变化的,这种情况和上一种情况差不多,区别只在于这种情况只需要在点击按钮的时候将position更新即可~所以在这里不做代码演示啦
了解了原理要写出来还是不难的~但我个人感觉有的时候前端的进阶难就难在止步于此,现在的浏览器性能好,可能写N个DOM都不会卡顿,很难会有觉悟自己去写一个虚拟列表来试试看性能是不是更好。希望自己永不止步,永远进步
JavaScript中valueOf、toString的隐式调用
在你不知道的地方JS偷偷调用了valueOf和toString!
你知道执行以下三种情况,控制台会打印什么吗?
1 | Array.prototype.toString = function () { |
1 | Array.prototype.toString = function () { |
1 | Array.prototype.toString = function () { |
控制台输出如下,你答对了吗?
全对的大佬可以出门左转了,出现错误的同学请继续往下看~
针对直接使用cosole.log打印单独的对象,打印语句一在三种情况下都发挥正常。但是往下看,打印语句二console.log(fn)
从上面的结果中我们猜测,当打印的内容为函数时,会在内部隐式调用toString(),如果toString()返回基本数据类型,则数据转换到此为止。如果返回对象数据类型,则在toString()之后还会调用valueOf()。
打印语句3和4:
有报错最好~从报的错误开始入手
TypeError: Cannot convert object to primitive value
翻译:不能将对象转换为原始值
查阅资料发现引用类型向原始类型转换遵循ToPrimitive规则
是引用类型向原始类型转变的规则,它遵循先valueOf后toString的模式期望得到一个原始类型。如果还是没法得到一个原始类型,就会抛出 TypeError。
如alert,console.log明确引用类型是要转换为字符串类型,那么采用的策略是先toString后valueOf。
如隐式类型转换,那么采用ToPrimitive规则,遵循先valueOf后toString的模式期望得到一个原始类型。
在复写这两个函数时,函数返回值起着关键作用,可以决定调用几个方法和最后函数的返回值。好好利用这一特点与隐式自动调用的模式,助力成为装逼达人!
alert会将两个方法最后得到的原始类型值进行返回
但是console.log不会,还是返回函数体。
其他引用类型类似,可以自己动手试试。
对于console.log这个操作不太理解,使用alert阻塞。可以看到先提示调用方法,但是确认完alert弹窗之后,居然console.log打印语句出现在了上面。实在是不理解,希望有懂的大佬不吝赐教。
1 | Array.prototype.toString = function () { |
事情是这样的,学习的时候看到一篇文章,对照着文章中的进行实践操作却得不到一样的结果,于是顺藤摸瓜摸出了这么多瓜。。。从柯里化到隐式调用到隐式类型转换。
记录一下今天安装nvm过程遇到的一些问题~
nvm是一个node的版本管理工具,很适合我这种需要很多node版本的人
我的安装步骤:
nvm -v
检查一下nvm是否安装成功遇到的问题一:无法创建文件夹
解决办法:使用管理员身份打开cmd即可,给程序创建文件夹的权限
遇到的问题二:
1 | PS C:\Users\asus\Desktop\visual_sense_3d>npm i cnpm -g |
解决方法:
1 | 1.查看npm镜像设置 |
应该是由于我前面安装nvm的时候在setting.txt文件中已经修改了node的镜像为taobao,所以在这个地方也要对应修改
安装完毕,美美用起nvm,真的很方便,随意丝滑切换node版本( ̄︶ ̄*))
:sunflower:作用:
当你希望用户能够将该引用程序添加到Mac OS上的“应用程序”文件夹、Windows上的“开始”菜单以及Android和IOS上的主屏幕时,需要一个Web应用程序清单。设置应用程序清单后,可以打开devtools中的Manifest中检验配置是否正确。
:sunflower:编写Web应用程序清单:
清单文件可采用任何名称,但通常命名为 manifest.json
,并从根目录(网站的顶级目录)提供。该规范建议该扩展项应为 .webmanifest
,但浏览器也支持 .json
扩展项。
创建清单后,将 <link>
标记添加到渐进式 Web 应用的所有页面。例如:
1 | <link rel="manifest" href="/manifest.json"> |
具体的Manifest编写规则和配置项如下:https://web.dev/articles/add-manifest?hl=zh-cn
在该位置我们对Service Worker进行检查和调试
:sunflower:Service Worker :
本质上充当位于 Web 应用程序、浏览器和网络(如果可用)之间的代理服务器。除其他外,它们的目的是创建有效的离线体验、拦截网络请求并根据网络是否可用采取适当的操作,以及更新服务器上的资产。它们还允许访问推送通知和后台同步 API。
:sunflower:特性:
import().
则会抛出异常,但允许使用import
语句进行静态导入具体编写Service Worker参考如下:https://developer.mozilla.org/en-US/docs/Web/API/Service_Worker_API
localStorage 用于长久保存整个网站的数据,保存的数据没有过期时间,直到手动去删除。
:hatched_chick:localStorage 的优势
:hatched_chick:localStorage 的局限
localStorage 与 sessionStorage 的唯一一点区别就是 localStorage 属于永久性存储,而 sessionStorage 属于当会话结束的时候,sessionStorage 中的键值对会被清空。
:japanese_ogre:不可以设置过期时间不可以设置过期时间,但是开发者可以自己判断时间然后把它移除,但是它本身是没有设置过期时间的这个操作的
其实都是set方法多传个过期时间啦
ES5扩展Storage
思路很简单,存储的值加一个时间戳,下次取值时验证时间戳。
注意: localStorage
只能存储字符,存入时将对象转为json
字符串,读取时也要解析
1 | Storage.prototype.setExpire = (key, value, expire) => { |
ES6拓展Storage
1 | class Storage { |
localStorage 只支持 string 类型的存储,存入数字取出来的类型也是字符串
需要存入对象时先使用Json.stringify转换为json字符串,取出之后再转换为对象
1 | var storage=window.localStorage; |
1 | localStorage.setItem("key", "value"); |
sessionStorage 用于临时保存同一窗口(或标签页)的数据,在关闭窗口或标签页之后将会删除这些数据。
和localStorage相同
1 | sessionStorage.setItem("key", "value"); |
这两者主要用在前端有大容量存储的页面上,例如,在线编辑浏览器或者网页邮箱
介绍 | 容量 | 状态 | |
---|---|---|---|
WebSql | 关系型数据库 | 大小限制因浏览器而异,但一般为5MB-50MB的范围 | 被W3C标准废弃 |
IndexDB | 非关系型数据库 | 存储大小是250m以上(受计算机硬件和浏览器厂商的限制) | 正常使用 |
BScroll的安装
安装:npm install better-scroll --save
引用:
import BScroll from 'better-scroll'
使用
HTML部分:
1 | <div class="content"> |
JS部分:
1 | //实现滚动功能 |
点击回到顶部
对new出来的BScroll对象使用scrollTo方法
bscroll.scrollTo(0,0,500)
500毫秒回到顶部
缺点
当滚动内容里面有图片时会偶尔出现bug,可能在 BS计算滚动内容的高度的时候图片还没加载过来,这时会导致记得算出来的高度比实际的高度低出现滚动问题
解决办法:图片的@load
监听图片加载完成再对BS进行refresh
vue中css文件引入
import "./normalize.css"
CSS中的变量
:root
获取根元素html
定义变量
1 | :root{ |
使用变量
1 | div{ |
用在设置多个相同颜色的时候很方便:D
position: sticky
使用该属性的时候要设置top属性
当该元素小于这个高度时正常滚动,当该元素大于这个高度时会被固定在这个高度
好用的属性一般不兼容 :(
神奇属性在哪里
1 | div{ |
可以做到div里面的div元素flex布局,而且一行刚好两个(通过调整子元素的宽度可以决定一行里面有多少个子元素),再通过justify-content
属性可以让它们之间的间隙各种等分!间隙等分,子元素等分都有,建议实践出真知,真的超级神奇
为了实现实时更新或类似于聊天室的应用
一开始想到的肯定是配合setTimeout每隔一小段时间就给服务器发送请求,但是这样给服务器的压力很大,不够随机应变太耗费资源
第二个尝试地是长轮询,顾名思义,是服务器发出一个时间比较长的请求。比如时间设定为30秒,在30秒之内,如果收到服务器更新的数据,则进行对应的操作之后再发出请求,如果在30秒之内没有收到返回数据则执行error回调函数继续发出请求。
1 | function Ask(){ |
这种操作比起第一种肯定会比较节约资源,但是本质还是没有改变,还是客户端和服务器之间的单向对话,不够随机应变:D
最后使用了WebSocket,操作简单体验感很好:accept:
(记得引入对应的WebSocket包
1 | //打开双通道 |
WebSocket和之前两种方式的原理都不一样,WebSocket可以打开双通道,做到请求不止可以有客户端发起也可以有服务器发出来,实现了双向对话,节约资源。一般有四个函数:打开双通道,接收服务器发送的消息,给服务器发消息,关闭双通道。
总结: WebSocket真香!