首页
归档
关于
友人帐
Search
1
Mac OS 终端利器 iTerm2 + Oh My Zsh
13,926 阅读
2
前端 History 路由及 API 反向代理的 Nginx 配置
10,601 阅读
3
解决 Xcode Command Line Tools 错误或无法安装问题
6,294 阅读
4
Mac下右键使用VSCode打开项目
3,947 阅读
5
BrowserHistory刷新页面404问题
3,439 阅读
码上世界
内容分享
生活印记
其他
登录
Search
小小孩
累计撰写
95
篇文章
累计收到
181
条评论
首页
栏目
码上世界
内容分享
生活印记
其他
页面
归档
关于
友人帐
搜索到
48
篇与
码上世界
的结果
2021-01-12
nginx 判断移动端还是电脑跳转域名或不同的文件
经常会遇到这种需求,根据访问终端的不同,来选择跳转不同的内容。一般区分小屏幕移动端和电脑端而区分访问内容。这有两种办法,一种是根据终端跳转域名,还有一种是根据终端访问的文件不同。下边就说下 nginx 对于这两种办法的配置跳转域名比如 www.domain.com 是 PC 端网址 而 m.domain.com是移动端网址server { listen 80; server_name www.domain.com domain.com; root /data/domain; charset utf-8; if ( $http_user_agent ~* "(Android|iPhone|Windows Phone|UC|Kindle)" ){ # 跳转判断 rewrite ^/(.*)$ http://m.domain.com$uri redirect; # redirect表示302跳转(暂时性转移) } index index.html index.htm; #... }跳转不同的文件server { listen 80; server_name www.domain.com domain.com; root /data/domain; #默认PC端访问内容 charset utf-8; if ( $http_user_agent ~* "(Android|iPhone|Windows Phone|UC|Kindle)" ){ # 跳转判断 root /data/mobile_domain; #移动端访问内容 } index index.html index.htm; #... }
2021年01月12日
1,428 阅读
0 评论
0 点赞
2020-12-06
H5 语音朗读 API
前段时间有同学问我 h5 有没有朗读 api ,一开始他找了 云服务的 api ,其实 h5 本身自带朗读h5 中相关的 API 有两类,一类是“语音识别(Speech Recognition)”,另外一个就是“语音合成(Speech Synthesis)”,这两个名词听上去很高大上其实就是 “语音转文字”,和“文字变语音”,这次我们看看我们要使用的“文字变语音”(SpeechSynthesisUtterance) 。兼容性用之前我先来看看兼容性可以看到 IE 不支持 ,部分移动端不支持,其他的都还好!简单用法先来个最简单的使用例子 读出 "羽叶风雨 Nice"var utterThis = new SpeechSynthesisUtterance("羽叶风雨 Nice"); speechSynthesis.speak(utterThis);浏览器 使用一下,(真~不~错~)。支持属性当然还有其他玩法 ,api 还支持 语言 音量 语速 音高text – 要合成的文字内容,字符串。lang – 使用的语言,字符串, 例如:"zh-cn"或'ja-JP'。voiceURI – 指定希望使用的声音和服务,字符串。volume – 声音的音量,区间范围是 0 到 1,默认是 1。rate – 语速,数值,默认值是 1,范围是 0.1 到 10,表示语速的倍数,例如 2 表示正常语速的两倍。pitch – 表示说话的音高,数值,范围从 0(最小)到 2(最大)。默认值为 1。根据这些属性我来封装成语音函数function speak(textToSpeak) { var u = new SpeechSynthesisUtterance(); u.text = textToSpeak; //汉语 u.lang = "zh-CN"; //速度 u.rate = 1; // 其他属性 speechSynthesis.speak(u); } speak("支付宝到账,100万,元");😄 😁 😆试试别的语言function speak(textToSpeak) { var u = new SpeechSynthesisUtterance(); u.text = textToSpeak; //汉语 u.lang = "ja-JP"; //速度 u.rate = 1; speechSynthesis.speak(u); } speak("雨の降る夜は"); //雨下的夜😛 😝 😜回调方法不仅如此,该实例对象还暴露了一些方法:onstart – 语音开始时触发。onpause – 语音暂停时触发。onresume – 语音重新开始时触发。onend – 语音结束时触发。onboundary - 当语音达到单词或句子边界时触发。onerror - 语音发生错误时触发。onmark - 语音发生错误时触发。示例 在语音读完打印 "真的到账了!!!"function speak(textToSpeak, fun) { var u = new SpeechSynthesisUtterance(); u.text = textToSpeak; //汉语 u.lang = "zh-CN"; //速度 u.rate = 1; // 其他属性 speechSynthesis.speak(u); u.onend = function () { fun && fun(); }; } speak("支付宝到账,100万,元", function () { console.log("真的到账了!!!"); });speechSynthesis我们看到上面例子还是用了 speechSynthesis 对象,它是是语音服务的控制接口;它可以用于获取设备上关于可用的合成声音的信息,开始、暂停语音,或除此之外的其他命令。SpeechSynthesis.cancel() - 移除所有语音谈话队列中的谈话。speechSynthesis.getVoices() - 返回当前设备所有可用声音的 SpeechSynthesisVoice 列表。SpeechSynthesis.pause() - 把 SpeechSynthesis 对象置为暂停状态。SpeechSynthesis.resume() - 把 SpeechSynthesis 对象置为一个非暂停状态:如果已经暂停了则继续。SpeechSynthesis.speak() - 添加一个 utterance 到语音谈话队列;它将会在其他语音谈话播放完之后播放。我来看看我的浏览器支持哪些语音包列表 ,运行 speechSynthesis.getVoices()总结语音API还不太成熟,可以当成彩蛋类来用!比如消息提醒,或者可能为以后无障碍提供帮助!
2020年12月06日
1,272 阅读
0 评论
0 点赞
2020-12-04
css 适配暗黑模式
自从苹果支持暗黑模式后,各个 APP 网站都支持暗黑模式,之前很多都是 js 手动切换模式,其实纯 css 媒体特性也能完全适配prefers-color-schemeprefers-color-scheme 是 CSS 媒体特性用于检测用户是否有将系统的主题色设置为亮色或者暗色。语法no-preference 表示系统未知用户在这方面的选项。light 表示系统选择使用浅色主题的界面。dark 表示系统选择使用暗色主题的界面。例子<div class="day">Day</div> <div class="night">Night</div> @media (prefers-color-scheme: dark) { .day { background: #333; color: white; } .night { background: black; color: #ddd; } } @media (prefers-color-scheme: light) { .day { background: white; color: #555; } .night { background: #eee; color: black; } }实际使用正常模式的下 我们正常写 css 代码 , 如果需要根据系统显示暗黑模式即可添加如下代码... // 正常css 代码 @media (prefers-color-scheme: dark) { ... //暗黑模式css代码; }兼容性好用是好用 ,如果考虑兼容性 就需要注意了(我是不考虑兼容性)
2020年12月04日
1,274 阅读
0 评论
0 点赞
2020-10-31
解决 Xcode Command Line Tools 错误或无法安装问题
问题发现升级 Mac系统后,发现 Xcode Command Line Tools 出现问题了在我构建项目时 出现了No receipt for 'com.apple.pkg.CLTools_Executables' found at '/'.当我运行/usr/sbin/pkgutil --packages | grep CL/usr/sbin/pkgutil --pkg-info com.apple.pkg.CLTools_Executables发现没有任何输出 无语😒 , 可能 Mac 升级把我的包搞丢了 试了下 sudo xcode-select --reset 运行后 并没有正常解决办法办法一 命令行安装命令行运行以下命令, 然后输入root密码来删除Xcode Command Line Toolssudo rm -rf $(xcode-select -print-path)再运行以下来命令重新安装xcode-select --install 重新安装结束后检查是否一切正常正常会如下图所示 如果重装不了 就试试办法二办法二 手动下载安装苹果开发官网(需要登录苹果账号) https://developer.apple.com/download/more/?=command%20line%20tools 会列出所有版本的安装包手动下载Command Line Tools for Xcode 双击安装一步一步安装
2020年10月31日
6,294 阅读
1 评论
1 点赞
2020-10-17
ES2020 新运算符 '??'
ES2020 新的运算符 ??ES2020 新的特性新增了一个运算符 ?? (空值合并运算符)什么是 ??空值合并运算符并不是什么全新的东西。它只是一种获得两者中的第一个不是 null/undefined值的语法。 a ?? b 的结果是:如果 a 是已定义的,则结果为 a,如果 a 不是已定义的,则结果为 b。等于说,如果第一个参数a不是 null/undefined,则 ?? 返回第一个参数a。否则,返回第二个参数b。 翻译一下 就是result = a ?? b; result = (a !== null && a !== undefined) ? a : b;与|| 比较咋一看 有点跟 || 像,空值合并运算符 ?? 是最近才被添加到 JavaScript 中的,它的出现是因为人们对 || 的使用不太满意。例如如下代码const a = 0,b=1; const result1 = a ||b // 1 const result2 = a ||b // 0它们之间重要的区别是:|| 返回第一个 真 值。?? 返回第一个 已定义的 值。在上面代码中a b 是已经定义的值,不存在未定义! || 的语法类似找到第一个值的 Boolean(a) 是 true 的运算付.?? 运算符的优先级?? 运算符的优先级相当低:在 MDN 文档中 中为 5。因此,?? 在 = 和 ? 之前计算,但在大多数其他运算符之后计算。let a = null; let b = null; // 重要:使用括号 let c = (a ?? 100) * (b ?? 50); alert(c); // 5000否则,如果我们省略了括号,则由于 * 的优先级比 ?? 高,它会先执行,进而导致错误的结果。// 没有括号 let c = a ?? 100 * b ?? 50; // 就变成了与下面这行代码的计算方式相同 let c = a ?? (100 * b) ?? 50; // 明显不是我们想要的 已知问题出于安全原因,JavaScript 禁止将?? 与 && 或 || 一起使用,除非使用括号明确指定了优先级。下面的代码会触发一个语法错误:let result = 1 && 2 ?? 3; // Syntax erro 可以明确地使用括号来解决这个问题:let result = (1 && 2) ?? 3; // 2 总结空值合并运算符 ?? 提供了一种从列表中选择第一个已定义的值。?? 运算符的优先级非常低,在表达式中使用它时请添加括号。如果没有明确添加括号,不能将其与 || 或 && 一起使用。
2020年10月17日
862 阅读
0 评论
0 点赞
2020-09-14
JavaScript 中精度问题及简单解决办法
JavaScript 中的数字按照 IEEE 754 的标准,使用 64 位双精度浮点型来表示。其中符号位 S,指数位 E,尾数位M分别占了 1,11,52 位,并且在 ES5 规范 中指出了指数位E的取值范围是 [-1074, 971]。想用有限的位来表示无穷的数字,显然是不可能的,因此会出现一些列精度问题 比如 0.1 + 0.2 !== 0.3解决思路一般是把浮点数转化为字符串,模拟实际运算的过程。以下是 简单的 解决办法// 两个浮点数求和 function fpAdd(num1, num2) { let r1, r2; try { r1 = num1.toString().split('.')[1].length; } catch (e) { r1 = 0; } try { r2 = num2.toString().split(".")[1].length; } catch (e) { r2 = 0; } const m = Math.pow(10, Math.max(r1, r2)); return Math.round(num1 * m + num2 * m) / m; } // 两个浮点数相减 function fpSub(num1, num2) { var r1, r2 try { r1 = num1.toString().split('.')[1].length; } catch (e) { r1 = 0; } try { r2 = num2.toString().split(".")[1].length; } catch (e) { r2 = 0; } const m = Math.pow(10, Math.max(r1, r2)); const n = (r1 >= r2) ? r1 : r2; return (Math.round(num1 * m - num2 * m) / m).toFixed(n); } // 两个浮点数相除 function fpDiv(num1, num2) { var t1, t2 try { t1 = num1.toString().split('.')[1].length; } catch (e) { t1 = 0; } try { t2 = num2.toString().split(".")[1].length; } catch (e) { t2 = 0; } const r1 = Number(num1.toString().replace(".", "")); const r2 = Number(num2.toString().replace(".", "")); return (r1 / r2) * Math.pow(10, t2 - t1); } // 两个浮点数相乘 function fpMul(num1, num2) { const m = 0, s1 = num1.toString(), s2 = num2.toString(); try { m += s1.split(".")[1].length } catch (e) { }; try { m += s2.split(".")[1].length } catch (e) { }; return Number(s1.replace(".", "")) * Number(s2.replace(".", "")) / Math.pow(10, m); }
2020年09月14日
723 阅读
0 评论
0 点赞
2020-08-28
H5 移动端开发经常遇到的问题
搜罗了一些 H5 移动端开发经常遇到的问题,来方便以后查阅!还会继续添加!1. 弹出数字键盘<!-- 有"#" "*"符号输入 --> <input type="tel" /> <!-- 纯数字 --> <input pattern="\d*" />安卓跟 IOS 的表现形式应该不一样,大家可以自己试试。当运用了正则 pattern 后,就不用关注 input 的类型了2. 调用系统的某些功能<!-- 拨号 --> <a href="tel:10086">打电话给: 10086</a> <!-- 发送短信 --> <a href="sms:10086">发短信给: 10086</a> <!-- 发送邮件 --> <a href="mailto:839626987@qq.com">发邮件给:839626987@qq.com</a> <!-- 选择照片或者拍摄照片 --> <input type="file" accept="image/*" /> <!-- 选择视频或者拍摄视频 --> <input type="file" accept="video/*" /> <!-- 多选 --> <input type="file" multiple />3. 打开原生应用<a href="weixin://">打开微信</a> <a href="alipays://">打开支付宝</a> <a href="alipays://platformapi/startapp?saId=10000007" >打开支付宝的扫一扫功能</a > <a href="alipays://platformapi/startapp?appId=60000002">打开支付宝的蚂蚁森林</a>这种方式叫做 URL Scheme,是一种协议,一般用来访问 APP 或者 APP 中的某个功能/页面(如唤醒 APP 后打开指定页面或者使用某些功能)😒URL Scheme 的基本格式如下:行为(应用的某个功能/页面) | scheme://[path][?query] | | 应用标识 功能需要的参数一般是由 APP 开发者自己定义,比如规定一些参数或者路径让其他开发者来访问,就像上面的例子 🍤注意事项唤醒 APP 的条件是你的手机已经安装了该 APP某些浏览器会禁用此协议,比如微信内部浏览器(除非开了白名单)4. 解决 active 伪类失效<body ontouchstart></body>给 body 注册一个空事件即可 😂5. 忽略自动识别<!-- 忽略浏览器自动识别数字为电话号码 --> <meta name="format-detection" content="telephone=no" /> <!-- 忽略浏览器自动识别邮箱账号 --> <meta name="format-detection" content="email=no" />当页面上的内容包含了手机号/邮箱等,会自动转换成可点击的链接 😁比如你有如下代码:但是有些浏览器会识别为手机,并且可以点击拨号。6. 解决 input 失焦后页面没有回弹一般出现在 IOS 设备中的微信内部浏览器,出现的条件为:页面高度过小聚焦时,页面需要往上移动的时候所以一般 input 在页面上方或者顶部都不会出现无法回弹 🤣解决办法为,在聚焦时,获取当前滚动条高度,然后失焦时,赋值之前获取的高度:<template> <input type="text" @focus="focus" @blur="blur" /> </template> <script> export default { data() { return { scrollTop: 0 }; }, methods: { focus() { this.scrollTop = document.scrollingElement.scrollTop; }, blur() { document.scrollingElement.scrollTo(0, this.scrollTop); } } }; </script>6. 禁止长按以上行为可以总结成这几个(每个手机以及浏览器的表现形式不一样):长按图片保存、长按选择文字、长按链接/手机号/邮箱时呼出菜单。想要禁止这些浏览器的默认行为,可以使用以下 CSS:// 禁止长按图片保存 img { -webkit-touch-callout: none; pointer-events: none; // 像微信浏览器还是无法禁止,加上这行样式即可 } // 禁止长按选择文字 div { -webkit-user-select: none; } // 禁止长按呼出菜单 div { -webkit-touch-callout: none; }7. 滑动不顺畅,粘手一般出现在 IOS 设备中,自定义盒子使用了 overflow: auto || scroll 后出现的情况。优化代码:div { -webkit-overflow-scrolling: touch; }8. 屏幕旋转为横屏时,字体大小会变具体出现的情况不明 😒,有时候有有时候没有,欢迎指出。优化代码:* { -webkit-text-size-adjust: 100%; }9. 最简单的 rem 自适应大家都知道,rem 的值是根据根元素的字体大小相对计算的,但是我们每个设备的大小不一样,所以根元素的字体大小要动态设置 😂html { font-size: calc(100vw / 3.75); } body { font-size: .14rem; }这是最简单的,稍微复杂的可以使用我正在使用的 rem.js10. 滑动穿透当你想在出现遮罩的时候,锁住用户的滚动行为,你可以这么做。假设 HTML 结构如下: 我是弹框 CSS 样式如下:.mask { position: fixed; top: 0; left: 0; display: flex; justify-content: center; align-items: center; width: 100%; height: 100%; background-color: rgba($color: #333, $alpha: 0.6); .content { padding: 20px; background-color: #fff; width: 300px; } }可以看到,当在遮罩上滑动的时候,是会穿透到父节点的,最简单的办法就是阻住默认行为:document.querySelector(".mask").addEventListener("touchmove", event => { event.preventDefault(); });如果在 vue 中,你可以这么写:<div class="mask" @touchumove.prevent></div> 如果.content 也有滚动条,那么只要阻止遮罩本身就行:document.querySelector(".mask").addEventListener("touchmove", event => { if (event.target.classList.contains("mask")) event.preventDefault(); });或者:<div class="mask" @touchumove.self.prevent></div> 这样,当出现遮罩的时候用户的滑动就会被锁住啦 👌
2020年08月28日
704 阅读
0 评论
1 点赞
2020-07-16
前端 History 路由及 API 反向代理的 Nginx 配置
underscores_in_headers on; location ~ ^/prod-api { rewrite ^/prod-api/(.*)$ /$1 break; proxy_pass http://*****.com; } location ^/file { proxy_pass http://*****.com; } location ~ / { try_files $uri $uri/ /index.html; } #if (!-e $request_filename) { # rewrite ^/(.*) /index.html last; # break; #}
2020年07月16日
10,601 阅读
0 评论
1 点赞
2020-05-01
Vue3 中的 Proxy API
Object.defineProperty 的一些弊端Vue2.x 中,实现数据的可响应,需要对 Object 和 Array 两种类型采用不同的处理方式。 Object 类型通过 Object.defineProperty 将属性转换成 getter/setter ,这个过程需要递归侦测所有的对象 key,来实现深度的侦测。为了感知 Array 的变化,对 Array 原型上几个改变数组自身的内容的方法做了拦截,虽然实现了对数组的可响应,但也存在一些问题。 同时,defineProperty 通过递归实现 getter/setter 也有一定的性能问题。更好的实现方式是通过 ES6 提供的 Proxy 。Proxy 的一些坑Proxy 具有更加强大的功能, 相比旧的 defineProperty ,Proxy 可以代理数组,并且提供了多个 traps(主要是 get 、 set ) ,可以实现诸多功能。但其中的一些比较容易被忽略的细节。trap 默认行为let data = { info: "info" }; let p = new Proxy(data, { get(target, key, receiver) { return target[key]; }, set(target, key, value, receiver) { console.log("set value"); target[key] = value; // ? } }); p.info = 123;通过 proxy 返回的对象 p 代理了对原始数据的操作,当对 p 设置时,便可以侦测到变化。但是这么写实际上是有问题, 当代理的对象数据是数组时,就会报错。let data = [1, 2]; let p = new Proxy(data, { get(target, key, receiver) { return target[key]; }, set(target, key, value, receiver) { console.log("set value"); target[key] = value; } }); p.push(3); // 报错将代码更改为:let data = [1, 2]; let p = new Proxy(data, { get(target, key, receiver) { return target[key]; }, set(target, key, value, receiver) { console.log("set value"); target[key] = value; return true; } }); p.push(3); // set value 打印 2 次实际上,当代理对象是数组,通过 push 操作,并不只是操作当前数据,push 操作还触发数组本身其他属性更改。let data = [1, 2]; let p = new Proxy(data, { get(target, key, receiver) { console.log("get value:", key); return target[key]; }, set(target, key, value, receiver) { console.log("set value:", key, value); target[key] = value; return true; } }); p.push(3); // get value: push // get value: length // set value: 2 3 // set value: length 3先看 set 操作,从打印输出可以看出,push 操作除了给数组的第 2 位下标设置值 3 ,还给数组的 length 值更改为 3。 同时这个操作还触发了 get 去获取 push 和 length 两个属性。我们可以通过 Reflect 来返回 trap 相应的默认行为,对于 set 操作相对简单,但是一些比较复杂的默认行为处理起来相对繁琐得多,Reflect 的作用就显现出来了。let data = [1, 2]; let p = new Proxy(data, { get(target, key, receiver) { console.log("get value:", key); return Reflect.get(target, key, receiver); }, set(target, key, value, receiver) { console.log("set value:", key, value); return Reflect.set(target, key, value, receiver); } }); p.push(3); // get value: push // get value: length // set value: 2 3 // set value: length 3相比自己处理 set 的默认行为,Reflect 就方便得多。多次触发 set / get当代理对象是数组时,push 操作会触发多次 set 执行,同时,也引发 get 操作,这点非常重要,vue3 就很好的使用了这点。 我们可以从另一个例子来看这个操作:let data = [1, 2, 3]; let p = new Proxy(data, { get(target, key, receiver) { console.log("get value:", key); return Reflect.get(target, key, receiver); }, set(target, key, value, receiver) { console.log("set value:", key, value); return Reflect.set(target, key, value, receiver); } }); p.unshift("a"); // get value: unshift // get value: length // get value: 2 // set value: 3 3 // get value: 1 // set value: 2 2 // get value: 0 // set value: 1 1 // set value: 0 a // set value: length 4可以看到,在对数组做 unshift 操作时,会多次触发 get 和 set 。 仔细观察输出,不难看出,get 先拿数组最末位下标,开辟新的下标 3 存放原有的末位数值,然后再将原数值都往后挪,将 0 下标设置为了 unshift 的值 a ,由此引发了多次 set 操作。而这对于 通知外部操作 显然是不利,我们假设 set 中的 console 是触发外界渲染的 render 函数,那么这个 unshift 操作会引发 多次 render 。我们后面会讲述如何解决相应的这个问题,继续。proxy 只能代理一层let data = { foo: "foo", bar: { key: 1 }, ary: ["a", "b"] }; let p = new Proxy(data, { get(target, key, receiver) { console.log("get value:", key); return Reflect.get(target, key, receiver); }, set(target, key, value, receiver) { console.log("set value:", key, value); return Reflect.set(target, key, value, receiver); } }); p.bar.key = 2; // get value: bar执行代码,可以看到并没有触发 set 的输出,反而是触发了 get ,因为 set 的过程中访问了 bar 这个属性。 由此可见,proxy 代理的对象只能代理到第一层,而对象内部的深度侦测,是需要开发者自己实现的。同样的,对于对象内部的数组也是一样。p.ary.push("c"); // get value: ary同样只走了 get 操作,set 并不能感知到。我们注意到 get/set 还有一个参数:receiver ,对于 receiver ,其实接收的是一个代理对象:let data = { a: { b: { c: 1 } } }; let p = new Proxy(data, { get(target, key, receiver) { console.log(receiver); const res = Reflect.get(target, key, receiver); return res; }, set(target, key, value, receiver) { return Reflect.set(target, key, value, receiver); } }); // Proxy {a: {…}}这里 receiver 输出的是当前代理对象,注意,这是一个已经代理后的对象。let data = { a: { b: { c: 1 } } }; let p = new Proxy(data, { get(target, key, receiver) { const res = Reflect.get(target, key, receiver); console.log(res); return res; }, set(target, key, value, receiver) { return Reflect.set(target, key, value, receiver); } }); // {b: {c: 1} }当我们尝试输出 Reflect.get 返回的值,会发现,当代理的对象是多层结构时,Reflect.get 会返回对象的内层结构。Vue3 如何解决 proxy 中的细节问题Vue3 项目结构采用了 lerna 做 monorepo 风格的代码管理,目前比较多的开源项目切换到了 monorepo 的模式, 比较显著的特征是项目中会有个 packages/ 的文件夹。Vue3 对功能做了很好的模块划分,同时使用 TS 。我们直接在 packages 中找到响应式数据的模块:Vue3 中的 reactivity其中,reactive.ts 文件提供了 reactive 函数,该函数是实现响应式的核心。 同时这个函数也挂载在了全局的 Vue 对象上。这里对源代码做一点程度的简化:const rawToReactive = new WeakMap(); const reactiveToRaw = new WeakMap(); // utils function isObject(val) { return typeof val === "object"; } function hasOwn(val, key) { const hasOwnProperty = Object.prototype.hasOwnProperty; return hasOwnProperty.call(val, key); } // traps function createGetter() { return function get(target, key, receiver) { const res = Reflect.get(target, key, receiver); return isObject(res) ? reactive(res) : res; }; } function set(target, key, val, receiver) { const hadKey = hasOwn(target, key); val = reactiveToRaw.get(val) || val; const result = Reflect.set(target, key, val, receiver); const oldValue = target[key]; if (!hadKey) { console.log("trigger ..."); } else if (val !== oldValue) { console.log("trigger ..."); } return result; } // handler const mutableHandlers = { get: createGetter(), set: set }; // entry function reactive(target) { return createReactiveObject( target, rawToReactive, reactiveToRaw, mutableHandlers ); } function createReactiveObject(target, toProxy, toRaw, baseHandlers) { let observed = toProxy.get(target); // 原数据已经有相应的可响应数据, 返回可响应数据 if (observed !== void 0) { return observed; } // 原数据已经是可响应数据 if (toRaw.has(target)) { return target; } observed = new Proxy(target, baseHandlers); toProxy.set(target, observed); toRaw.set(observed, target); return observed; }rawToReactive 和 reactiveToRaw 是两个弱引用的 Map 结构,这两个 Map 用来保存 原始数据 和 可响应数据 ,在函数 createReactiveObject 中,toProxy和 toRaw 传入的便是这两个 Map 。我们可以通过它们,找到任何代理过的数据是否存在,以及通过代理数据找到原始的数据。除了保存了代理的数据和原始数据,createReactiveObject 函数仅仅是返回了 new Proxy 代理后的对象。 重点在 new Proxy中传入的 handler 参数 baseHandlers。还记得前面提到的 Proxy 实现数据侦测的细节问题吧,我们尝试输入:let data = { foo: "foo", ary: [1, 2] }; let r = reactive(data); r.ary.push(3);打印结果:可以看到打印输出了一次 trigger ...问题一:如何做到深度的侦测数据的 ?深度侦测数据是通过 createGetter 函数实现的,前面提到,当对多层级的对象操作时,set 并不能感知到,但是 get 会触发, 于此同时,利用 Reflect.get() 返回的“多层级对象中内层” ,再对“内层数据”做一次代理。function createGetter() { return function get(target, key, receiver) { const res = Reflect.get(target, key, receiver); return isObject(res) ? reactive(res) : res; }; }可以看到这里判断了 Reflect 返回的数据是否还是对象,如果是对象,则再走一次 proxy,从而获得了对对象内部的侦测。并且,每一次的 proxy 数据,都会保存在 Map 中,访问时会直接从中查找,从而提高性能。当我们打印代理后的对象时:可以看到这个代理后的对象内层并没有代理的标志,这里仅仅是代理外层对象。输出其中一个存储代理数据的 rawToReactiv :对于内层 ary: [1, 2] 的代理,已经被存储在了 rawToReactive 中。由此实现了深度的数据侦测。问题二:如何避免多次 trigger ?function hasOwn(val, key) { const hasOwnProperty = Object.prototype.hasOwnProperty; return hasOwnProperty.call(val, key); } function set(target, key, val, receiver) { console.log(target, key, val); const hadKey = hasOwn(target, key); val = reactiveToRaw.get(val) || val; const result = Reflect.set(target, key, val, receiver); const oldValue = target[key]; if (!hadKey) { console.log("trigger ... is a add OperationType"); } else if (val !== oldValue) { console.log("trigger ... is a set OperationType"); } return result; }关于多次trigger 的问题,vue 处理得很巧妙。在 set 函数中 hasOwn 前打印 console.log(target, key, val) 。let data = ["a", "b"]; let r = reactive(data); r.push("c");r.push('c') 会触发 set 执行两次,一次是值本身 'c' ,一次是 length 属性设置设置值 'c' 时,传入的新增索引 key 为 2,target 是原始的代理对象 ['a', 'c'] ,hasOwn(target, key) 显然返回 false ,这是一个新增的操作,此时可以执行 trigger ... is a add OperationType当传入 key 为 length 时,hasOwn(target, key) ,length 是自身属性,返回 true,此时判断 val !== oldValue, val 是 3, 而 oldValue 即为 target['length'] 也是 3,此时不执行 trigger 输出语句。所以通过 判断 key 是否为 target 自身属性,以及设置 val 是否跟 target[key]相等 可以确定 trigger 的类型,并且避免多余的 trigger总结Vue3 并非简单的通过 Proxy 来递归侦测数据, 而是通过 get 操作来实现内部数据的代理,并且结合 WeakMap 来对数据保存,这将大大提高响应式数据的性能。
2020年05月01日
783 阅读
0 评论
0 点赞
2020-04-01
腾讯Ubuntu云服务器环境初始配置
一、配置 root 登陆腾讯 Ubuntu 云服务器默认用户ubuntu ,又懒得 每次都输 sudo ,所以加上 root 用户1. 设置 root 密码先使用 ubuntu 用户 ssh 登录腾讯云,然后执行命令sudo passwd root2. 修改 ssh 登录的配置/etc/ssh/sshd_config文件,修改为允许 root 登录,可以执行命令sudo vim /etc/ssh/sshd_config注意:这里的 sudo 前缀不可少,否则接下来的修改无法保存。进入 vim 编辑,用方向键向下滚动找到 PermitRootLogin 这项按下 insert 键进入插入模式,将 PermitRootLogin 后面的 prohibit-password 改为 yes,再按下 Esc 键,然后依次按下:键(英文冒号键)、w 键和 q 键,最后按下回车键,保存修改成功。3. 重启 ssh 服务sudo service ssh restart使刚才的 ssh 配置的修改生效,执行命令使用 root 用户登录使用root用户登录 必要的话 可以删除ubuntu 用户删除命令:userdel -r ubuntu二、安装 docker 及 docker-composedocker最方便的方法是使用官方脚本并使用阿里云镜像安装curl -fsSL https://get.docker.com | bash -s docker --mirror Aliyun如果您想将 Docker 用作非 root 用户,您现在应该考虑将您的用户添加到“docker”组,例如:可以自行添加用户 (尽量避免使用docker作为用户名)useradd your-usersudo usermod -aG docker your-user请记得注销并重新登录才能生效!docker-compose1. 运行脚本sudo curl -L "https://github.com/docker/compose/releases/download/1.24.0/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose其中 1.24.0 可以切换你想安装的版本2. 对二进制文件应用可执行权限:sudo chmod +x /usr/local/bin/docker-compose注意:如果 docker-compose 安装后命令失败,请检查您的路径。您还可以创建/usr/bin 路径中的符号链接或任何其他目录。 例如:sudo ln -s /usr/local/bin/docker-compose /usr/bin/docker-compose3. 测试安装。docker-compose --version4. 卸载:sudo rm /usr/local/bin/docker-compose三、安装 Nginx因为是ubuntu 系统 不像 centos ,ubuntu 的包都比较新 所以直接用包管理器安装apt-get update apt-get install nginx完成之后 nginx -v 打印出版本号就说明安装成功了四、安装 Node.js因为每个项目可能依赖的 Node 版本不同 这里选用了 nvm 来作为 node 的包管理器 ,它可以方便的在同一台设备上进行多个 node 版本之间切换curl -o- https://raw.githubusercontent.com/creationix/nvm/v0.34.0/install.sh | bash注意:在 Linux 上,运行安装脚本后,如果 nvm: command not found 在键入后收到或看不到终端的反馈:command -v nvm只需关闭当前终端,打开新终端,然后再次尝试 nvm -v验证。验证成功后就可以安装node.js了nvm 用法要下载,编译和安装最新版本的节点,请执行以下操作:nvm install node # node是最新版本的别名要安装特定版本的节点:nvm install 6.14.4 # 10.10.0,8.9.1 等您可以使用 ls-remote 列出可用版本:nvm ls-remote然后在任何新的 shell 中只使用已安装的版本:五、安装 MongoDB既然我们安装了 docker 我们就用 docker 安装 MongoDB1. 拉取镜像 docker pull mongo # 直接拉去默认tag 为latest 的mongo:latest 镜像2. 使用 mongo 镜像docker run --name mongo -p 27017:27017 -v /mongo/db:/data/db -d mongo命令说明:--name 命名容器名字-p 27017:27017 :将容器的 27017 端口映射到主机的 27017 端口-v /mongo/db:/data/db :将主机中/mongo/db 挂载到容器的/data/db,作为 mongo 数据存储目录3. 查看容器启动情况docker ps可看到 已经运行成功了使用 mongo 镜像执行 mongo 命令连接到刚启动的容器docker run -it mongo:latest mongo六、安装 MongoDB跟安装 MongoDB 一样docker pull mysql:5.6 mkdir -p /mysql/data /mysql/logs /mysql/conf docker run -p 3306:3306 --name mymysql \ -v /mysql/conf:/etc/mysql/conf.d \ -v /mysql/logs:/logs \ -v /mysql/data:/var/lib/mysql \ -e MYSQL_ROOT_PASSWORD=123456 \ -d mysql:5.6命令说明:-p 3306:3306 #将容器的 3306 端口映射到主机的 3306 端口。-v /mysql/conf:/etc/mysql/conf.d #将主机当前目录下的 conf/my.cnf 挂载到容器的 /etc/mysql/my.cnf。-v /mysql/logs:/logs #将主机当前目录下的 logs 目录挂载到容器的 /logs。-v /mysql/data:/var/lib/mysql #将主机当前目录下的 data 目录挂载到容器的 /var/lib/mysql 。-e MYSQL_ROOT_PASSWORD=123456 #初始化 root 用户的密码。查看容器启动情况docker ps 注意 : mysql 5.7 及以上版本 映射的配置文件目录可能有所不同 详细可取 Docker Hub 查看
2020年04月01日
520 阅读
0 评论
0 点赞
1
2
3
4
5