首页
归档
关于
友人帐
Search
1
Mac OS 终端利器 iTerm2 + Oh My Zsh
12,063 阅读
2
前端 History 路由及 API 反向代理的 Nginx 配置
9,912 阅读
3
解决 Xcode Command Line Tools 错误或无法安装问题
3,975 阅读
4
Mac下右键使用VSCode打开项目
3,283 阅读
5
Nodejs 爬取one和墨迹天气定时发邮件
2,968 阅读
码上世界
内容分享
生活印记
其他
登录
Search
羽叶
累计撰写
82
篇文章
累计收到
158
条评论
首页
栏目
码上世界
内容分享
生活印记
其他
页面
归档
关于
友人帐
搜索到
36
篇与
码上世界
的结果
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日
716 阅读
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日
3,975 阅读
1 评论
0 点赞
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日
598 阅读
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日
559 阅读
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日
601 阅读
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日
9,912 阅读
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日
543 阅读
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日
400 阅读
0 评论
0 点赞
2020-03-25
JS判断是否是真实移动设备
要想判断真实的移动设备打开,而不是电脑模拟的移动设备打开,肯定不能通过userAgent判断,因为电脑开发工具可修改。 想到了Navigator的只读属性maxTouchPoints是返回当前设备支持的最大同时触摸接触点数。 一般电脑没有触摸接触点数,而电脑模拟的移动设备也只有一个,而真实的移动设备却是 3-4个,以此来判断是否是真实的移动设备。直接上代码const checkPhone = () => navigator.platform.indexOf('Mac') === navigator.platform.indexOf('Win') && navigator.maxTouchPoints && 2 < navigator.maxTouchPoints
2020年03月25日
1,656 阅读
4 评论
0 点赞
2019-11-11
从一个文字渐变引发的一系列问题
前言事件的起因是朋友给我发的一个微信当时就奇怪字体跟数据绑定有啥关系,处于好奇我让他写个 demo 给我看看 ,然后他发来一个代码文件代码精简如下<!DOCTYPE html> <html> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta http-equiv="X-UA-Compatible" content="ie=edge" /> <title>Demo</title> <style> body { font-size: 40px; } .time1 { display: inline-block; background: linear-gradient(to right, #a6ffcb, #1fa2ff); -webkit-background-clip: text; color: transparent; } </style> </head> <body> <div id="app"> <div class="time1">{{ time }}</div> </div> <script src="https://cdn.jsdelivr.net/npm/vue@2.6.10/dist/vue.min.js"></script> <script> new Vue({ el: "#app", data: { time: new Date().toLocaleString() }, methods: { updateTime() { this.time = new Date().toLocaleString(); } }, mounted() { setInterval(this.updateTime, 1000); } }); </script> </body> </html>发现在浏览器下时间并不会改变 ,当我在控制台把颜色注释掉后,发现数据是改变的,这就奇怪了呀! 这里我就试了下 另一种渐变样式写法知识拓展:css 文字渐变一些方法background 属性这种我不细说了 也很好理解 可以看上面代码实现方式mask 属性<style> .time2 { position: relative; color: #a6ffcb; } .time2:before { content: attr(time); position: absolute; z-index: 10; color: #1fa2ff; -webkit-mask: linear-gradient(to left, #1fa2ff, transparent); } </style> <div class="time2" time="time">我是渐变文字</div> :before 选择器向选定的元素前插入内容。使用 content 属性来指定要插入的内容。mask 属性让元素的某一部分显示或隐藏第一个:content 取值 attr 就是用来获取属性值的,content:attr(属性名)content: attr(time); 能获取到元素的 time 属性,这里的这个 time 属性是自己自定义的一个属性,随便写<h1 date="前端简单说">前端简单说</h1>然后content属性 这样写,content: attr(date); 同样是可以起作用的。第二个:mask 属性 允许使用者通过部分或者完全隐藏一个元素的可见区域。这种效果可以通过遮罩或者裁切特定区域的图片。详情可看https://developer.mozilla.org/zh-CN/docs/Web/CSS/mask回归问题我们试试另一种情况会不会出现不渲染情况<!DOCTYPE html> <html> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta http-equiv="X-UA-Compatible" content="ie=edge" /> <title>Demo</title> <style> body { font-size: 40px; } .time1 { display: inline-block; } .time1 { background: linear-gradient(to right, #a6ffcb, #1fa2ff); -webkit-background-clip: text; color: transparent; } .time2 { position: relative; color: #a6ffcb; } .time2:before { content: attr(time); position: absolute; z-index: 10; color: #1fa2ff; -webkit-mask: linear-gradient(to left, #1fa2ff, transparent); } </style> </head> <body> <div id="app"> <div class="time1">{{ time }}</div> <div class="time2" :time="time">{{ time }}</div> </div> <script src="https://cdn.jsdelivr.net/npm/vue@2.6.10/dist/vue.min.js"></script> <script> new Vue({ el: "#app", data: { time: new Date().toLocaleString() }, methods: { updateTime() { this.time = new Date().toLocaleString(); } }, mounted() { setInterval(this.updateTime, 1000); } }); </script> </body> </html>看下效果我当时的表情我想到了是不是浏览器重排和重绘的问题知识拓展:浏览器重排和重绘我们了解下重排和重绘浏览器编译页面分为 5 步处理 html 生成 DOM(Document Object Model) Tree处理 css 生成 CSSOM(CSS Object Model) TreeDOM 树与 CSS-DOM 树合并为 Render 树对 Render 树进行布局计算遍历 Render 树的每一个节点绘制到屏幕重绘与重排概念当 DOM 变化影响了元素的几何属性(宽、高改变等等),浏览器此时需要重新计算元素几何属性,并且页面中其他元素的几何属性可能会受影响,这样渲染树就发生了改变,也就是重新构造 RenderTree 渲染树,这个过程叫做重排(reflow)如果 DOM 变化仅仅影响的了背景色等等非几何属性,此时就发生了重绘(repaint)而不是重排,因为布局没有发生改变页面布局和元素几何属性的改变就会导致重排下列情况会发生重排:页面初始渲染添加/删除可见 DOM 元素改变元素位置改变元素尺寸(宽、高、内外边距、边框等)改变元素内容(文本或图片等)改变窗口尺寸不同的条件下发生重排的范围及程度会不同某些情况甚至会重排整个页面,比如滑动滚动条以下属性或方法会刷新渲染队列(offsetTop、offsetLeft、offsetWidth、offsetHeightclientTop、clientLeft、clientWidth、clientHeightscrollTop、scrollLeft、scrollWidth、scrollHeightgetComputedStyle()(IE 中 currentStyle))再次回归问题这边时间文字改变了,但文字大小没变,我感觉按道理应该没有触发重排从而使背景色没有改变 ,那我试试改变文字大长度试试效果如下发现确实,文字长度没变,第一个不会重新渲染,长度一旦发生改变,第一个才会改变渲染,而第二个一直在渲染问题好像突然找到原因了,但我又无意发现新的问题新的问题<!DOCTYPE html> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta http-equiv="X-UA-Compatible" content="ie=edge" /> <title>Demo</title> <style> body { font-size: 40px; } .time1 { display: inline-block; } .time1, .time3 { background: linear-gradient(to right, #a6ffcb, #1fa2ff); -webkit-background-clip: text; color: transparent; } .time2 { position: relative; color: #a6ffcb; } .time2:before { content: attr(time); position: absolute; z-index: 10; color: #1fa2ff; -webkit-mask: linear-gradient(to left, #1fa2ff, transparent); } </style> </head> <body> <div id="app"> <div class="time1">{{ time }}</div> <div class="time2" :time="time">{{ time }}</div> <div> <span class="time3">{{ time }}</span> </div> </div> <script src="https://cdn.jsdelivr.net/npm/vue@2.6.10/dist/vue.min.js"></script> <script> new Vue({ el: "#app", data: { msg: "Hello", time: new Date().toLocaleString() }, methods: { updateTime() { this.time = new Date().toLocaleString(); } }, mounted() { setInterval(this.updateTime, 1000); } }); </script> </body> </html>当我改变 dom 结构发现又好了效果如下好了 我彻底呆了最后最后我也没搞明白,不过为了保险起见以后类似这种还是用第二种渐变样式吧!
2019年11月11日
1,067 阅读
0 评论
0 点赞
1
2
3
4