分类
技术研究

NativeScript的工作原理:用JavaScript调用原生API实现跨平台 – OurJS

注* NativeScript是最近推出的一个跨平台解决方案,可以让你可以用JavaScript来直接写Android、iOS本地应用程序,未来还即将扩展到Windows平台。是最近比较受关注的项目。它与nw (原名node-webkit ,用Web写winodw/linux桌面应用)和phonegap内嵌webview写APP的实现方式有着本质的不同,它直接用JavaScript调用系统原生API,因而有一些原生应用的特点。

NativeScript

NativeScript是一个运行环境,可以让你使用通用的JavaScript代码,打造原生的iOS,Android和Windows(即将推出)应用程序。 NativeScript有很多很酷的功能,比如支持JavaScript对象双向绑定到原生UI组件,以及用CSS为原生应用程序写样式。但我最喜欢的功能是NativeScript可以让您直接访问本地平台的原生API。

注* 可以理解为NativeScript是一个JavaScript V8运行环境的命令转发代理,将JavaScript调用转发给不同平台上的原生API如Android、iOS,以及即将支持的Windows。

例如,看看这个NativeScript写的Android应用程序的代码:

var time = new android.text.format.Time();
time.set( 1, 0, 2015 );
console.log( time.format( "%D" ) );

你只需要一两分钟来分析一下就明白了,这段JavaScript代码实例化一个Java android.text.format.Time()对象,调用其set()方法,然后打印format后的返回值,是字符串“01/01/15”。

我知道你已经很激动了,先不要慌,让我们再来看看iOS的代码:

var alert = new UIAlertView();
alert.message = "Hello world!";
alert.addButtonWithTitle( "OK" );
alert.show();

这段JavaScript代码实例化一个Objective-C UIAlertView类,设置它的信息属性,然后调用它的addButtonWithTitle()和show()方法。当您运行这段代码,你会看到hello word的警告框。

NativeScript运行时

该NativeScript运行环境看起来可能像变魔术一样,不管你信不信,该架构并不是那么的复杂。一切从JavaScript虚拟机开始,NativeScript从这里开始执行JavaScript指令。具体来说,NativeScript在Android采用v8;在iOS上采用JavaScriptCore。由于NativeScript使用JavaScript虚拟机,你访问原生API的所有JavaScript代码,仍然需要遵守JavaScript的语法结构和规范。

一般来说,NativeScript会同时采用V8和JavaScriptCore的最新稳定版;因此NativeScript对ECMAScript语言的支持在iOS的桌面Safari上面几乎是相同的,并且NativeScript在Android上面也几乎与桌面浏览器相同。包括一些ES6的新语法。

了解NativeScript使用了JavaScript虚拟机是很重要的,但这只是实现的第一步,让我们来看看上文示例的第一行代码:

var time = new android.text.format.Time();

在NativeScript Android运行环境中,该代码会被编译(JIT)并在V8中执行。这我们可能会很容易理解变…:

var x = 1 + 2;

但是接下来的问题是…V8怎么知道什么是android.text.format.Time()呢?

我们将重点讲V8和Android的实现,基本架构模式同样适用于iOS上的JavaScriptCore。如果出现显着差别,本文会提到。

这里将不讨论NativeScript在Windows上的实现细节,因为解决方案可能还会变,但是,当前的Windows实现跟iOS上的JavaScriptCore运行原理几乎一样。

NativeScript是怎样管理JavaScript虚拟机的

V8知道android是什么,因为NativeScript在运行时进行了注入,因为V8拥有一堆让你配置JavaScript环境的API。在JavaScript中您可以使用自定义的C++代码来分析CPU使用率,管理的JavaScript垃圾收集,等等一大堆API

面对这些API的是几个“Context”类,可以让你操纵全局变量,从而有可能为NativeScript注入一个全局的android对象。这实际上使用了与Node.js的相同运行机制,使全局API可用 – 如 require() – NativeScript使用它注入可以让你访问本地代码的API。 JavaScriptCore的也有类似的机制。酷吧?

让我们回到我们的代码:

var time = new android.text.format.Time();

现在你知道这个代码在V8中运行时,而V8已经知道什么是android.text.format.Time()了,因为NativeScript注入了必需的对象到全局范围。但仍存在着一些大的悬而未决的问题,如何让NativeScript明白那些注入的API到底是干什么的,然后调用?

Metadata(元数据)

对于NativeScript,反射是让NativeScript可以调用每个平台上的API的基石。包括android.text.format.Time。因为从性能角度来看重构这些API是很困难的,NativeScript会提前做掉这些,并在Android/iOS预编绎过程中嵌入预先生成的元数据。

考虑到这一点,让我们再次回到我们的代码:

var time = new android.text.format.Time();

现在你了解了这个V8代码是这样运行的,即NativeScript注入了android.text.format.Time的JavaScript对象,通过每一个单独的元数据注入。下一个问题:如何将NativeScript里的JavaScript调用Time()转发到本机android.text.format.Time()?

调用本地代码

NativeScript如何调用本机代码的答案就在于JavaScript虚拟机的API。我们上次使用V8的API是注入全局变量。这一次,我们将着眼于在JavaScript回调中调用给定的C++代码。

例如,JavaScript函数调用的代码 new android.text.format.Time(),V8会产生一个回调。也就是说V8有一个回调,让NativeScript拦截函数调用,然后用自定义的C ++代码执行一些动作,并返回一​​个新的结果。

在Android中的情​​况下,NativeScript运行的C++代码不能直接访问Java API,如android.text.format.Time。然而,Android的JNI ,或Java本地接口,提供了C++和Java之间的桥接能力,所以NativeScript使用JNI完成转发。在iOS中这个桥梁是不必要的,因为C++代码可以直接调用Objective-C的API。

了解了这些,让我们再回到代码:

var time = new android.text.format.Time();

我们已经知道,这个代码在V8中运行;是因为NativeScript注入过对象,它知道什么是android.text.format.Time;并且NativeScript中有这些基于元数据生成的API。我们现在知道,当 Timer() 执行时,会发生下面的事情:

1)V8运行回调函数。
2)NativeScript运行时通过它的元数据知道,Time()调用需要实例化一个android.text.format.Time对象。
3)NativeScript运行时使用JNI来实例化一个android.text.format.Time对象并保持对它的引用。
4)NativeScript运行时将代理的Java Time对象转化成JavaScript对象返回。
5)控制返回的JavaScript代理对象为被存储起来的本地时间变量。

代理对象是在NativeScript中保持JavaScript对象与本地平台对象的人工映射。例如,让我们来看看前面代码的下一行:

var time = new android.text.format.Time();
time.set( 1, 0, 2015 );

基于所生成的元数据,NativeScript知道代理对象上的所有方法。在这种情况下,代码调用Timer对象的set()方法时。此方法会再次调用V8及其功能回调; 然后NativeScript通过Android JNI转发到Java时间对象上相应的方法调用。

这就是NativeScript在部分的工作原理。酷吧?

现在,还遗留下了一些非常复杂的部分,因为将Objective-C和Java对象转换成JavaScript对象可能会很麻烦,尤其是考虑到不同的继承类型语言时。

我们不打算深入探讨这些问题的细节,NativeScript的另一个特点让你不必深入到本地代码,比如:TNS模块。

TNS Modules

TNS modules是Telerik NativeScript modules的简写。跟Node模块一样,它同样使用CommonJS。因此如果你已经会用require()和exports对象,那么你就已经掌握了TNS模块。

TNS模块允许你将特定的本地调用抽象成平台无关的API,NativeScript本身提供了几十个这样的模块供您使用。举个例子,假设您需要在您的iOS / Android应用程序创建的文件。你可能在Android中要写以下代码:

new java.io.File( path );

同样在iOS里写下面的代码:

NSFileManager.defaultManager();
fileManager.createFileAtPathContentsAttributes( path );

但是如果你使用TNS文件系统模块,你的代码将是一样的,而不必担心的iOS / Android的内部实现细节:

var fs = require( "file-system" );
var file = new fs.File( path );

更酷的是,你可以自己写TNS模块。比如这里有一个TNS模块,检索设备的操作系统版本:

// device.ios.js
module.exports = {
    version: UIDevice.currentDevice().systemVersion
}

// device.android.js
module.exports = {
    version: android.os.Build.VERSION.RELEASE
}

此代码只检索一个版本属性,但它给你多少灵感?使用自定义TNS模块是微不足道的,跟在nodejs中使用NPM模块一样:

var device = require( "./device" );
console.log( device.version );

如果你已经熟悉了npm的使用,NativeScript模块非常容易编写,分发和使用。就个人而言,作为一个Web开发人员,原生的iOS和Android代码让我害怕,尤其是当Java / Objective-C的API文档扔在一起的功能,它降低了我们跨平台开发的障碍。

 

转自:http://top.css88.com/archives/656
英文原版:http://developer.telerik.com/featured/nativescript-works/

分类
技术研究

Sass 3.4 最新特性

仅仅在 Sass 3.3 发布几个月后,官方就放出了 3.4 版本 —— “Selective Steve”。虽然名字有点怪异,但却很准确,因为新版本的诸多特性就是关于选择器的。

父类选择器 &

“SassScript?” 你或许对此会有所疑问。那么你可以将 SassScript 视为 Sass 的编程语言。在使用 SassScript 创造了 Sass 之后,Sass 成了一个预处理器,也成了一套开发系统,换一种角度说,Sass 总是隐藏在后台之中。或许就像是 Natalie Weizenbaum 的观点

SassScript,就是 Sass 用来创造变量、属性值等等的轻量级语言。虽然其中使用了许多 CSS 的值,但它还提供了自定义函数、算法等功能。

在 Sass 3.4 之前,除了下面的几个用法,开发者基本没法使用父级选择器做太多的事情(我并不喜欢父级选择器这个名字,实际上该选择器只是对上级的引用,而非真正地使用了父级选择器):

.selector {
  .no-svg & {
    // Stuff
  }

  &:hover {
    // (H)o(v|th)er stuff (see what I did there?)
  }
}

现在,我们可以做的更多了。首先需要说明的是,该特性本打算在 Sass 3.3 中实现,但由于技术原因推迟了。

大体上说,你可以像使用其他变量一样来使用 && 中通常保存了一系列的列表——这些列表可能就是你所期望的。

举例来说,如果有一个选择器 .foo .bar, .baz,那么 & 列表就会包含两个元素:.foo .bar.baz——而且这两个都是列表。详细来说,第一个列表也包含了两个元素(.foo.bar),而第二个列表则只包含了一个(.baz)。

要当心了,即使是单一选择器构成的列表,也会包含一个两级列表。举例来说,.item 就会包含 ((item,),)。所以,如果想从& 中选择 .item ,那么就需要使用 nth(nth(&, 1), 1)

以上说法可能还无法说明白为什么需要这样的功能。当然,不可否认的是,一直以来没有这样的我们也可以顺利的工作。但是在这之前,有一个会频繁使用到的功能,却没法实现:

// This doesn't work in 3.3.
// This doesn't work in 3.4.

.selector {
  color: blue;

  a& {
    color: red;
  }
}

期待输出的结果:

.selector {
  color: blue;
}
a.selector {
  color: red;
}

典型问题是:我想快速引用一个选择器。在这个示例中,我们希望给类名为 .selector 的链接(a)添加自定义样式。

虽然看起来并不简洁,但是我们至少可以实现功能了:

// This doesn't work in 3.3.
// This does work in 3.4.

.selector {
  color: blue;

  @at-root #{a + &} {
    color: red;
  }
}

首先,我们需要在顶层输出,因此可以使用 @at-root,这样我们就不致于生成 .selector a.selector。然后,只需要插入样式即可。

现在,我们可以通过创建函数将该功能模块化。就像下面这样:

// This doesn't work in 3.3.
// This does work in 3.4.

@mixin qualify($selector) {
  @at-root #{$selector + &} {
    @content;
  }
}

然后:

.selector {
  color: blue;

  @include qualify(a) {
    color: red;
  }
}

很酷对不对?据我所知,这只是 SassScript 中 & 用法的冰山一角。虽然可能有更多其他的场景,但它们通常包含更复杂的上下文环境(Sass 框架,等等)。

选择器函数

接下来,你会兴奋地看到 Sass 3.4 带来了一些处理选择器的函数。

关于这些函数我并不会讲太多,因为这对大多数开发者来说是没有意义(包括我)。事实上,这些特性主要服务于框架开发者(这是 Natalie 在 Twitter 上跟我确认的,不过我已经找不到这条信息了,要怪就怪 twitter 的搜索太差劲了)。

基本上,你可以做任何想做的事情:无论是在选择器后继续添加选择器,还是嵌套、替换选择器,或者是合并选择器……

不过,这里我只介绍两个函数:selector-extendselector-replace

首先,非常有必要指出 selector-extend 的解析机制和 @extend 指令完全相同。实际上,我怀疑使用的就是该指令。

其次,selector-replace 可能会比我们的想的更有效。注意!这不是一个字符串替换函数。实际上它正是利用了 @extend 指令的能力,在不违反逻辑思维的前提下替换选择器的。

这里我要借用一下 Natalie 的例子——selector-replace(".foo.bar.baz", ".foo.baz", ".qux")(在.foo.bar.baz.qux.foo.baz 替换)返回 .bar.qux。普通的字符处替换函数不会返回值,因为 .foo.bar.foor.bar.baz 中根本不存在。这就是 Sass 选择器函数的创新之处。

如果你问我这些是否有用,我会回答大多数没用。不过有些人也找到了用处,比如 Marc MintelMicah Godbolt,但是就我而言,这解决不了多大的问题,因为当他们这么做的同时,也会增加代码的复杂程度。简单至上!

使用 @error 进行错误处理

好了,关于选择器就讲这么多吧!如果说 Sass 3.4 中我所期待的特性,那么就要说 @error 指令了。实际上,作为一个框架开发者,我已经期待它两年多了。

再也不用使用 @warn "..."; @return null; 了,朋友们!@error 万岁!直到现在,Sass 都还没一个简洁的方式来处理错误。如果你长期关注我的文章,那么你会发现对下面的代码模式很熟悉:

@function rainbow($unicorn) {
  @if type-of($unicorn) != "unicorn" {
    @warn "What?! `#{$unicorn}` is not a unicorn? Are you serious?!";
    @return null;
  }

  // Proceed with the code.
}

基本上我们需要检查所有需要检查的地方,通过 @warn 提醒使用者,该指令返回 null (或者 false 等其他消息),并会继续处理所有的逻辑操作。

下面让我们见识一下 Sass 3.4 的魔力:

@function rainbow($unicorn) {
  @if type-of($unicorn) != "unicorn" {
    @error "What?! `#{$unicorn}` is not a unicorn? Are you serious?!";
  }

  // Proceed with the code.
}

Boom! 如果解析器执行到 @error 指令,它就会中断,并在控制台输出错误信息——类似语法错误之类的提示。代码执行就是简单的中断了,这是非常棒的做法!

颜色方面的改进

首先,rebeccapurple(#663399)被添加进了 Sass,其处理方式和其他颜色相同。这是为了纪念 Eric A. Meyer(CSS 传奇人物) 的女儿,她最喜欢的颜色就是紫色,不幸的是她在六岁就去世了。

此外,在 Sass 处理颜色的操作细节上,做了很棒的优化。举例来说,如有可能,Sass 将会保留颜色的原始值,除非在压缩模式下。压缩模式下,Sass 会尝试寻找最简洁的方式表示颜色。

同样的,rgb(), rgba(), hsl(),和 hsla() 函数也会尝试压缩为最小/最大的可用值,而不是直接抛出错误。这个改动更多的是为了在 CSS Colors Level 4 上和 CSS 规范保持一致。

最后,很多开发者都抱怨在占位符中使用颜色的做法,现在 Sass 将对这种编程方式提出警告。基本上,一些开发者所抱怨的就是下面代码生成的结果:

@each $color in darkolivegreen, firebrick, blanchedalmond {
  .item-#{$color} {
    color: $color;
  }
}

虽然这段代码可以在任何模式下正常渲染(除了压缩模式),但编译之后的代码却成了这样:

.selector-#556b2f{color:#556b2f}.selector-#b22222{color:#b22222}.selector-#ffebcd{color:#ffebcd}

正如所看到的,因为十六进制值更短小,所以为了节省空间,使用十六进制值所替代了颜色名。解决方案是引用列表("darkolivegreen", "firebrick", "blanchedalmond")中的颜色,但为了防止更多的混淆,Sass 将会在控制台中直接提出警告。

有没有感到很激动?!

如果这些都没有打动你,朋友们,那么不用担心,因为 Sass 3.4 还有很多新特性值得深挖。

让我给你介绍一些额外的功能吧——我认为很有趣:

  • 现在使用 map-remove 函数可以一次性删除多个键。该函数与 map-merge 一致,也可以接受数对参数。
  • 现在 @extend 指令可以正确处理伪类了——Sass 3.4 之前还存在问题。
  • Sourcemaps 大幅优化。

更多信息请参阅官方更新动态

 

————–
译文转自:http://www.w3cplus.com/preprocessor/sass-3-4-is-out.html

原文:http://www.sitepoint.com/sass-3-4-is-out/

分类
记忆碎片

而立

然后,就到了而立之年。

二十几岁的好青年,就这样变成了三十岁的好好青年。
然后,下个月,我们的宝马也到时间“提货”啦 ,刚好赶上我的而立之年。BTW,也是个处女座,嘻嘻……

而立,意味着什么?似乎明白,又似乎不太明白,首先想到的一个词是“责任”。
如果说跟二十有哪些不同,除头发少了不少,肚子稍凸,皮肤变糙之外,最大的不同是,远离了老陈与老佘的身边,组建了一个由自己主导的家……感谢有Dumai的陪伴,当然还有我们的宝马。

也许还有一点的不同就是再也不能像过去一样,在这里吐槽一堆,或假装忧郁地敲上一堆文字……

谢谢老妈,老爸,过去三十年养育。
谢谢媳妇陪伴。
谢谢媳妇的妈妈,还有借了专用的大厨照顾我们的媳妇她爹。
当然还有我们的宝马。

而立,那就继续认真地努力地生活吧!