作者:徐小夕

转发链接:https://mp.weixin.qq.com/s/ZbBTSuO1UPDBWbTKHxI4eA

序言

作为一名前端爱好者, 笔者利用空余韶光研究了几个国外网站的源码,创造不管是库,还是业务代码,都会用到了一些比较故意思的API,虽然平时在事情中部分打仗过,但是经由这次的研究,以为很有必要总结一下,毕竟已经2020年了,是时候更新一下技能储备了,本文紧张通过实际案例来带大家快速理解以下几个知识点:

jshtml流解析例子几个异常有意思的javascript常识点总结实践 Node.js

Observer 原生不雅观察者script标签事宜深入 - 移除script标签后事宜仍旧能实行的缘故原由Proxy/Reflect自定义事宜fileReader APIFullscreen 网页全屏URL API的利用Geolocation 地理位置API的利用Notifications 浏览器原生关照Battery Status 设备电量情形

我会对部分API做一些比较故意思的案例,那么开始我们的学习吧~

1. Observer API

Observer是浏览器自带的不雅观察者,它紧张供应了Intersection, Mutation, Resize, Performance这四类不雅观察者, 这里笔者重点先容Intersection Observer.

1.1 Intersection Observer

IntersectionObserver供应了一种异步不雅观察目标元素与其先人元素交叉状态的方法。
当一个IntersectionObserver工具被创建时,其被配置为监听根中一段给定比例的可见区域,并且无法变动其配置,以是一个给定的不雅观察者工具只能用来监听可见区域的特定变革值;然而,我们可以在同一个不雅观察者工具中配置监听多个目标元素。

说大略点便是该api可以异步监听目标元素在根元素里的位置变动,并触发相应事宜.我们可以利用它来实现更为高效的图片

// 1.定义不雅观察者及不雅观察回调const intersectionObserver = new IntersectionObserver((entries, observer) => { entries.forEach(entry => { console.log(entry) // ...一些操作 }); }, { root: document.querySelector('#root'), rootMargin: '0px', threshold: 0.5 })// 2. 定义要不雅观察的目标工具const target = document.querySelector(“.target”);intersectionObserver.observe(target);复制代码

以上代码就实现了一个基本的Intersection Observer,虽然已有代码中还表示不出什么本色性功能. 接下来先容一下代码中利用到的参数的含义:

callback IntersectionObserver实例的第一个参数, 当目标元素与根元素通过阈值时就会触发该回调.回调中第一个参数是被不雅观察工具列表,一旦被不雅观察工具发生突变就会被移入该列表, 列表中每一项都保留有不雅观察者的位置信息;第二个参数为observer,不雅观察者本身.如下图掌握台打印:个中rootBounds表示根元素的位置信息, boundingClientRect表示目标元素的位置信息,intersectionRect表示叉部分的位置信息, intersectionRatio表示目标元素的可见比例.配置属性 IntersectionObserver实例的第二个参数,用来配置监听属性,详细有以下三个属性:root 所监听工具的详细先人元素(element)。
如果未传入值或值为null,则默认利用顶级文档的视窗。
rootMargin 打算交叉时添加到根(root)边界盒bounding box的矩形偏移量, 可以有效的缩小或扩大根的剖断范围从而知足打算须要thresholds 一个包含阈值的列表, 按升序排列, 列表中的每个阈值都是监听工具的交叉区域与边界区域的比率。
当监听工具的任何阈值被超越时,都会天生一个关照(Notification)。
如果布局器未传入值, 则默认值为0。
以上属性先容字面上可能很难明得,笔者花几个草图来让大家有个直不雅观的认知:当我们设置rootMargin为10px时,我们的root会增大影响范围,但目标元素移动到淡赤色区域就会被监听到,当然我们还可以设置rootMargin为负值来减少影响区域.其支持的值为百分比和px,如下:

rootMargin: '10px'rootMargin: '10%'rootMargin: '10px 0px 10px 10px'复制代码

thresholds可以如下图理解:

由上图所示,当我们设置阈值为[0.25, 0.5]时, 目标元素的25%和50%进入根元素的影响范围时都会触发回调.利用这个特性我们每每可以实现位差动画,或者更根据目标元素的位置变革做不同的交互. 当然Intersection还供应了以下几个方法来掌握不雅观察工具:

disconnect() 使IntersectionObserver工具停滞监听事情takeRecords() 返回所有不雅观察目标的IntersectionObserverEntry工具数组unobserve() 使IntersectionObserver停滞监听特定目标元素

理解了利用方法和api之后,我们来看看一个实际运用--实现图片

<img src="loading.gif" data-src="absolute.jpg"><img src="loading.gif" data-src="relative.jpg"><img src="loading.gif" data-src="fixed.jpg"><script>let observerImg = new IntersectionObserver((entries, observer) => { entries.forEach(entry => { // 更换为正式的图片 entry.target.src = entry.target.dataset.src; // 停滞监听 observer.unobserve(entry.target); }); }, { root: documennt.getElementById('scrollView'), threshold: 0.3 });document.querySelectorAll('img').forEach(img => { observerImg.observe(img) });</script>复制代码

以上代码就实现了一个图片1.2 Mutation Observer和Resize Observer

Mutation Observer紧张用来实现dom变动时的监听,同样也是异步触发,对监听性能非常友好. Resize Observer紧张用来监听元素大小的变革,比较于每次窗口变动都触发的window.resize事宜, Resize Observer有更好的性能和对dom有更细粒度的掌握,它只会在绘制前或布局后触发调用. 以上两个api的利用和Intersection利用非常类似,官方资料也写得很全,大家可以好好研究一下.

2. 移除script标签后事宜仍旧能实行的缘故原由

这个问题紧张是之前有朋友问过我,当时的想法便是大略的认为script内的代码实行完之后以及与dom绑定了,存放在了浏览器内存中,最近查了很多资料创造有一个有点意思的阐明,放出来大家可以感想熏染一下:

JavaScript阐明器在实行脚本时,是按块来实行的,也便是说浏览器在解析HTML文档流时,如果碰着一个script标签,javascript阐明器会等待这个代码块都加载完了,才进行预编译,然后才实行。
以是,当开始实行这个代码块的代码时,这个代码段已经被解析完了。
这时再从DOM中删去也就不影响代码的实行了。

3. Proxy/Reflect

Proxy/Reflect虽然是es6的api,涌现也已经有几年了,但是在项目中用的还是比较少,如果是做底层架构方面的事情,还是建议大家多去利用,毕竟vue/react这种框架源码把这些api玩的如火纯青,还是很有必要节制一下的。

实在我们负责看mdn的先容或者阮一峰老师的文章,还是很好理解这些api的用法的,接下来我们详细先容一下这两个api以及运用处景.

3.1 Proxy

Proxy 可以理解成,在目标工具之前架设一层“拦截”,外界对该工具的访问,都必须先通过这层拦截,因此供应了一种机制,可以对外界的访问进行过滤和改写。
Proxy在很多场景中都会和Reflect一起利用. 用法也很大略,我们看看Proxy的基本用法:

const obj = { name: '徐小夕', age: '120' } const proxy = new Proxy(obj, { get(target, propKey, receiver) { console.log('get:' + propKey) return Reflect.get(target, propKey, receiver) }, set(target, propKey, value, receiver) { console.log('set:' + propKey) return Reflect.set(target, propKey, value, receiver) } }) console.log(proxy.name) // get:name 徐小夕 proxy.work = 'frontend' // set:work frontend复制代码

以上代码拦截了obj工具,并重新定义了读写(get/set)方法,这样我们就可以在访问工具时进行额外的操作了.

Proxy还有apply(拦截 Proxy 实例作为函数调用的操作)和construct(拦截 Proxy 实例作为布局函数调用的操作)等属性可以利用,我们可以在工具操作的不同阶段进行拦截,这里我就不一一样举例了.接下来看看Proxy的实际运用处景.

实现数组读取负数的索引

我们一样平常操作数组大多数都是正向操作的,不能通过指定负数来逆向查找数组,如下图:

我们不能通过arr[-1]来拿到数组的尾部元素(字符串同理),这个时候我们就可以用Proxy来实现这一功能,这是我们的构造有点像环状:

这种实现的好处是如果我们想访问数组的末了一个元素时,我们不须要先拿到长度,再通过索引访问了:

// 原始写法arr[arr.length -1]// 通过proxy改造后写法arr[-1]复制代码

实当代码如下:

function createArray(...elements) { let handler = { get(target, propKey, receiver) { let index = Number(propKey); if (index < 0) { propKey = String(target.length + index); } return Reflect.get(target, propKey, receiver); } }; let target = []; target.push(...elements); return new Proxy(target, handler);}复制代码

我们可以创造以上代码利用proxy来代理数组的读取操作,在内部封装了支持负值查找的功能,当然我们也可以不用proxy来实现同样的功能,这里实现参考阮一峰老师的实现.

利用proxy实现更优雅的校验器

一样平常我们在做表单校验的时候会写一些if else或者switch判断来实现对不同属性值的校验,同样我们也可以用proxy来优雅的实现它,代码如下:

const formData = { name: 'xuxi', age: 120, label: ['react', 'vue', 'node', 'javascript'] } // 校验器 const validators = { name(v) { // 考验name是否为字符串并且长度是否大于3 return typeof v === 'string' && v.length > 3 }, age(v) { // 考验age是否为数值 return typeof v === 'number' }, label(v) { // 考验label是否为数组并且长度是否大于0 return Array.isArray(v) && v.length > 0 } } // 代理校验工具 function proxyValidator(target, validator) { return new Proxy(target, { set(target, propKey, value, receiver) { if(target.hasOwnProperty(propKey)) { let valid = validator[propKey] if(!!valid(value)) { return Reflect.set(target, propKey, value, receiver) }else { // 一些其他缺点业务... throw Error(`值验证缺点${propKey}:${value}`) } } } }) }复制代码

有了以上实现模式,我们就可以实现对表单中某个值进行设置时进行校验了,用法如下:

let formObj = proxyValidator(formData, validators)formObj.name = 333; // Uncaught Error: 值验证缺点name:fformObj.age = 'ddd' // Uncaught Error: 值验证缺点age:f复制代码

以上代码中当设置了不合法的值时,掌握台将会剖出错误,如果在实际业务中,我们可以给用户做出适当的提醒.

实现要求拦截和缺点上报实现数据过滤

以上几点笔者在之前的文章中也写过,以是这里不在详细先容了.大家也可以根据实际情形自己实现更加灵巧的拦截操作.当然Proxy供应的API远远不止这几个,我们可以在MDN或者其他渠道理解更多高等用法.

3.2 Reflect

Reflect工具与Proxy工具一样,也是 ES6 为了操为难刁难象而供应的新 API,更多的运用处景是合营proxy一起利用,在上文中已经用到了.可以将Object工具的一些明显属于措辞内部的方法放到Reflect工具上,并修正某些Object方法的返回结果. Reflect工具的方法与Proxy工具的方法逐一对应,只假如Proxy工具的方法,就能在Reflect工具上找到对应的方法。

4. 自定义事宜

CustomEvent API是个非常故意思的api, 而且非常实用, 更主要的是学起来非常大略,而且被大部分当代浏览器支持.我们可以让任意dom元素监听和触发自定义事宜,只须要如下操作:

// 添加一个适当的事宜监听器dom1.addEventListener("boom", function(e) { something(e.detail.num) })// 创建并分发事宜var event = new CustomEvent("boom", {"detail":{"num":10}})dom1.dispatchEvent(event)复制代码

我们来看看CustomEvent的参数先容:

type 事宜的类型名称,如上面代码中的'boom'CustomEventInit 供应了事宜的配置信息,详细有以下几个属性bubbles 一个布尔值,表明该事宜是否会冒泡cancelable 一个布尔值,表明该事宜是否可以被取消detail 当事宜初始化时通报的数据

我们可以通过dispatchEvent来触发自定义事宜.实在他的用场有很多,比如创建不雅观察者模式, 实现数据双向绑定, 亦或者在游戏开拓中实现打怪掉血,比如下面的例子:

笔者上面画了一个打boss的草图, 现在的场景是两个玩家一起打boss, 我们可以在玩家发动攻击的时候触发dispatch掉血的自定义事宜, boss监听到事宜后将血量自动扣除, 至于不同角色的侵害值,我们可以存放在detail中,然后通过策略模式去分发侵害.笔者曾今在学校开拓的H5游戏时就大量采取类似的模式,还是非常故意思的.

5. fileReader

File API使得我们在浏览器端可以访问文件的数据,比如预览文件,获取文件信息(比如文件名,文件内容,文件大小等), 并且可以在前端实现文件下载(可以借助canvas和 window.URL.revokeObjectURL的一些能力).当然我们还可以实现拖拽上传文件这样高用户体验的操作.接下来我们来看看几个实际例子.

显示缩略图

function previewFiles(files, previewBox) { for (var i = 0; i < files.length; i++) { var file = files[i]; var imageType = /^image\//; if (!imageType.test(file.type)) { continue; } var img = document.createElement("img"); previewBox.appendChild(img); // 假设"preview"便是用来显示内容的div var reader = new FileReader(); reader.onload = (function(imgEl) { return function(e) { imgEl.src = e.target.result; }; })(img); reader.readAsDataURL(file); } }复制代码

以上代码可以在reviewBox容器中显示已上传好的图片,当然我们还可以基于此来扩展,利用canvas将图片画到canvas上,然后进行图片压缩,末了再把压缩后的图片上传到做事器.这种办法实在目前很多工具型网站都在用,比如在线图片处理网站,供应的批量压缩图片,批处理水印等功能,套路都差不多,感兴趣的朋友可以考试测验研究一下.

封装文件上传组件

这块笔者之前也写过详细的文章,这里就不一一举例了, 文章地址:

3分钟教你用原生js实现具有进度监听的文件上传预览组件记一次老项目中的跨页面通信问题和前端实现文件下载功能6. Fullscreen

全屏API紧张是让网页能在电脑屏幕中全屏显示,它许可我们打开或者退出全屏模式,以便我们根据须要进行对应的操作,比如我们常用的网页图形编辑器或者富文本编辑器, 为了让用户专心于内容设计,我们每每供应切换全屏的功能供用户利用.由于全屏API比较大略,这里我们直接上代码:

// 开启全屏document.documentElement.requestFullscreen();// 退出全屏document.exitFullscreen();复制代码

以上代码的document.documentElement也可以换成任何一个你想让其全屏的元素.默认情形下我们还可以通过document.fullscreenElement来判断当前页面是否处于全屏状态,来实现屏幕切换的效果.如果是react开拓者,我们也可以将其封装成一个自定义hooks来实现与业务干系的全屏切换功能.

7. URL

URL API是URL标准的组成部分,URL标准定义了构成有效统一资源定位符的内容以及访问和操作URL的API。

我们利用URL组件可以做很多故意思的事情.比如我们有个需求须要提取url的参数传给后台,传统的做法是自己写一个方法来解析url字符串,手动返回一个query工具.但是利用URL工具,我们可以很方便的拿到url参数,如下:

let addr = new URL(window.location.href)let host = addr.host // 获取主机地址let path = addr.pathname // 获取路径名let user = addr.searchParams.get("user") // 获取参数为user对应的值复制代码

以上代码可知,我们如果将url转化为URL工具,那么我们就可以很方便的通过searchParams供应的api来拿到url参数而无需自己再写一个方法了.

另一方面,如果网站安全性比较高,我们还可以对参数进行自然数排序然后再加密上传给后端.详细代码如下:

function sortMD5WithParameters() { let url = new URL(document.location.href); url.searchParams.sort(); let keys = url.searchParams.keys(); let params = {} for (let key of keys) { let val = url.searchParams.get(key); params[key] = val }; // ...md5加密 return MD5(params) }复制代码8. Geolocation

地理位置 API 通过 navigator.geolocation 供应, 这个浏览器API也比较实用, 我们在网站中可以用此办法确定用户的位置信息,从而让网站有不同的展现,增强用户体验.

举几个故意思的例子可以让大家感想熏染一下:

根据不同地区,网站展示不同的主题:根据用户所在地区,展示不同推举内容 这一点电商网站或者内容网站用的比较多, 比如用户在新疆,则给他推举瓜果类广告, 在北京,则给他推举旅游景点类广告等,虽然实际运用中每每会更繁芜,但是也是一种思路.

实在运用远远不止如此,程序员可以发挥想象来实现更故意思的事情,让自己的网站更智能.接下来笔者就基于promise写一段获取用户位置的代码:

function getUserLocation() { return new Promise((resolve, reject) => { if (!navigator.geolocation) { reject() } else { navigator.geolocation.getCurrentPosition(success, error); } function success(position) { const latitude = position.coords.latitude; const longitude = position.coords.longitude; resolve({latitude, longitude}) } function error() { reject() } }) }复制代码

利用办法和结果如下图所示:

我们基于获取到的经纬度调用第三方api(比如百度,高德)就可以获取用户所在为精确位置信息了.

9. Notifications

Notifications API 许可网页或运用程序在系统级别发送在页面外部显示的关照;这样纵然运用程序空闲或在后台,Web运用程序也会向用户发送信息。

我们举个实际的例子,比如我们网站内容有更新,关照用户,效果如下:

干系代码如下:

Notification.requestPermission( function(status) { console.log(status); // 仅当值为 "granted" 时显示关照 var n = new Notification("趣谈前端", {body: "从零搭建一个CMS全栈项目"}); // 显示关照});复制代码

当然浏览器的Notification还给我们供应了4个事宜触发api方便我们做更全面的掌握:

show 当关照被显示给用户时触发click 当用户点击关照时触发close 当关照被关闭时触发error 当关照发生缺点的时候触发

有了这样的事宜监听,我们就可以掌握当用户点击关照时, 跳转到对应的页面或者实行干系的业务逻辑.如下代码所示:

Notification.requestPermission( function(status) { console.log(status); // 仅当值为 "granted" 时显示关照 var n = new Notification("趣谈前端", {body: "从零搭建一个CMS全栈项目"}); // 显示关照 n.onshow = function () { // 显示时实行的逻辑 console.log('show') } n.click = function () { // 被点击时实行的逻辑 history.push('/detail/1232432') } n.close = function () { // 关闭时实行的逻辑 console.log('close') }});复制代码

当然我们在利用前须要获取权限,办法也很大略,大家可以在mdn长进修理解.

10. Battery Status

Battery Status API供应了有关系统充电级别的信息并供应了通过电池等级或者充电状态的改变提醒用户的事宜。
这个可以在设备电量低的时候调度运用的资源利用状态,或者在电池用尽前保存运用中的修正以防数据丢失。

之前的版本中Battery Status API供应了几个事宜监听函数来监听电量的变革以及监听设备是否充电,但是笔者看文档时这些api都已经废弃,如下:

chargingchange 监听设别是否充电levelchange 监听电量充电等级chargingtimechange 充电韶光变革dischargingtimechange 放电韶光变革

虽然以上几个看似有用的api已经被弃用,但是笔者亲测谷歌还是可以正常利用的,但是为了让自己代码更可靠,我们可以用其他办法代替,比如用定时器定期去检测电量情形,进而对用户做出不同的提醒.

接下来我们看看基本的用法:

navigator.getBattery().then(function(battery) { console.log("是否在充电? " + (battery.charging ? "是" : "否")); console.log("电量等级: " + battery.level 100 + "%"); console.log("充电韶光: " + battery.chargingTime + " s"); console.log("放电韶光: " + battery.dischargingTime + "s");});复制代码

我们可以通过getBattery拿到设备电池信息,这个api非常有用,比如我们可以在用户电量不敷时禁用网站动画或者停用一些耗时任务,亦或者是对用户做适当的提醒,改变网站颜色等,对付webapp中播放视频或者直播时,我们也可以用css画一个电量条,当电量告急时提醒用户.作为一个精良的网站体验师,这一块还是不容忽略的.

推举javascript学习干系文章

《关于前端174道 JavaScript知识点汇总(一)》

《关于前端174道 JavaScript知识点汇总(二)》

《关于前端174道 JavaScript知识点汇总(三)》

《70个JavaScript知识点详细总结(上)【实践】》

《70个JavaScript知识点详细总结(下)【实践】》

《JavaScript ECMAScript语法概括【思维导图】》

《100个原生JavaScript代码片段知识点详细汇总【实践】》

《都2020年了,你还不会JavaScript 装饰器?》

《首屏韶光从12.67s到1.06s,手把手教你如何做到的?》

作者:徐小夕

转发链接:https://mp.weixin.qq.com/s/ZbBTSuO1UPDBWbTKHxI4eA