- 「ES2015 - ES2018」Rest / Spread Properties 梳理
- Three.js 概念梳理
- 火狐 Nightly 支持 CSS Grids 动画
- 换个姿势起 Node.js 工程
- WebView & JS Bridge 笔记
「ES2015 - ES2018」Rest / Spread Properties 梳理
第 9 版 ECMAScript 标准,官方称为 ECMAScript 2018(或简称 ES2018),已于 2018 年 6 月发布。从 ES2016 开始,ECMAScript 规范每年都会发布新版本,但添加的功能少于以前的主版本。最新版本发布了 4 个新的 RegExp、Rest / Spread 属性、Asynchronous Iteration、Promise.prototype.finally。此外,ES2018 从标记模板中删除了转义序列的语法限制。
这里我们对 Rest / Spread Properties 进行梳理。
Spread
在 ES2015 中一个非常有趣的特性就是 spread operator(扩展操作符)。这一操作符是的复制和合并数组变得更为简洁。我们不再需要使用 concat()
方法或 slice()
方法,一个 ...
操作符已经足够:
const arr1 = [10, 20, 30];
// make a copy of arr1
const copy = [...arr1];
console.log(copy); // → [10, 20, 30]
const arr2 = [40, 50];
// merge arr2 with arr1
const merge = [...arr1, ...arr2];
console.log(merge); // → [10, 20, 30, 40, 50]
不仅如此,我们还可以将数组作为一个需要单独一个个传入参数的函数的参数传入,这么说可能有点绕,看个例子就懂了:
const arr = [10, 20, 30]
// equivalent to
// console.log(Math.max(10, 20, 30));
console.log(Math.max(...arr)); // → 30
在 ES2018 中,这一语法糖进一步扩展,对于对象属性展开复制的场景也可以发挥作用。事实上这一特性从比较早的时候,我们就开始应用在了项目中:
const obj1 = {
a: 10,
b: 20
};
const obj2 = {
...obj1,
c: 30
};
console.log(obj2); // → {a: 10, b: 20, c: 30}
在上面的代码中,...
操作符用于获取 obj1 的属性,并赋值给 obj2。在 ES2018 之前,这么做是会抛出异常的。如果存在若干相同 key 的属性,会选择后赋值或生成的属性:
const obj1 = {
a: 10,
b: 20
};
const obj2 = {
...obj1,
a: 30
};
console.log(obj2); // → {a: 30, b: 20}
当然,我们还可以使用扩展属性来合并两个甚至更多对象,当然我们也可用通过 Object.assign()
来实现:
const obj1 = {a: 10};
const obj2 = {b: 20};
const obj3 = {c: 30};
// ES2018
console.log({...obj1, ...obj2, ...obj3}); // → {a: 10, b: 20, c: 30}
// ES2015
console.log(Object.assign({}, obj1, obj2, obj3)); // → {a: 10, b: 20, c: 30}
不过有意思的是,扩展属性并不总能生成与 Object.assign()
相同的结果,我们看下下面的场景:
Object.defineProperty(Object.prototype, 'a', {
set(value) {
console.log('set called!');
}
});
const obj = {a: 10};
console.log({...obj});
// → {a: 10}
console.log(Object.assign({}, obj));
// → set called!
// → {}
Object.assign()
会执行集成的 setter 属性,而扩展运算会忽略 setter。
值得注意的是,扩展属性只会复制可枚举属性。下面的例子中,type
属性不会出现在复制的对象中,因为它的 enumerable
属性被置为 false。
const car = {
color: 'blue'
};
Object.defineProperty(car, 'type', {
value: 'coupe',
enumerable: false
});
console.log({...car}); // → {color: "blue"}
当然,即使是 enumerable 的属性,继承过来的仍然会被忽略:
const car = {
color: 'blue'
};
const car2 = Object.create(car, {
type: {
value: 'coupe',
enumerable: true,
}
});
console.log(car2.color); // → blue
console.log(car2.hasOwnProperty('color')); // → false
console.log(car2.type); // → coupe
console.log(car2.hasOwnProperty('type')); // → true
console.log({...car2}); // → {type: "coupe"}
上面的代码中,car2 从 car1 继承了 color 属性。因为扩展运算只复制对象本身的属性,color 就不含在返回的值中了。
要记住的一点是,扩展运算做的是对象的浅拷贝。如果一个属性本身也是对象,那么扩展运算只会复制这个对象的引用:
const obj = {x: {y: 10}};
const copy1 = {...obj};
const copy2 = {...obj};
console.log(copy1.x === copy2.x); // → true
copy1 中的 x 属性与 copy2 的 x 属性在内存中引用的是相同的对象,因此「严格等于」运算符返回的是 true。
Rest
ES2015 中引入的另一个有用的特性是 rest parameters,它可以使得我们使用 ...
来把一些值作为数组返回。举个例子就明白了:
const arr = [10, 20, 30];
const [x, ...rest] = arr;
console.log(x); // → 10
console.log(rest); // → [20, 30]
这里,arr 的第一个项赋值给了 x,而剩下的项被复制给了 rest。这个模式我们称之为 array destructuring,中文翻译为「数组解构」,这个模式在 Ecma 技术委员会决定将其带到对象中后变得非常的流行:
const obj = {
a: 10,
b: 20,
c: 30
};
const {a, ...rest} = obj;
console.log(a); // → 10
console.log(rest); // → {b: 20, c: 30}
在这段代码中,通过结构的方式,我们复制了可枚举属性并复制到新的对象中。注意,rest 属性总是出现在对象的最后,否则就会抛出异常:
const obj = {
a: 10,
b: 20,
c: 30
};
const {...rest, a} = obj; // → SyntaxError: Rest element must be last element
同样需要注意的是,如果在同一个对象中使用多个 rest,也会导致错误,除非这些 rest 是嵌套的形式:
const obj = {
a: 10,
b: {
x: 20,
y: 30,
z: 40
}
};
const {b: {x, ...rest1}, ...rest2} = obj; // no error
const {...rest, ...rest2} = obj; // → SyntaxError: Rest element must be last element
兼容性
60 | 55 | 11.1 | No |
60 | 55 | 11.3 | No | 8.2 | 60 |
对应 Node.js,兼容性如下:
- 8.0.0 (需要 --harmony 运行时 flag)
- 8.3.0 (完全支持)
源地址:https://css-tricks.com/new-es...
Three.js 概念梳理
Three.js 是一款运行在浏览器中的 3D 引擎,我们可以用它创建各种三维场景。今天我们来对这一块进行一些初步的学习和梳理。
兼容性
Three.js 基于 WebGL 进行封装,因此其兼容性由 WebGL 在不同浏览器的兼容性决定。其中,WebGL 的兼容性如下:
可以看出,WebGL 的兼容性还是不错的。
然而 WebGL 2 的兼容性就不那么好了,
可以看到,Safari 对 WebGL 2 的支持还不是很好,而知名的 TensorFlow.js 的浏览器版本就是基于 WebGL 2,因此 TensorFlow.js 在 Safari 上就有些无力了。
小工具
在了解的过程中发现了一个不错的工具,可以查看当前浏览器对 WebGL 1.0/2.0 的支持情况。
概念点和关系梳理
火狐 Nightly 支持 CSS Grids 动画
在火狐浏览器 Nightly (66) 版本中,CSS Grids 的轨迹可以动画化了,可以看看视频,效果很酷炫。
这一效果用 CSS 动画以及 grid-template-columns
和 grid-template-rows
即可实现,具体 Demo 可见这里。
源地址:https://twitter.com/jensimmon...
换个姿势起 Node.js 工程
通常起 Node.js 项目的姿势
通常我们会使用 npm
来起一个新的 Node.js 工程:
$ npm init
npm
接下来会问一系列的问题,并根据我们的回答来生成 package.json
。然后呢?我们不可避免的从 Github 上的一些仓库里 copy & paste 一个 .gitignore
模板来用。当然如果我们玩的是开源项目,还得记得附上一些 LICENSE
文件来声明我们使用的协议。
但这样也太没效率了!如果每次都这么搞一遍,真的能忍?
更有效率的方式
这周我在推上看到这么一条:
这四条指令足以把我手动做的这些事都做掉,而且还不止,然后我们就能够成功的创建起一个工程了。下面我们逐条解释下这四条的作用:
npx license mit
会使用 license 包来下载该协议对应的选项,这里的例子是指 MIT 协议。npx gitignore node
使用 gitignore 包来从 GitHub 仓库自动下载对应的.gitignore
文件。npx covgen
使用 covgen 包来生成 Contributor Covenant(参与者公约)。npm init -y
会自动选择npm init
询问的那些问题的默认选项。
自定义 npm init
是的,npm 支持一些自定义配置。我们可以通过在命令行输入 npm config list
来看下 npm 的配置。当然如果只想看与 npm init
相关的配置项,可以搭配使用 grep
:
npm config list | grep init
我可以设置许多默认配置:作者姓名、作者电子邮件、作者 URL、许可证和版本。如果要设置它们,我们可以在命令行中输入下面对应语句,或者使用 npm config edit
在文本编辑器中打开配置文件。使用命令行很简单,你可以设置所有五个默认值:
npm set init.author.name "Your name"
npm set init.author.email "[email protected]"
npm set init.author.url "https://your-url.com"
npm set init.license "MIT"
npm set init.version "1.0.0"
当你已经做好自定义配置后,npm init -y
就会直接生成你想要的配置了。
最后
这样你就可以换个姿势来开启一个 Node 工程啦!
源地址:https://philna.sh/blog/2019/0...
WebView & JS Bridge 笔记
这一篇是总结了一些文档和博客的笔记。
什么是 WebView
对于这个问题,我们可以谷歌一下,去开发者官方文档看看文档是怎么说的:
总结下来就一句话:WebView 就是个用于展示页面的 View。可以看到 android.webkit.WebView 继承自 android.view.View,只要接触过一些 native 的东西,大致就知道它是类似 div 一样的东西。View 本身是 widget 的基类,而 widget 就是用来创建交互 UI 的组件(如按钮、文字域之类)。
那么 WebView 可以做什么呢?
首先,WebView 只可以让你在 app 的界面上展示网页内容,但不具备通常浏览器的完整功能,如导航控制、地址栏等。当然,WebView 使得开发者可以将 web 页面嵌到 app 上,在一些场景下是一种不错的模式。
其次,你可以通过一些 native 技术来为 WebView 提供一些 bridge,从而获得一些原生能力,这也就是我们通常所说的 js bridge。但是现实总是残酷的,我们可以看下这一段:
JS Bridge 基本原理
JS Bridge 是为了支持 JavaScript 与 native 相互通信的模块(库/组件),有了 JS Bridge 我们就能够在 WebView 里使用一些 native 提供的能力。
JavaScript 调用 native 的方式大致可以有三类:注入映射、拦截 schema、拦截方法。
下面以安卓为例来解释下两种思路:
- 注入映射类的方式:
//定义好 Java 接口对象
public class SDK extends Object {
@JavascriptInterface
public void hello(String msg) {
System.out.println("Hello World");
}
}
//Webview 中调用
WebView webview = (WebView) findViewById(R.id.webview);
webview.addJavascriptInterface(new SDK(), 'sdk');
webview.loadUrl('http://imnerd.org'); //注入后加载页面
可以看到 addJavascriptInterface
方法定义了映射,我们就可以通过 sdk.hello() 来执行 native 方法了。不过这个方法存在安全问题,这里仅作演示。
作为对比,我们看看 iOS 的,意思是差不多的:
- (void)webViewDidFinishLoad:(UIWebView *)webView
{
self.jsContext = [self.webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];
self.jsContext[@"hello"] = ^() {
NSLog(@"Hello World");
};
}
- 拦截 schema 的方式:
这一种方式我们很容易理解,我们可以约定 url 的 schema,当发生 url 跳转时,如果符合约定的 schema,就交给客户端来拦截相应操作。
- 拦截方法的方式
JS 的一些方法执行时会触发客户端中的一些回调,因此可以通过对前端参数进行识别来执行对应的客户端代码。目前前端主要有以下四种方法会触发对应的回调方法:
alert | onJsAlert | runJavaScriptAlertPanelWithMessage |
prompt | onJsPrompt | runJavaScriptTextInputPanelWithPrompt |
confirm | onJsConfirm | runJavaScriptConfirmPanelWithMessage |
参考地址:
https://developer.android.com...
https://juejin.im/post/5a94f9...
https://juejin.im/post/5abca8...
https://75team.com/post/andro...