详解valueOf() 与toString()是做什么的以及其在各种情况下的应用

前言

各种引用对象都继承或最终继承于 Object ,使用着Object的原型,所以它们不管何时都有 toString() 和 valueOf() 方法,只不过有些类型的原型重写了这两个方法,比如 Function 实例的原型就重写了 toString() 方法,按照原型链的规则,如果方法和属性在原型链的各原型中有重名,则优先使用最近的方法和属性。


先看看常用的引用类型重写了这两个方法的情况

  • Function 重写了 toString()
  • Date 重写了 toString() 也重写了 valueOf()
  • Array 重写了 toString()
阅读更多

Mac怎么刷新DNS缓存

请使用以下“终端”命令来还原 DNS 缓存设置:

sudo killall -HUP mDNSResponder

从 Event Loop 角度解读 Vue NextTick 源码

解读背景

  1. 在学习 vue 源码,nextTick 方法借助了浏览器的 event loop 事件循环做到了异步更新。
  2. 在公司面试的时候,笔试题最喜欢出关于 JavaScript 运行机制,Promise/A+ 等关于 event loop 线程的题目。
  3. 学会 nextTick 原理帮助定位 BUG , 使用 Vue 会更加灵活。

什么是 event loop

  1. 先执行同步阻塞任务,同步任务会等待上一个执行完毕以后执行下一个,当同步任务执行完毕,再执行异步任务,遇到异步任务会将异步任务的回调函数注册在异步任务队列里。注意,如果主线程上没有同步任务会直接调用异步任务的微任务。
  2. 执行宏任务,遇到微任务将都添加到微任务队列里。
  3. 开始执行微任务队列,当宏任务执行完后执行微任务队列,直到微任务队列全部执行完,微任务队列为空。
  4. 执行宏任务,如果在执行宏任务期间有微任务,将微任务添加到微任务队列里,执行完宏任务之后执行微任务,直到微任务队列全部执行完。
  5. 继续执行宏任务队列。

重复2, 3, 4,5……直到宏微任务为空。

$nextTick 的实现原理

从字面意思理解,next 下一个,tick 滴答(钟表)来源于定时器的周期性中断(输出脉冲),一次中断表示一个 tick,也被称做一个“时钟滴答”),nextTick 顾名思义就是下一个时钟滴答。看源码,在 Vue 2.x 版本中,nextTicksrc\core\util 中的一个单独的文件 next-tick.js ,可见 nextTick 的重要性,虽然短短 200 多行,尤大却单独创建一个文件去维护。
接下来我们来看整个文件。

  1. 声明了三个全局变量,callbacks: [] ,pending: Boolean,timerFunc: undefined
  2. 声明了一个函数 flushCallbacks
  3. 一堆 **if,else if **判断。
  4. 抛出了一个函数 nextTick

nextTick 函数

  1. 声明一个局部变量 _resolve
  2. 把所有回调函数压进 callbacks 中,以栈的形式的存储所有 callback
  3. pendingfalse 时,执行 timerFunc 函数。
  4. 当没有 callback 的时候,返回一个 Promise 的调用方式,可以用 .then 接收。

timerFunc 函数

我们开始说了,timerFunc 为全局变量,现在调用 timerFunc ,timerFunc 是什么时候被赋值为一个函数,并且函数里执行代码又是什么?

我们看到,这段判断代码总共有四个分支,四个分支里对 timerFunc 有不同的赋值,我们先来看第一个分支。

Promise 分支

if (typeof Promise !== 'undefined' && isNative(Promise)) {
  const p = Promise.resolve()
  timerFunc = () => {
    p.then(flushCallbacks)
    // In problematic UIWebViews, Promise.then doesn't completely break, but
    // it can get stuck in a weird state where callbacks are pushed into the
    // microtask queue but the queue isn't being flushed, until the browser
    // needs to do some other work, e.g. handle a timer. Therefore we can
    // "force" the microtask queue to be flushed by adding an empty timer.
    if (isIOS) setTimeout(noop)
  }
  isUsingMicroTask = true
}
  1. 判断环境是否支持 Promise 并且 Promise 是否为原生。
  2. 使用 Promise 异步调用 flushCallbacks 函数。
  3. 当执行环境是 iPhone 等,使用 setTimeout 异步调用 noopiOS 中在一些异常的webview 中,promise 结束后任务队列并没有刷新所以强制执行 setTimeout 刷新任务队列。

MutationObserver 分支

else if (!isIE && typeof MutationObserver !== 'undefined' && (
  isNative(MutationObserver) ||
  // PhantomJS and iOS 7.x
  MutationObserver.toString() === '[object MutationObserverConstructor]'
)) {
  // Use MutationObserver where native Promise is not available,
  // e.g. PhantomJS, iOS7, Android 4.4
  // (#6466 MutationObserver is unreliable in IE11)
  let counter = 1
  const observer = new MutationObserver(flushCallbacks)
  const textNode = document.createTextNode(String(counter))
  observer.observe(textNode, {
    characterData: true
  })
  timerFunc = () => {
    counter = (counter + 1) % 2
    textNode.data = String(counter)
  }
  isUsingMicroTask = true
}
  1. 对非IE浏览器和是否可以使用 HTML5 新特性 MutationObserver 进行判断。
  2. 实例一个 MutationObserver 对象,这个对象主要是对浏览器 DOM 变化进行监听,当实例化 MutationObserver 对象并且执行对象 observe,设置 DOM 节点发生改变时自动触发回调。
  3. timerFunc 赋值为一个改变 DOM 节点的方法,当 DOM 节点发生改变,触发 flushCallbacks 。(这里其实就是想用利用 MutationObserver 的特性进行异步操作)

setImmediate 分支

else if (typeof setImmediate !== 'undefined' && isNative(setImmediate)) {
  // Fallback to setImmediate.
  // Technically it leverages the (macro) task queue,
  // but it is still a better choice than setTimeout.
  timerFunc = () => {
    setImmediate(flushCallbacks)
  }
}
  1. 判断 setImmediate 是否存在,setImmediate 是高版本 IE (IE10+) 和 edge 才支持的。
  2. 如果存在,传入 flushCallbacks 执行 setImmediate

setTimeout 分支

else {
  // Fallback to setTimeout.
  timerFunc = () => {
    setTimeout(flushCallbacks, 0)
  }
}
  1. 当以上所有分支异步 api 都不支持的时候,使用 macro task (宏任务)的 setTimeout 执行 flushCallbacks

执行降级

我们可以发现,给 timerFunc 赋值是一个降级的过程。为什么呢,因为 Vue 在执行的过程中,执行环境不同,所以要适配环境。

这张图便于我们更清晰的了解到降级的过程。

flushCallbacks 函数

function flushCallbacks () {
  pending = false
  const copies = callbacks.slice(0)
  callbacks.length = 0
  for (let i = 0; i < copies.length; i++) {
    copies[i]()
  }
}

循环遍历,按照 队列 数据结构 “先进先出” 的原则,逐一执行所有 callback

总结

到这里就全部讲完了,nextTick 的原理就是利用 Event loop 事件线程去异步重新渲染,分支判断首要选择 Promise 的原因是当同步JS代码执行完毕,执行栈清空会首先查看 micro task (微任务)队列是否为空,不为空首先执行微任务。在我们 DOM 依赖数据发生变化的时候,会异步重新渲染 DOM ,但是比如像 echartscanvas……这些 Vue 无法在初始状态下收集依赖的 DOM ,我们就需要手动执行 nextTick 方法使其重新渲染。

如何实现浏览器内多个标签页之间的通信?

本题主要考察数据存储的知识,数据存储有本地和服务器存储两种方式。这里主要讲解用本地存储方式解决。即调用 localStorage、Cookie等本地存储方式。

第一种 调用localStorage

在一个标签页里面使用 localStorage.setItem(key,value)添加(修改、删除)内容;
在另一个标签页里面监听 storage 事件。
即可得到 localstorge 存储的值,实现不同标签页之间的通信。

标签页1:

<input id="name">  
<input type="button" id="btn" value="提交">  
<script type="text/javascript">  
    $(function(){    
        $("#btn").click(function(){    
            var name=$("#name").val();    
            localStorage.setItem("name", name);   
        });    
    });    
</script>  

标签页2:

<script type="text/javascript">  
    $(function(){   
        window.addEventListener("storage", function(event){    
            console.log(event.key + "=" + event.newValue);    
        });     
    });  
</script>

第二种 调用cookie+setInterval()

将要传递的信息存储在cookie中,每隔一定时间读取cookie信息,即可随时获取要传递的信息。

页面1:

<input id="name">  
<input type="button" id="btn" value="提交">  
<script type="text/javascript">  
    $(function(){    
        $("#btn").click(function(){    
            var name=$("#name").val();    
            document.cookie="name="+name;    
        });    
    });    
</script>

页面2:

<script type="text/javascript">  
    $(function(){   
        function getCookie(key) {    
            return JSON.parse("{\"" + document.cookie.replace(/;\s+/gim,"\",\"").replace(/=/gim, "\":\"") + "\"}")[key];    
        }     
        setInterval(function(){    
            console.log("name=" + getCookie("name"));    
        }, 10000);    
    });  
</script>

HTML5的文件离线储存怎么使用,工作原理是什么?

HTML5离线存储存储功能非常强大,它的作用是:在用户没有与因特网连接时,可以正常访问站点或应用,在用户与因特网连接时,自动更新缓存数据。

原理:

HTML5的离线存储是基于一个新建的.appcache文件的,通过这个文件上的解析清单离线存储资源,这些资源就会像cookie一样被存储了下来。之后当网络在处于离线状态下时,浏览器会通过被离线存储的数据进行页面展示。

怎么用:

首先,在html页面头部加入一个manifest的属性:

<!DOCTYPE HTML>
<html manifest = "cache.manifest">
...
</html>

然后书写cache.manifest文件:

CACHE MANIFEST
#v0.11

CACHE:
js/app.js
css/style.css

NETWORK:
resourse/logo.png

FALLBACK:
/ /offline.html

manifest (即 .appcache 文件)文件是简单的文本文件,可分为三个部分:

CACHE :

在此标题下列出的文件将在首次下载后进行缓存。(由于包含manifest文件的页面将被自动离线存储,所以不需要把页面自身也列出来)

NETWORK :

在此标题下列出的文件需要与服务器的连接,且不会被缓存,离线时无法使用。 
可以使用 “*” 来指示所有其他资源/文件都需要因特网连接:

NETWORK: *

如果在CACHE和NETWORK中有一个相同的资源,那么这个资源还是会被离线存储,也就是说CACHE的优先级更高。

FALLBACK:

在此标题下列出的文件规定当页面无法访问时的回退页面。比如上面这个文件表示的就是如果访问根目录下任何一个资源失败了,那么就去访问offline.html。

浏览器是怎么对HTML5的离线储存资源进行管理和加载的呢?

在线的情况下,浏览器发现html头部有manifest属性,它会请求manifest文件,如果是第一次访问app,那么浏览器就会根据manifest文件的内容下载相应的资源并且进行离线存储。如果已经访问过app并且资源已经离线存储了,那么浏览器就会使用离线的资源加载页面,然后浏览器会对比新的manifest文件与旧的manifest文件,如果文件没有发生改变,就不做任何操作,如果文件改变了,那么就会重新下载文件中的资源并进行离线存储。
离线的情况下,浏览器就直接使用离线存储的资源。

sessionStorage 、localStorage 和 cookie 之间的区别

共同点

都是保存在浏览器端,且同源的。

区别

  • cookie数据始终在同源的http请求中携带(即使不需要),即cookie在浏览器和服务器间来回传递。而sessionStorage和localStorage不会自动把数据发给服务器,仅在本地保存。cookie数据还有路径(path)的概念,可以限制cookie只属于某个路径下。
  • 存储大小限制也不同,cookie数据不能超过4k,同时因为每次http请求都会携带cookie,所以cookie只适合保存很小的数据,如会话标识。sessionStorage和localStorage 虽然也有存储大小的限制,但比cookie大得多,可以达到5M或更大。
  • 数据有效期不同,sessionStorage:仅在当前浏览器窗口关闭前有效,自然也就不可能持久保持;localStorage:始终有效,窗口或浏览器关闭也一直保存,因此用作持久数据;cookie只在设置的cookie过期时间之前一直有效,即使窗口或浏览器关闭。
  • 作用域不同,sessionStorage不在不同的浏览器窗口中共享,即使是同一个页面;localStorage 在所有同源窗口中都是共享的;cookie也是在所有同源窗口中都是共享的。
  • Web Storage 支持事件通知机制,可以将数据更新的通知发送给监听者。
  • Web Storage 的 api 接口使用更方便。

src 与 href 的区别

src

  • src用于替换当前元素,href用于在当前文档和引用资源之间确立联系。
  • src是source的缩写,指向外部资源的位置,指向的内容将会嵌入到文档中当前标签所在位置;在请求src资源时会将其指向的资源下载并应用到文档内,例如js脚本,img图片和frame等元素
<script src ="js.js"></script>

当浏览器解析到该元素时,会暂停其他资源的下载和处理,直到将该资源加载、编译、执行完毕,图片和框架等元素也如此,类似于将所指向资源嵌入当前标签内。这也是为什么将js脚本放在底部而不是头部

href

  • href是Hypertext Reference的缩写,指向网络资源所在位置,建立和当前元素(锚点)或当前文档(链接)之间的链接,如果我们在文档中添加
<link href="common.css" rel="stylesheet"/>
  • 那么浏览器会识别该文档为css文件,就会并行下载资源并且不会停止对当前文档的处理。这也是为什么建议使用link方式来加载css,而不是使用@import方式

vue3.0 是如何变快的?

diff算法优化

Vue2中的虚拟 dom 是进行全量的对比
Vue3新增了静态标记(PatchFlag)
在与上次虚拟节点进行对比时候,只对比带有 patch flash 的节点
并且可以通过 flag 的信息得知当前节点要对比的具体内容

阅读更多

vue3.0 六大亮点

  1. performance: 性能比vue2.x块1.2~2倍;
  2. Tree shaking support: 按需编译,体积比vue2.x更小;
  3. Composition API: 组合API(类似React Hooks);
  4. Better TypeScript support: 更好的 ts 支持;
  5. Custom Render API: 暴露了自定义渲染的API ;
  6. Fragment, Teleport(Protal): 更先进的组件 ;

修改 hosts

Windows系统

  • 打开目录:C:/Windows/System32/drivers/etc/
  • 找到hosts文件,使用管理员模式打开记事本
  • 将hosts文件拖到管理员模式下的记事本中,然后在文件尾部添加以下文本:
    151.101.0.133 raw.githubusercontent.com
  • 保存文件(可能还要重启一下计算机?),就解决了。

Mac OS 系统

  • 打开终端(Terminal),默认位置在 启动台 –> 其它 –> 终端
  • 在终端中,输入以下命令,使用vim修改hosts文件(会要求输入计算机密码):
    sudo vi /etc/hosts
  • 确保输入法为英文模式,按一下键盘上的 i 键
  • 使用方向键,将光标移动至文本尾部,然后再文件尾部添加以下文本:
    151.101.0.133 raw.githubusercontent.com
  • 按一下esc,然后输入 :wq! ,然后回车
  • 关闭终端,问题解决。

Linux 系统

  • 和Mac下操作基本一致。。。。