跟我学用REACT NATIVE构建原生应用(二)

本文案例、代码及主要内容基于知乎前端专栏翻译的文章《深入浅出React Native:使用JavaScript构建原生应用》《Introducing React Native: Building Apps with JavaScript》而写。

在原文的基础上做了一些校对,并增加了android得环境安装和调试的内容。

由我书写的注释将会显示左边这个紫色竖线(#523f6d),而原文的注释为蓝色的(#559bce)。

内容传送门

一、《深入浅出R跟我学用React Native构建原生应用(一)
二、《深入浅出R跟我学用React Native构建原生应用(二)
三、《深入浅出R跟我学用React Native构建原生应用(三)
四、《深入浅出R跟我学用React Native构建原生应用(四)
五、《深入浅出R跟我学用React Native构建原生应用(五)
六、《深入浅出R跟我学用React Native构建原生应用(六)

你好 JSX 的世界

你当前的应用程序会使用 React.createElement 来构建应用 UI ,React会将其转换到原生环境中。在当前情况下,你的JavaScript代码是完全可读的,但一个更复杂的 UI 与嵌套的元素将迅速使代码变成一大坨。

确保应用程序仍在运行,然后回到你的文本编辑器中,编辑 PropertyFinderApp.js 。修改组件 render 方法的返回语句如下:

return <React.Text style={styles.text}>Hello World (Again)</React.Text>;

这是 JSX ,或 JavaScript 语法扩展,它直接在你的 JavaScript 代码中混合了类似 HTML 的语法;如果你是一个 web 开发人员,应该对此不陌生。在本篇文章中你将一直使用 JSX 。

把你的改动保存到 PropertyFinderApp.js 中,并返回到模拟器。按下 Cmd + R ,你将看到你的应用程序刷新,并显示更新的消息 “Hello World(again)”。

重新运行一个 React Native 应用程序像刷新 web 浏览器一样简单!:]

因为你会使用相同的一系列 JavaScript 文件,您可以让应用程序一直运行,只在更改和保存 PropertyFinderApp.js 后刷新即可

注意:如果你感到好奇,可以看看你的“包”在浏览器中,JSX被转换成什么。

这个 “Hello World” 已经够大家玩耍了,是时候构建实际的应用程序了!

添加导航

我们的房产查找应用使用标准的栈式导航,基于 UIKit 的 navigation controller。现在正是添加的时候。

在 index.ios.js 文件中,把 PropertyFinderApp 重命名为HelloWorld:

class HelloWorld extends React.Component {

“Hello World” 这几个字你还需要让它显示一会儿,但它不再是应用的根组件了。

接下来,在 HelloWorld 这个组件下面添加如下这个类:

class PropertyFinderApp extends React.Component {
  render() {
    return (
      <React.NavigatorIOS
        style={styles.container}
        initialRoute={{
          title: 'Property Finder',
          component: HelloWorld,
        }}/>
    );
  }
}

构造一个 navigation controller,应用一个样式,并把初始路由设为 Hello World 组件。在 Web 开发中,路由就是一种定义应用导航的一种技术,即定义页面——或者说是路由——与 URL 的对应关系。

在同一个文件中,更新样式定义,包含如下 container 的样式:

var styles = React.StyleSheet.create({
  text: {
    color: 'black',
    backgroundColor: 'white',
    fontSize: 30,
    margin: 80
  },
  container: {
    flex: 1
  }
});

在随后的教程中会告诉你 flex: 1 是什么意思。

回到模拟器,Cmd+R,看看新 UI 的样子:

5a8cd3dc5eaee2cbf0c91c3b939044a4_b

这就是包含了 root view 的 navigation controller,目前 root view 就是 “Hello World”。很棒——应用已经有了基础的导航结构,到添加真实 UI 的时候了。

创建搜索页

在项目中添加一个新文件,命名为 SearchPage.js,然后将其放在PropertyFinderApp.js 所在目录下。在文件中添加下面代码:

'use strict';

var React = require('react-native');
var {
  StyleSheet,
  Text,
  TextInput,
  View,
  TouchableHighlight,
  ActivityIndicatorIOS,
  Image,
  Component
} = React;

你会注意到,位于引入 react-native 所在位置的前面有一个严格模式标识,紧接着的声明语句是新知识。

这是一种解构赋值,准许你获取对象的多个属性并且使用一条语句将它们赋给多个变量。结果是,后面的代码中可以省略掉 React 前缀;比如,你可以直接引用 StyleSheet ,而不再需要 React.StyleSheet。解构同样适用于操作数组,更多细节请戳这里

继续在 SearchPage.js 文件中添加下面的样式:

var styles = StyleSheet.create({
  description: {
    marginBottom: 20,
    fontSize: 18,
    textAlign: 'center',
    color: '#656565'
  },
  container: {
    padding: 30,
    marginTop: 65,
    alignItems: 'center'
  }
});

同样,以上都是标准的 CSS 属性。和 Interface Builder 相比,这样设置样式缺少了可视化,但是比起在 viewDidLoad() 中逐个设置视图属性的做法更友好!

只需要把组件添加到样式声明的前面:

class SearchPage extends Component {
  render() {
    return (
      <View style={styles.container}>
        <Text style={styles.description}>
          Search for houses to buy!
        </Text>
        <Text style={styles.description}>
          Search by place-name, postcode or search near your location.
        </Text>
      </View>
    );
  }
}

render 很好地展示出 JSX 以及它表示的结构。通过这个样式,你可以轻易地描绘出组件 UI 的结构:一个容器,包含两个 text 标签。

最后,将下面的代码添加到文件末尾:

module.exports = SearchPage;

这可以 export SearchPage 类,方便在其他文件中使用它。

下一步是更新应用的路由,以初始化路由。

打开 PropertyFinderApp.js,在文件顶部紧接着上一个 require 语句的位置添加下面代码:

var SearchPage = require('./SearchPage');

在 PropertyFinderApp 类的 render 函数内部,通过更新 initialRoute 来引用最新添加的页面,如下:

component: SearchPage

此时,如果你愿意则可以移除 HelloWorld 类以及与它相关联的样式。你不在需要那段代码了。

切换到模拟器,按下 Cmd+R 查看新的 UI:

cbe4401c09d061f7755ef80a353933b9_b

使用 Flexbox 定义外观现在,你已经看到了用基本的 CSS 属性来控制外间距(margin),内间距(padding)还有颜色(color)。不过,可能你还不太了解要如何使用伸缩盒(flexbox),flexbox 是最近新加入 CSS 规范,用它就能很便利地布局界面。

React Native 用 css-layout(这是一个用 JavaScript 实现flexbox标准然后编译成 C(iOS平台)或者Java(Android平台)的库)。

Facebook把这个项目单独出来实在太正确了,这样可以编译成多种语言,促进更多新颖的应用的发展,比如flexbox layout to SVG

在你的App中,容器(container)默认地是纵向布局,也就是说在它的子元素将会竖直地排列,像这样:

e07a20540d130f08167c93d1c6f0f540_b

这被称为主轴 (main axis),它的方向可以是竖直的也可以是水平的。每一个子元素在竖直方向上的位置是由它的margin,height和padding共同决定的。容器的 alignItems 属性也要设置成 center,这个属性可以控制子元素在十字轴上的位置。在这里,它实现了居中对齐的文本。

好啦,现在我们把输入框和按钮加上去吧。打开 SearchPage.js,将下面的代码插入第二个 Text 元素的后面:

<View style={styles.flowRight}>
  <TextInput
    style={styles.searchInput}
    placeholder='Search via name or postcode'/>
  <TouchableHighlight style={styles.button}
      underlayColor='#99d9f4'>
    <Text style={styles.buttonText}>Go</Text>
  </TouchableHighlight>
</View>
<TouchableHighlight style={styles.button}
    underlayColor='#99d9f4'>
  <Text style={styles.buttonText}>Location</Text>
</TouchableHighlight>

现在你已经加上了两个最高等级的视图(top-level view),一个视图包含了文本输入框和一个按钮,还有一个视图内只有一个按钮。在后文中你会看到,它们的样式是什么样的。

接着,添加上对应的样式:

flowRight: {
  flexDirection: 'row',
  alignItems: 'center',
  alignSelf: 'stretch'
},
buttonText: {
  fontSize: 18,
  color: 'white',
  alignSelf: 'center'
},
button: {
  height: 36,
  flex: 1,
  flexDirection: 'row',
  backgroundColor: '#48BBEC',
  borderColor: '#48BBEC',
  borderWidth: 1,
  borderRadius: 8,
  marginBottom: 10,
  alignSelf: 'stretch',
  justifyContent: 'center'
},
searchInput: {
  height: 36,
  padding: 4,
  marginRight: 5,
  flex: 4,
  fontSize: 18,
  borderWidth: 1,
  borderColor: '#48BBEC',
  borderRadius: 8,
  color: '#48BBEC'
}

要注意格式问题:每一个样式都是用逗号分隔开的,所以别忘了在container 选择器后面还要加上一个逗号。

以上的样式将会应用在你刚刚加上的输入框和按钮上。

现在返回到模拟器,然后按下 Cmd+R 刷新界面:

10077dcc4cdc5d66f87e572203e96fc2_b

文本区域和 ’Go’ 按钮在同一行,不需要显式地定义两个组件的宽度,你只需要将它们放在同一个容器中,加上 flexDirection:’row’ 样式,再定义好它们的 flex 值。文本区域是 flex:4,按钮则是 flex:1,这说明两者的宽度比是4:1。大概你也发现了,你的“按钮”其实并不是按钮!:] 使用了 UIKit 后,按钮更倾向于是可以轻碰(tap)的标签(label),所以 React Native 团队决定直接在 JavaScript 中构建按钮了。所以你在 App 中使用的按钮是 TouchableHighlight,这是一个 React Native 组件,当轻碰 TouchableHighlight 时,它会变得透明从而显示出衬底的颜色(也就是按钮下层的组件颜色)。

搜索界面的最后一步就是加上一张图片.你可以从这里下载我们用的图片素材并解压。

在Xcode中打开Images.xcassets文件,点击加号添加一个新的图片集。然后将图片素材拖进正确的“区间”:

7fd915417400b22cde8a274924fa7808_b你需要重启应用才能让图片生效。

将以下代码添加到 TouchableHighlight 组件后面,它将用于“获取位置”按钮:

<Image source={require('image!house')} style={styles.image}/>

现在再样式表的最后加上图片对应的样式,别忘了给原样式中最后一个加上逗号哦:

image: {
  width: 217,
  height: 138
}

require(‘image!house’) 语句用于确定在你应用的asset目录下的图片资源,在 Xcode 中,如果你的打开了 Images.xcassets,你会看到一个“房屋”的图标,正是上面代码中引用到的。

返回到模拟器,Cmd+R刷新UI:

73421776c82a309841db13177bfa261d_b

注意:如果你这会没有看到“房屋”图片,取而代之的是一张“找不到资源”的图片,尝试重启packager(也就是在终端里输入 npm start 命令)。

现在你的应用看起来挺不错的啦,不过它还少了点功能。接下来你的任务就是给它加上点状态,让它执行一些操作。

Mac 可设置环境变量的位置、查看和添加PATH环境变量

原文地址:http://elf8848.iteye.com/blog/1582137

Mac 启动加载文件位置(可设置环境变量

——————————————————-

(1)首先要知道你使用的Mac OS X是什么样的Shell,使用命令

echo $SHELL

如果输出的是:csh或者是tcsh,那么你用的就是C Shell。

如果输出的是:bash,sh,zsh,那么你的用的可能就是Bourne Shell的一个变种。

Mac OS X 10.2之前默认的是C Shell。

Mac OS X 10.3之后默认的是Bourne Shell。

 

(2)如果是Bourne Shell。

那么你可以把你要添加的环境变量添加到你主目录下面的.profile或者.bash_profile,如果存在没有关系添加进去即可,如果没有生成一个。

 

1./etc/profile   (建议不修改这个文件 )

全局(公有)配置,不管是哪个用户,登录时都会读取该文件。

 

2./etc/bashrc    (一般在这个文件中添加系统级环境变量)

全局(公有)配置,bash shell执行时,不管是何种方式,都会读取此文件。

我在这里加入mysqlstart、mysql和mysqladmin命令的别名,保证每一个用户都可以使用这3个命令。

 

3.~/.bash_profile  (一般在这个文件中添加用户级环境变量)

(注:Linux 里面是 .bashrc 而 Mac 是 .bash_profile)

若bash shell是以login方式执行时,才会读取此文件。该文件仅仅执行一次!默认情况下,他设置一些环境变量

我在这里:设置终端配色、

我在这里:设置命令别名alias ll=’ls -la’

我在这里:设置环境变量:export PATH=/opt/local/bin:/opt/local/sbin:$PATH

 

 

MAC 修改host文件 

——————————————————-

sudo vi /etc/hosts

 

 

linux下查看和添加PATH环境变量

==============================================

 

PATH的格式为:

——————————————————-

PATH=$PATH:<PATH 1>:<PATH 2>:<PATH 3>:——:<PATH N>   ,中间用冒号隔开。

 

 

 

添加PATH环境变量:

——————————————————-

[root@localhost u-boot-sh4]#export PATH=/opt/STM/STLinux-2.3/devkit/sh4/bin:$PATH

 

 

查看PATH环境变量:

——————————————————-

[root@localhost u-boot-sh4]#echo $PATH

/usr/kerberos/sbin:/usr/kerberos/bin:/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin:/root/bin

 

 操作示例:

——————————————————-

通过编辑 启动文件 来改PATH,

# vim /etc/profile

在文档最后,添加:

export PATH=”/opt/STM/STLinux-2.3/devkit/sh4/bin:$PATH”

保存,退出。

 

立即生效请运行:

#source /etc/profile

不报错则成功。

 

如果想立刻生效,则可执行下面的语句:

$ source .bash_profile(这是文件名)

环境变量更改后,在用户下次登陆时生效。

跟我学用React Native构建原生应用(一)

本文案例、代码及主要内容基于知乎前端专栏翻译的文章《深入浅出React Native:使用JavaScript构建原生应用》《Introducing React Native: Building Apps with JavaScript》而写。

在原文的基础上做了一些校对,并增加了android得环境安装和调试的内容。

由我书写的注释将会显示左边这个紫色竖线(#523f6d),而原文的注释为蓝色的(#559bce)。

内容传送门

一、《深入浅出R跟我学用React Native构建原生应用(一)
二、《深入浅出R跟我学用React Native构建原生应用(二)
三、《深入浅出R跟我学用React Native构建原生应用(三)
四、《深入浅出R跟我学用React Native构建原生应用(四)
五、《深入浅出R跟我学用React Native构建原生应用(五)
六、《深入浅出R跟我学用React Native构建原生应用(六)

数月前,Facebook 对外宣布了正在开发的 React Native 框架,这个框架允许你使用 JavaScript 开发原生的 iOS 应用——就在今天,Beta 版的仓库释出了!基于 PhoneGap 使用 JavaScript 和 HTML5 开发 iOS 应用已经有好几年了,那 React Native 有什么牛的?React Native 真的很牛,让大家兴奋异常的主要原因有两点:

  1. 可以基于 React Native使用 JavaScript 编写应用逻辑,UI 则可以保持全是原生的。这样的话就没有必要就 HTML5 的 UI 做出常见的妥协;
  2. React 引入了一种与众不同的、略显激进但具备高可用性的方案来构建用户界面。长话短说,应用的 UI 简单通过一个基于应用目前状态的函数来表达。

React Native 的关键就是,以把 React 编程模式的能力带到移动开发来作为主要目标。它的目标不是跨平台一次编写到处执行,而是一次学习跨平台开发。这个是一个非常大的区别。这篇教程只介绍 iOS 平台,不过你一旦掌握了相关的概念,就可以应用到 Android 平台,快速构建 Android 应用。

如果之前只用过 Objective-C 或者 Swift 写应用的话,你很可能不会对使用 JavaScript 来编写应用的愿景感到兴奋。尽管如此,作为一个 Swift 开发者来说,上面提到的第二点应该可以激起你的兴趣!

你通过 Swift,毫无疑问学习到了新的更多有效的编码方法和技巧,鼓励转换和不变性。然而,构建 UI 的方式还是和使用 Objective-C 的方式一致。仍然以 UIKit 为基础,独断专横。

通过像 virtual DOM 和 reconciliation 这些有趣的概念,React 将函数式编程直接带到了 UI 层。

这篇教程将带着你一路构建一个 UK 房产搜索应用:

062e83eb739070f18971e3d7a3f473ff_b

如果你之前一点 JavaScript 都没写过,别担心。这篇教程带着你进行一步一步进行编码。React 使用 CSS 属性来定义样式,一般比较容易读也比较容易理解。但是如果你想了解更多的话,可以去看看 Mozilla Developer Network reference,很不错的。想要学习更多,继续往下读!

准备工作

React Native 框架托管在 GitHub 上。你可以通过两种方式获取到它:使用 git 克隆仓库,或者下载一个 zip 压缩包文件。如果你的机器上已经安装了 React Native,在着手编码前还有其他几个因素需要考虑。

  • React Native 借助 Node.js,即 JavaScript 运行时来创建 JavaScript 代码。如果你已经安装了 Node.js,那就可以上手了。

首先,使用 Homebrew 官网提供的指引安装 Homebrew,然后在终端执行以下命令:

brew install node

接下来,使用 homebrew 安装 watchman,一个来自Facebook 的观察程序:

brew install watchman

通过配置 watchman,React 实现了在代码发生变化时,完成相关的重建的功能。就像在使用 Xcode 时,每次保存文件都会进行一次创建。

接下来使用 `npm` 安装 React Native CLI 工具:

npm install -g react-native-cli

如果是用mac开发,使用npm遇到问题了,可以看下《MAC安装NPM插件的权限问题》这篇文章。后面还附带一个淘宝镜像的使用方法。

这使用 Node 包管理器抓取 CLI 工具,并且全局安装;`npm` 在功能上与 CocoaPods 或者 Carthage 类似。

浏览到你想要创建 React Native 应用的文件夹下,使用 CLI 工具构建项目:

react-native init PropertyFinder

现在,已经创建了一个初始项目,包含了创建和运行一个 React Native 应用所需的一切。

如果仔细观察了创建的文件夹和文件,你会发现一个 node_modules 文件夹,包含了 React Native 框架。你也会发现一个 index.ios.js 文件,这是 CLI 工具创建的一个空壳应用。最后,会出现一个 Xcode 项目文件和一个 iOS 文件夹,包含了少量的代码用来引入 bootstrap 到你的项目中。

打开 Xcode 项目文件,然后创建并运行。模拟器将会启动并且展示下面的问候语:

062e83eb739070f18971e3d7a3f473ff_b (1)
你可以能发现,有一个终端窗口弹出,输出了下面的信息:

$ npm start

> react-native@0.1.0 start /Users/colineberhardt/Projects/react-native
> ./packager/packager.sh

 ===============================================================
 |  Running packager on port 8081.       
 |  Keep this packager running while developing on any JS         
 |  projects. Feel free to close this tab and run your own      
 |  packager instance if you prefer.                              
 |                                                              
 |     https://github.com/facebook/react-native                 
 |                                                              
 ===============================================================

    React packager ready.

这就是 React Native 包,在 node 下运行。一会儿你就会知道它是用来干什么的。

不要关闭终端窗口;就然它在后台运行。如果你不小心关了,只需要停下来使用 Xcode 重新运行项目。

注意:在进入编码工作之前,还有最后一件事 —— 在这个教程中,你需要编写大量的 JavaScript 代码,Xcode 并非是最好的工具! Sublime Text,一个价格合理且应用广泛的编辑器。不过,atombrackets 或者其他轻量的编辑器都能胜任这份工作。

我使用的时webStrom10,这个版本的webStrom已经支持JSX的语法,还是比较方便的。

React Native 你好

在开始“搜房App”之前,先来个简单的 Hello World App 热热身。在这一节里,你将会使用到一些组件。

在你钟爱的编辑其中打开 index.ios.js,删除当前的内容,因为你要从头构建你自己的应用。然后在文件顶部增加下面这样一行::

'use strict';

这行代码是用于开启 Strict Mode,Strict mode的错误处理可以有所提高,JavaScript的一些语言缺陷也可以避免。简而言之就是,JavaScript在这种模式下工作地更好!

注意:想要研究一下 Strict Mode 的朋友,我会推荐你阅读 Jon Resig 的文章:“ECMAScript 5 Strict Mode, JSON, and More”

然后,加上这一行:

var React = require('react-native');

这句代码是将 react-native 模块加载进来,并将它赋值给变量 React 的。React Native 使用同 Node.js 相同的模块加载方式:require,这个概念可以等同于 Swift 中的“链接库”或者“导入库”。

注意:想要了解更多关于 JavaScript 模块的知识,我推荐阅读 Addy Osmani 写的这篇文章

在 require 语句的下面,加上这一段:

var styles = React.StyleSheet.create({
  text: {
    color: 'black',
    backgroundColor: 'white',
    fontSize: 30,
    margin: 80
  }
});

以上代码定义了一段应用在 “Hello World” 文本上的样式。如果你曾接触过Web开发,那你很可能已经发现了:React Native 使用的是 CSS 来定义应用界面的样式。

现在我们来关注应用本身吧!依然是在相同的文件下,将以下代码添加到样式代码的下面:

class PropertyFinderApp extends React.Component {
  render() {
    return React.createElement(React.Text, {style: styles.text}, "Hello World!");
  }
}

是的,奏是 JavaScript class!

类 (class) 是在ES6中被引入的,纵然JavaScript一直在进步,但Web开发者受困于兼容浏览器的状况中,不能怎么使用JS的新特性。React Native运行在JavaScriptCore中是,也就是说,你可以使用JS的新特性啦,完全不用担心兼容什么的呢。

注意:如果你是一名 Web 开发者,我百分百鼓励你要使用现代的JavaScript,然后使用像 Babel 这样的工具生成兼容性的 JavaScript,用于支持兼容性不好的老浏览器。

注意:截止到我整理这套教程的时间,react-native初始化的代码样例还是ES5的,这并不影响你的使用,如果有兴趣你可以去了解一下ES5ES6对这部分操作的区别,以帮助你更好地区分和理解。

PropertyFinderApp 继承了 React.Component(React UI的基础模块)。组件包含着不可变的属性,可变的状态变量以及暴露给渲染用的方法。这会你做的应用比较简单,只用一个渲染方法就可以啦。

React Native 组件并不是 UIKit 类,它们只能说是在某种程度上等同。框架只是将 React 组件树转化成为原生的UI。

最后一步啦,将这一行加在文件末尾:

React.AppRegistry.registerComponent('PropertyFinder', function() { return PropertyFinderApp });

AppRegistry 定义了App的入口,并提供了根组件。

保存 PropertyFinderApp.js,回到Xcode中。确保 PropertyFinder 规划(scheme)已经勾选了,并设置了相应的 iPhone 模拟器,然后生成并运行你的项目。几秒之后,你应该就可以看到 “Hello World” 应用正在运行了:

80b700261430c5da1da10c9034afbc6b_b

这个JavaScript应用运行在模拟器上,使用的是原生UI,没有任何内嵌的浏览器哦!还不相信这是真的?:] 那打开你的 Xcode,选择 Debug\View Debugging\Capture View Hierarchy,你看 native view hierarchy 中都没有 UIWebView,就只有一个原生的view!

a54cf6d88da1ca1a2d32bc8e47806698_b
你一定很好奇其中的原理吧,那就在 Xcode 中打开 AppDelegate.m,接着找到 application:didFinishLaunchingWithOptions:这个方法构建了 RCTRootView 用于加载 JavaScript 应用以及渲染最后的视图的。

当应用开始运行的时候,RCTRootView将会从以下的URL中加载应用:

http://localhost:8081/index.ios.bundle

重新调用了你在运行这个App时打开的终端窗口,它开启了一个 packager 和 server 来处理上面的请求。

在 Safari 中打开那个 URL;你将会看到这个 App 的 JavaScript 代码。你也可以在 React Native 框架中找到你的 “Hello World” 代码。

当你的App开始运行了以后,这段代码将会被加载进来,然后 JavaScriptCore 框架将会执行它。在 Hello World 的例子里,它将会加载 PropertyFinderApp 组件,然后构建出原生的 UIKit 视图。关于这部分的内容,后文里会再详细解释的。

mac安装npm插件的权限问题

在mac全局上安装npm的插件会有权限问题,一般来说大家会在前面加sudo来解决。

可是有的插件在自行安装npm插件的时候不会加sudo,比如react在初始化项目的时候。

下面这个命令一劳永逸解决问题。
sudo chown -R 'whoami' /usr/local
详情可参阅github上的解决方案:https://github.com/npm/npm/issues/5922

当然也可以改的更彻底一些
sudo chown -R 'whoami':staff /usr/local
sudo chown -R 'whoami':staff /Users/'whoami'/.npm

另外关于npm下载速度慢得问题可以使用淘宝的镜像库

npm install- g cnpm --registry=http://registry.npm.taobao.org

Angularjs指令中的ngModel

之前用的时候忘记总结了,这里集中总结一下这个问题。

使用require:‘ngModel’ 这个选项来增强我们对指令的处理,

这样就可以作为link选项的第四个参数,
link: function ($scope,$element,$attrs,$ngModel){
//其他逻辑代码
}

这里面的$ngModel便是代码中ng-model指令传递过来的对象,包含以下这些方法和属性:

  • $viewValue:视图值,即显示在视图(页面)的实际值(就是上面例子中input输入框的值)
  • $modelValue:模型值,即赋给ng-model的值(与控制器绑定的值)
    两者不一定相等,因为$viewValue同步到$modelValue要经过一系列的操作(经过三个管道)。
  • $parsers:一个执行它里面每一个元素(每一个元素都是一个函数)的数组,
    主要是用来做验证和转换值的过程,ngModel从DOM读取的值会被传入到其中的函数它会依次执行每一个函数,把每一个函数执行的结果传个下一个函数,而最后一个函数执行的值将会传到model中,

    我们可以将函数push进去,那样它就会执行。

  • $formatters:也是一个执行它里面每一个元素(每一个元素都是一个函数)的数组,主要用来对值进行格式化和转换,以便在绑定了这个值的控件中显示。当数据的模型值发生变化的时候,里面的函数会被一一执行,同样我们就可以将函数push进去,让它执行
  • $viewChangeListeners:也是一个由函数组成的数组当视图的值发生变化的时候里面的函数会被一一调用,实现跟$watch类似的功能。
  • $render:函数负责将模型值同步到视图上, 如果模型值被改变,需要同步视图的值。
  • $setViewValue:用于设置视图值(上面的例子就是将input的value值赋值给$viewValue)
  • $error:一个包含所有error的对象
  • $setPristine:设置为原始状态,会添加ng-pristine的class类名,移除ng-dirty的class类名
  • $setValidity:设置错误的标志为一个函数,接受两个参数,第一个参数为错误标志的名字,是字符串类型,将会被设置成$error的属性第二个参数为布尔值,为这个错误标志的值。

     

Angular.js中使用$watch监听模型变化

先说基本的

$watch简单使用

$watch是一个scope函数,用于监听模型变化,当你的模型部分发生变化时它会通知你。

 

$watch(watchExpression, listener, objectEquality);

每个参数的说明如下:

  1. watchExpression:监听的对象,它可以是一个angular表达式如’name’,或函数如function(){return $scope.name}。
  2. listener:当watchExpression变化时会被调用的函数或者表达式,它接收3个参数:newValue(新值), oldValue(旧值), scope(作用域的引用)
  3. objectEquality:是否深度监听,如果设置为true,它告诉Angular检查所监控的对象中每一个属性的变化. 如果你希望监控数组的个别元素或者对象的属性而不是一个普通的值, 那么你应该使用它。

简单的实现看书好了,我这里重点要说的是监听多个值。

$watch监听多个值

 

从第一个参数的说明里可以看出,可以用这样的函数做监听对象function(){return $scope.name}。

就是函数返回的值改变了,我们就认为值变了。

 

那么我们用这个方法监听多个值,如下:
$scope.$watch(function () {
return JSON.stringify([$scope.name, $scope.value])
}, function (newVal,oldVal) {
//...
});

以上面的方式就可以同时监听该作用域($scope)下的name和value两个值了。

 

关于$watchCollection

该方法允许我们为对象的属性或者数组的元素设置浅(一层)监控。

再说CSS3渐变——线性渐变

原文转载自:http://www.w3cplus.com/css3/new-css3-linear-gradient.html

 

渐变背景一直以来在Web页面中都是一种常见的视觉元素。但一直以来,Web设计师都是通过图形软件设计这些渐变效果,然后以图片形式或者背景图片的形式运用到页面中。Web页面上实现的效果,仅从页面的视觉效果上来看,与设计并无任何差异。

事实上这种方法是比较麻烦的,因为首先需要设计师进行设计,然后进行切图,在通过样式应用到页面中。另外,在实际应用中可扩展性差,还直接影响页面性能。

值得庆幸的是,W3C组织将渐变设计收入到CSS3标准中,让广大的前端设计师直接受益,可以直接通过CSS3的渐变属性制作类似渐变图片的效果。而且渐变属性慢慢得到了众多现代浏览器的兼容,甚至烦人的IE,在IE10版本也支持了这个属性。

CSS3渐变的介绍

欲要了解CSS3渐变,就先要知道CSS3渐变是什么?从早前的设计中我们可以知道,渐变是两种或多种颜色之间的平滑过渡。在创建渐变的过程中,可以指定多个中间颜色值,这个值称为色标。每个色标包含一种颜色和一个位置,浏览器从每个色标的颜色淡出到下一个,以创建平滑的渐变。如下图所示:

再说CSS3渐变——线性渐变

渐变可以应用于任何使用背景图片的地方。这意味着在CSS样式中,渐变相当于背景图片,在理论上可在任何使用url() 值的地方采用,比如最常见的background-image、list-style-type以及前面介绍的CSS3的图像边框属性border-image。但直到目前为止,仅在背景图片中得到最完美的支持。

渐变功能的实现

最早支持CSS3渐变的是Webkit内核的浏览,随后在Firefox和Opera等浏览器都得到支持,但是众浏览器之间没有得到统一的标准,用法差异很大。不同的渲染引擎实现渐变的语法也不同,各浏览器下使用都需要带上自己的前缀,给前端设计师们带来极大的不便。

不过还好,到写本章内容的时候,CSS3渐变属性在IE10+、Firefox19.0+、Chrome26.0+和Opera12.1+等浏览器已完全支持W3C的标准语法,但在Webkit内核下的Safari、iOS Safari、Android浏览器和Blackberry浏览器中还是需要添加浏览器的前缀 “-webkit-”。

较以前相比,虽然CSS3的渐变属性在众浏览器中得到较好的支持,但在实际使用的时候也像制作软件(Photoshop CS6、Firework CS6等)的渐变工具一样,将渐变分成了几种:线性渐变,径向渐变、重复线性渐变和重复径向渐变。接下来,我们将依次介绍它们。

线性渐变

在线性渐变过程中,颜色沿着一条直线过渡:从左侧到右侧、从右侧到左侧、从顶部到底部、从底部到顶部或着沿任何任意轴。如果你曾使用过制作图件,比如说Photoshop,你对线性渐变并不会陌生。

CSS3制作渐变效果,其实和使用制作软件中的渐变工具没有什么差别。首先需要指定一个渐变的方向、渐变的起始颜色、渐变的结束颜色。具有这三个参数就可以制作一个最简单、最普通的渐变效果。如果你需要制作一个复杂的多色渐变效果,就需要在同一个渐变方向增添多个色标。具备这些渐变参数(至少三个),各浏览器就会绘制与渐变线垂直的颜色结来填充整个容器。浏览器渲染出来的效果就类似于制作软件设计出来的渐变图像,从一种颜色到另一种颜色的平滑淡出,沿所指的线性渐变方向实现颜色渐变效果。

一、线性渐变语法与参数

线性渐变的语法相对于其他的CSS3属性的语法而言要复杂的多。早期的语法在各浏览器内核下其语法尽不相同,特别是在Webkit内核之下还分新旧两种版本。接下来我们先从各浏览器下的语法入手,开始介绍CSS3的线性渐变语法。

1. Webkit引擎的CSS3线性渐变语法与属性参数

Webkit是第一个支持CSS3渐变的浏览器引擎,不过其语法也相对其他浏览器引擎复杂,还分为新旧两个版本。

Webkit引擎老式语法

-webkit-gradien(<type>,<point>[,<radius>]?,<point>[,<radius>]?[,<stop>]*)

Webkit引擎新式语法

-webkit-linear-gradient([<point>||<angle>,]?<stop>,<stop>[,<stop>]*)

Webkit引擎渐变属性参数

-webkit-gradient是webkit引擎对渐变的实现一共有五个参数。第一个参数表示渐变类型(type),可以是线性渐变linear或者径向渐变radial。第二个参数和第三个参数,都是一对值,分别表示渐变的起点位置和终点位置。这对值可以用坐标形式表示,也可以用关键值表示,比如left top(左上角)和left bottom(左下角)。第四个和第五个参数,分别是两个color-stop函数(色标)。color-stop函数接受两个参数,第一个表示渐变的位置,0表示起点,0.5为中点,1为结束点;第二个表示该点的颜色。 如下图所示:

再说CSS3渐变——线性渐变

2. Gecko引擎的CSS3的线性渐变语法与属性参数

Gecko引擎的浏览器Firefox在3.6版本就开始支持CSS3的线性渐变属性。Gecko引擎与Webkit引擎的新版本渐变设计时用法基本相同,只是使用的私有前缀不同。

Gecko引擎的渐变语法

-moz-linear-gradient([<point>||<angle>,]?<stop>,<stop>[,<stop>]*)

Gecko引擎的渐变属性参数

在Gecko引擎的渐变中共有三个参数,第一个数数表示线性渐变的方向,例如:top是从上到下、left是从左到右。如果定义成left top,那就是从左上角到右下角。第二个和第三个参数分别是起点颜色和终点颜色。你还可以在它们之间插入更多的参数,表示多种颜色的渐变。如图所示:

再说CSS3渐变——线性渐变

3. Presto引擎的CSS3线性渐变语法与属性参数

Presto引擎的Opera浏览器在11.6版本开始就支持CSS3的线性渐变。在Presto引擎浏览器中CSS3线性渐变的使用语法和Gecko引擎浏览器中的线性渐变的语法非常类似,唯一不同的就是在Presto引擎浏览器中需要使用其自己的私有前缀为“-o-”。

Presto引擎的线性渐变语法

-o-linear-gradient([<point>||<angle>,]?<stop>,<stop>[,<stop>]*)

Presto引擎的线性渐变的属性参数

-o-linear-gradient也具有三个参数:第一个参数表示线性渐变的方向,top表示从上到下,left表示从左到右,如果定义成left top表示从左上角到右下角。第二个和第三个参数分别是起点颜色和结束颜色。还可以在它们之间插入更多的参数,表示多种颜色的渐变。如图所示:

再说CSS3渐变——线性渐变

4. Trident引擎的CSS3线性渐变语法与属性参数

Trident引擎的浏览器主要有IE,早期版本的IE浏览器是不支持CSS3线性渐变的属性,不过在其IE10开始支持了这个属性。在这里我们主要针对IE10+浏览器的CSS3线性渐变进行简单的介绍。

Trident引擎的CSS3线性渐变语法

-ms-linear-gradient([<point>||<angle>,]?<stop>,<stop>[,<stop>]*)

Trident引擎的CSS3线性渐变属性参数

-ms-linear-gradient属性参数和-moz-linear-gradient以及-o-linear-gradient的属性参数是一样的,这里就不在进行重复性的介绍。

5. W3C标准线性渐变语法与属性参数

W3C组织于2012年04月发布了CSS3线性渐变的CR版本(候选人推荐版本)。这一次发布的CSS3渐变属性有着很大的变化,使用语法较前面的版本要简单多,容易理解的多。最让大家感到高兴的是,到写本文的时候,所有现代浏览器都支持W3C的标准语法,包括曾经令人讨厌的IE浏览器,也在IE10中支持了标准语法。

W3C标准线性渐变语法

linear-gradient([[<angle> | to <side-or-corner> ],]?<color-stop>[,<color-stop>]+)

W3C标准线性渐变属性参数

W3C标准线性渐性语法包括三个主要属性参数:第一个参数指定了渐变的方向,同时决定了渐变颜色的停止位置。这个参数值可以省略,当省略不写的时候其取值为“to bottom”。在决定渐变的方向主要有两种方法:

  •  <angle>:通过角度来确定渐变的方向。0度表示渐变方向从下向上,90度表示渐变方向从左向右。如果取值为下值,其角度按顺时针方向旋转。
  •  关键词:通过关键词来确定渐变的方向。比如“to top”、“to right”、“to bottom”和“to left”。这些关键词对应的角度值为“0deg”、“90deg”、“180deg”和“270deg”。除了使用“to top”、“top left”之外,还可以使用“top left”左上角到右下角、“top right”左上角到右下解等。
  •  第二个和第三个参数,表示颜色的起始点和结束点。大家可以在从中插入更多的颜色值。

二、线性渐变的基本用法

前几节中介绍了各引擎浏览器以及W3C标准中的CSS3线性渐变的语法以及相关属性的基本知识。在W3C标准语法一节中,我们可以得知, CSS3在各浏览器下得到较好的支持,接下来,我们主要以标准语法的使用,通过案例向大家展示CSS3线性渐变的基本使用。

颜色从底部向顶部渐变(Bottom → Top)

制作从底部到顶部直线渐变最简单的方法直接使用“to top”关键词,表示第一颜色向第二颜色渐变。实现类似于“to top”效果还可以使用角度值“0deg”、“360deg”和“-360deg”。

div {
  width: 400px;
  height: 150px;
  border: 1px solid #666;
  line-height: 150px;
  text-align: center;
  font-weight: 900;
  font-size: 30px;
  color: #fff;
  margin: 10px auto;
}
.toTop {
  background-image:-webkit-linear-gradient(to top, orange, green);
  background-image:linear-gradient(to top,orange,green);
}
.toTop-deg{
  background-image:-webkit-linear-gradient(0deg, orange, green);
  background-image:linear-gradient(0deg,orange,green);
}
.toTop-deg2{
  background-image:-webkit-linear-gradient(360deg, orange, green);
  background-image:linear-gradient(360deg,orange,green);
}
.toTop-deg3 {
  background-image:-webkit-linear-gradient(-360deg, orange, green);
  background-image:linear-gradient(-360deg,orange,green);
}

效果如下所示:

再说CSS3渐变——线性渐变

CodePen的案例

正如效果图10-5所示,在Safari浏览器下还不支持“to top”这样的关键词,而且在角度值的解析渐变也不同。“to top”所展示的是第一色从底部向顶部的第二色渐变,如上例所示:从orange向green渐变,而且是从底部向顶部直线渐变。

颜色从顶部向底部渐变(top→bottom)

“to top”实现了颜色从底部向顶部渐变,其有关键词“to bottom”刚好与“to top”实现的效果相反,可以实现从顶部向底部实现渐变效果。“to bottom”实现顶部向底部实现渐变也可以使用角度值:“180deg”和“-180deg”实现同等效果。

.toBottom {
  background-image:-webkit-linear-gradient(to bottom, orange, green);
  background-image:linear-gradient(to bottom,orange,green);
}
.toBottom-deg{
  background-image:-webkit-linear-gradient(180deg, orange, green);
  background-image:linear-gradient(180deg,orange,green);
}
.toBottom-deg2{
  background-image:-webkit-linear-gradient(-180deg, orange, green);
  background-image:linear-gradient(-180deg,orange,green);
}

其效果正好与“to top”效果相反,如图所示:

再说CSS3渐变——线性渐变

CodePen的案例

“to bottom”实现了第一色从顶部向底部的第二色直线渐变,正如此例所示,“orange”向“green”渐变,而且是从顶部向底部渐变。

颜色从右向左渐变(right→left)

“to left”关键词实现了从右向左颜色渐变,实现第一颜色从右向左实现第二颜色渐变。“to left”实现的效果也可以使用角度值“90deg”和“270deg”:

.toLeft {
  background-image:-webkit-linear-gradient(to left, orange, green);
  background-image:linear-gradient(to left,orange,green);
}
.toLeft-deg{
  background-image:-webkit-linear-gradient(-90deg, orange, green);
  background-image:linear-gradient(-90deg,orange,green);
}
.toLeft-deg2{
  background-image:-webkit-linear-gradient(270deg, orange, green);
  background-image:linear-gradient(270deg,orange,green);
}

效果如下图所示:

再说CSS3渐变——线性渐变

CodePen的案例

“to left”关键词实现了第一颜色从右向左第二颜色渐变。与“to left”关键词实现的等同效果还有“-90deg”和“270deg”。从图10-7效果中可以看出,“to left”实现了“green”(第二色)向“orange”(第一色)渐变效果,其方向是从左向右。

颜色从左向右渐变(left→right)

“to right”实现的效果正好与“to left”效果相反。实现了颜色从左向右直线渐变。与“to right”等同的效果也可以使用角度值:“90deg”和“-270deg”。

.toRight {
  background-image:-webkit-linear-gradient(to right, orange, green);
  background-image:linear-gradient(to right,orange,green);
}
.toRight-deg{
  background-image:-webkit-linear-gradient(90deg, orange, green);
  background-image:linear-gradient(90deg,orange,green);
}
.toRight-deg2{
  background-image:-webkit-linear-gradient(-270deg, orange, green);
  background-image:linear-gradient(-270deg,orange,green);
}

效果如下图所示:

再说CSS3渐变——线性渐变

CodePen的案例

“to right”关键词实现了第一颜色从左向右,实现线性渐变。也就是第一颜色的“orange”从左向右的第二颜色“green”渐变。

从右下角向左上角

“to top left”主要实现从右下角向左上角线性渐变。也就是第一颜色“orange”从右下角向左上角的第二颜色(green)实现线性渐变。

.toTopLeft {
  background-image:-webkit-linear-gradient(to top left, orange, green);
  background-image:linear-gradient(to top left,orange,green);
}
.toTopLeft-deg{
  background-image:-webkit-linear-gradient(315deg, orange, green);
  background-image:linear-gradient(315deg,orange,green);
}
.toTopLeft-deg2{
  background-image:-webkit-linear-gradient(-45deg, orange, green);
  background-image:linear-gradient(-45deg,orange,green);
}

效果如下图所示:

再说CSS3渐变——线性渐变

CodePen的案例

“to top left”实现了第一颜色从右下角向左上角第二颜色渐变,其效果与角度值“315 deg”和“-45deg”类似,但细节上略有不同,正如上图所示。

从左下角到右上角线性渐变

“to top right”关键词实现左下角到右上角的线性渐变。也就是第一颜色“orange”从左下角向右上角第二颜色(green)渐变。

.toTopRight {
  background-image:-webkit-linear-gradient(to top right, orange, green);
  background-image:linear-gradient(to top right,orange,green);
}
.toTopRight-deg{
  background-image:-webkit-linear-gradient(45deg, orange, green);
  background-image:linear-gradient(45deg,orange,green);
}
.toTopRight-deg2{
  background-image:-webkit-linear-gradient(-315deg, orange, green);
  background-image:linear-gradient(-315deg,orange,green);
}

效果如下图所示:

再说CSS3渐变——线性渐变

CodePen的案例

“to top right”关键词实现了第一色(orange)向第二色(green)从左下角向右上角实现线性渐变,其效果与角度值“315deg”和“-45deg”效果类似,但细节上略有不同。

从右上角到左下角线性渐变

“to bottom left”关键词实现了右上角向左下角直线渐变,也就是第一颜色(orange)从右上角向左下角第二颜色(green)渐变。

.toBottomLeft {
  background-image:-webkit-linear-gradient(to bottom left, orange, green);
  background-image:linear-gradient(to bottom left,orange,green);
}
.toBottomLeft-deg{
  background-image:-webkit-linear-gradient(225deg, orange, green);
  background-image:linear-gradient(225deg,orange,green);
}
.toBottomLeft-deg2{
  background-image:-webkit-linear-gradient(-135deg, orange, green);
  background-image:linear-gradient(-135deg,orange,green);
}

效果如下图所示:

再说CSS3渐变——线性渐变

CodePen的案例

“to bottom left”实现了第一颜色“orange”从右上角向左下角第二色(green)实现线性渐变。其效果与角度值“225deg”和“-135deg”效果类似,但细节上略有不同。

从左上角向右下角线性渐变

“to bottom right”关键词实现了左上角向右下角直线渐变,也就是第一颜色(orange)从左上角向右下角的第二颜色(green)渐变。

.toBottomRight {
  background-image:-webkit-linear-gradient(to bottom right, orange, green);
  background-image:linear-gradient(to bottom right,orange,green);
}
.toBottomRight-deg{
  background-image:-webkit-linear-gradient(135deg, orange, green);
  background-image:linear-gradient(135deg,orange,green);
}
.toBottomRight-deg2{
  background-image:-webkit-linear-gradient(-225deg, orange, green);
  background-image:linear-gradient(-225deg,orange,green);
}

效果如下图所示:

再说CSS3渐变——线性渐变

CodePen的案例

“to bottom right”关键词实现了第一颜色(orange)从左上角向右下角的第二颜色(green)线性渐变,其效果与角度值“225deg”和“-135deg”实现的效果类似,但在细节上略有不同。

上面演示了线性渐变的一些基本效果,主要演示了关键词与以对应的角度值实现的渐变效果。而且其中角度渐变取值多项,比如说“to top left”实现了右下角向左上角渐变。而与其实现同等效果的除了对应的角度值“315deg”和“-45deg”之外,还与“to left top”关键词实现的效果是一样的。换句话说“to top left”和“top left top”关键词实现的效果是相同的。按同样的原理,其他角度值也有相对应的关键词:

/*向左上角渐变*/
.toTopLeft {
  background-image:-webkit-linear-gradient(to top left, orange, green);
  background-image:linear-gradient(to top left,orange,green);
}
.toLeftTop {
  background-image:-webkit-linear-gradient(to left top, orange, green);
  background-image:linear-gradient(to left top,orange,green);
}
/*向右上角渐变*/
.toTopRight{
  background-image:-webkit-linear-gradient(to top right, orange, green);
  background-image:linear-gradient(to top right,orange,green);
}
.toRightTop {
  background-image:-webkit-linear-gradient(to right top, orange, green);
  background-image:linear-gradient(to right top,orange,green);
}
/*向左下角渐变*/
.toBottomLeft {
  background-image:-webkit-linear-gradient(to bottom left, orange, green);
  background-image:linear-gradient(to bottom left,orange,green);
}
.toLeftBottom {
  background-image:-webkit-linear-gradient(to left bottom, orange, green);
  background-image:linear-gradient(to left bottom,orange,green);
}
/*向右下角渐变*/
.toBottomRight {
  background-image:-webkit-linear-gradient(to bottom right, orange, green);
  background-image:linear-gradient(to bottom right,orange,green);
}
.toRightBottom {
  background-image:-webkit-linear-gradient(to right bottom, orange, green);
  background-image:linear-gradient(to right bottom,orange,green);
}

效果如下图所示:

再说CSS3渐变——线性渐变

CodePen的案例

综上所述:使用关键词实现的线性渐变效果可以从关键词的方向性来定,例如“to top”可以理解为“向上”线性渐变。简单点理解就是第一颜色从下向上的第二颜色线性渐变,正如前面的示例所示,“to top”也就是第一颜色“orange”从底部向顶部的第二颜色“green”线性渐变。

多色线性渐变

前面向大家演示的效果仅是一些简单的线性渐变(两色渐变),其实在实际中,渐变不仅仅是只有两种颜色,会有多色。接下来,我们一起来看一个从左向右的五彩渐变:

.toLeft {
  background-image:-webkit-linear-gradient(to left, red, orange,yellow,green,blue,indigo,violet);
  background-image:linear-gradient(to left, red, orange,yellow,green,blue,indigo,violet);
}
.toRight {
  background-image:-webkit-linear-gradient(to right, red, orange,yellow,green,blue,indigo,violet);
  background-image:linear-gradient(to right, red, orange,yellow,green,blue,indigo,violet);
}
.toTop {
  background-image:-webkit-linear-gradient(to top, red, orange,yellow,green,blue,indigo,violet);
  background-image:linear-gradient(to top, red, orange,yellow,green,blue,indigo,violet);
}
.toBottom {
  background-image:-webkit-linear-gradient(to bottom, red, orange,yellow,green,blue,indigo,violet);
  background-image:linear-gradient(to bottom, red, orange,yellow,green,blue,indigo,violet);
}

效果如下图所示:

再说CSS3渐变——线性渐变

CodePen的案例

三、线性渐变实际应用

现在我们很好的理解了如何声明线性渐变,下面让我们来声明自己的渐变。

在我们实现PSD转换Web页面时,有时候在页面的设计图中会包含渐变效果。为了实现设计效果图中的渐变效果一样,我们可以借助相关的制作软件,例如:Photoshop制图软件。通过制作软件打开设计原文件,并通过制图工具中渐变工具确定相关的渐变参数,如图所示:

再说CSS3渐变——线性渐变

通过Photoshop屏幕截图可以注意到,颜色从0%的不透明度开始,第一个色标的位置位于0%,其透明度为0%,第二个色标位置为80%的不透明度,位置位于8%。我们可以使用这个渐变工具从CSS声明中捕捉相关数据,我们可以实现自定义线性渐变:

.toLeft {
  background-image:-webkit-linear-gradient(
    to left, 
    rgba(255,0,0,0) 0%, 
    rgba(255,0,0,0.8) 7%, 
    rgba(255,0,0,1) 11%, 
    rgba(255,0,0,1) 12%, 
    rgba(255,252,0,1) 28%, 
    rgba(1,180,7,1) 45%, 
    rgba(0,234,255,1) 60%, 
    rgba(0,3,144,1) 75%, 
    rgba(255,0,198,1) 88%, 
    rgba(255,0,198,0.8) 93%, 
    rgba(255,0,198,0) 100%); 
  background-image:linear-gradient(
    to left, 
    rgba(255,0,0,0) 0%, 
    rgba(255,0,0,0.8) 7%, 
    rgba(255,0,0,1) 11%, 
    rgba(255,0,0,1) 12%, 
    rgba(255,252,0,1) 28%, 
    rgba(1,180,7,1) 45%, 
    rgba(0,234,255,1) 60%, 
    rgba(0,3,144,1) 75%, 
    rgba(255,0,198,1) 88%, 
    rgba(255,0,198,0.8) 93%, 
    rgba(255,0,198,0) 100%); 
  }
.toRight {
  background-image:-webkit-linear-gradient(
    to right, 
    rgba(255,0,0,0) 0%, 
    rgba(255,0,0,0.8) 7%, 
    rgba(255,0,0,1) 11%, 
    rgba(255,0,0,1) 12%, 
    rgba(255,252,0,1) 28%, 
    rgba(1,180,7,1) 45%, 
    rgba(0,234,255,1) 60%, 
    rgba(0,3,144,1) 75%, 
    rgba(255,0,198,1) 88%, 
    rgba(255,0,198,0.8) 93%, 
    rgba(255,0,198,0) 100%);
  background-image: linear-gradient(
    to right, 
    rgba(255,0,0,0) 0%, 
    rgba(255,0,0,0.8) 7%, 
    rgba(255,0,0,1) 11%, 
    rgba(255,0,0,1) 12%, 
    rgba(255,252,0,1) 28%, 
    rgba(1,180,7,1) 45%, 
    rgba(0,234,255,1) 60%, 
    rgba(0,3,144,1) 75%, 
    rgba(255,0,198,1) 88%, 
    rgba(255,0,198,0.8) 93%, 
    rgba(255,0,198,0) 100%);
}

效果如下图所示:

再说CSS3渐变——线性渐变

CodePen的案例

上图再证明,一个渐变可以包含多个色标,位置值为0~1之间的小数,或者0~100%之间的百分数,指定色标的位置比例,其用法与Photoshop中的值线渐变工具用法相似。

其实早期在站上有一篇关于渐变的教程《CSS3 Gradient》。只因里面的内容对于现在来使用,有一些落后了,因此在整理《CSS3核心技术》的时候,重新整理了渐变的相关知识。在这一篇中我们主要介绍了渐变中的线性渐变的一些基本语法和基本使用。其实里面还有一个实例没有放上来,因为担心文章过长。不过这样的案例在站上层出不穷,也不差这一个。下一篇我们继续渐变的话题,主要向大家介绍径向渐变的使用,感兴趣的同学可以观注相关更新。

使用 AngularJS & NodeJS 实现基于 token 的认证应用

这是一篇翻译文章来自于知乎

 

认证是任何 web 应用中不可或缺的一部分。在这个教程中,我们会讨论基于 token 的认证系统以及它和传统的登录系统的不同。这篇教程的末尾,你会看到一个使用 AngularJS 和 NodeJS 构建的完整的应用。

传统的认证系统

在开始说基于 token 的认证系统之前,我们先看一下传统的认证系统。

  1. 用户在登录域输入 用户名 和 密码 ,然后点击 登录 ;
  2. 请求发送之后,通过在后端查询数据库验证用户的合法性。如果请求有效,使用在数据库得到的信息创建一个 session,然后在响应头信息中返回这个 session 的信息,目的是把这个 session ID 存储到浏览器中;
  3. 在访问应用中受限制的后端服务器时提供这个 session 信息;
  4. 如果 session 信息有效,允许用户访问受限制的后端服务器,并且把渲染好的 HTML 内容返回。

0d018270d567ae23a87c3c33b12ea43e_b

在这之前一切都很美好。web 应用正常工作,并且它能够认证用户信息然后可以访问受限的后端服务器;然而当你在开发其他终端时发生了什么呢,比如在 Android 应用中?你还能使用当前的应用去认证移动端并且分发受限制的内容么?真相是,不可以。有两个主要的原因:

  1. 在移动应用上 session 和 cookie 行不通。你无法与移动终端共享服务器创建的 session 和 cookie。
  2. 在这个应用中,渲染好的 HTML 被返回。但在移动端,你需要包含一些类似 JSON 或者 XML 的东西包含在响应中。

在这个例子中,需要一个独立客户端服务。

基于 token 的认证

在基于 token 的认证里,不再使用 cookie 和session。token 可被用于在每次向服务器请求时认证用户。我们使用基于 token 的认证来重新设计刚才的设想。

将会用到下面的控制流程:

  1. 用户在登录表单中输入 用户名 和 密码 ,然后点击 登录 ;
  2. 请求发送之后,通过在后端查询数据库验证用户的合法性。如果请求有效,使用在数据库得到的信息创建一个 token,然后在响应头信息中返回这个的信息,目的是把这个 token 存储到浏览器的本地存储中;
  3. 在每次发送访问应用中受限制的后端服务器的请求时提供 token 信息;
  4. 如果从请求头信息中拿到的 token 有效,允许用户访问受限制的后端服务器,并且返回 JSON 或者 XML。

在这个例子中,我们没有返回的 session 或者 cookie,并且我们没有返回任何 HTML 内容。那意味着我们可以把这个架构应用于特定应用的所有客户端中。你可以看一下面的架构体系:

b12165f41aada0ca1129819b76fa2c9b_b

那么,这里的 JWT 是什么?

JWT

JWT 代表 JSON Web Token ,它是一种用于认证头部的 token 格式。这个 token 帮你实现了在两个系统之间以一种安全的方式传递信息。出于教学目的,我们暂且把 JWT 作为“不记名 token”。一个不记名 token 包含了三部分:header,payload,signature。

  • header 是 token 的一部分,用来存放 token 的类型和编码方式,通常是使用 base-64 编码。
  • payload 包含了信息。你可以存放任一种信息,比如用户信息,产品信息等。它们都是使用 base-64 编码方式进行存储。
  • signature 包括了 header,payload 和密钥的混合体。密钥必须安全地保存储在服务端。

你可以在下面看到 JWT 刚要和一个实例 token:

0990eb021dad8bb999249cd58c27bfc8_b

你不必关心如何实现不记名 token 生成器函数,因为它对于很多常用的语言已经有多个版本的实现。下面给出了一些:

NodeJS: auth0/node-jsonwebtoken · GitHub

PHP: firebase/php-jwt · GitHub

Java: auth0/java-jwt · GitHub

Ruby: progrium/ruby-jwt · GitHub

.NET: AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet · GitHub

Python: progrium/pyjwt · GitHub

一个实例

在讨论了关于基于 token 认证的一些基础知识后,我们接下来看一个实例。看一下下面的几点,然后我们会仔细的分析它:

d34c2b673f428f1307274f4ecce7846d_b

  1. 多个终端,比如一个 web 应用,一个移动端等向 API 发送特定的请求。
  2. 类似 [https://api.yourexampleapp.com](https://api.yourexampleapp.com) 这样的请求发送到服务层。如果很多人使用了这个应用,需要多个服务器来响应这些请求操作。
  3. 这时,负载均衡被用于平衡请求,目的是达到最优化的后端应用服务。当你向 [https://api.yourexampleapp.com](https://api.yourexampleapp.com) 发送请求,最外层的负载均衡会处理这个请求,然后重定向到指定的服务器。
  4. 一个应用可能会被部署到多个服务器上(server-1, server-2, …, server-n)。当有请求发送到[https://api.yourexampleapp.com](https://api.yourexampleapp.com) 时,后端的应用会拦截这个请求头部并且从认证头部中提取到 token 信息。使用这个 token 查询数据库。如果这个 token 有效并且有请求终端数据所必须的许可时,请求会继续。如果无效,会返回 403 状态码(表明一个拒绝的状态)。

优势

基于 token 的认证在解决棘手的问题时有几个优势:

  • Client Independent Services 。在基于 token 的认证,token 通过请求头传输,而不是把认证信息存储在 session 或者 cookie 中。这意味着无状态。你可以从任意一种可以发送 HTTP 请求的终端向服务器发送请求。
  • CDN 。在绝大多数现在的应用中,view 在后端渲染,HTML 内容被返回给浏览器。前端逻辑依赖后端代码。这中依赖真的没必要。而且,带来了几个问题。比如,你和一个设计机构合作,设计师帮你完成了前端的 HTML,CSS 和 JavaScript,你需要拿到前端代码并且把它移植到你的后端代码中,目的当然是为了渲染。修改几次后,你渲染的 HTML 内容可能和设计师完成的代码有了很大的不同。在基于 token 的认证中,你可以开发完全独立于后端代码的前端项目。后端代码会返回一个 JSON 而不是渲染 HTML,并且你可以把最小化,压缩过的代码放到 CDN 上。当你访问 web 页面,HTML 内容由 CDN 提供服务,并且页面内容是通过使用认证头部的 token 的 API 服务所填充。
  • No Cookie-Session (or No CSRF) 。CSRF 是当代 web 安全中一处痛点,因为它不会去检查一个请求来源是否可信。为了解决这个问题,一个 token 池被用在每次表单请求时发送相关的 token。在基于 token 的认证中,已经有一个 token 应用在认证头部,并且 CSRF 不包含那个信息。
  • Persistent Token Store 。当在应用中进行 session 的读,写或者删除操作时,会有一个文件操作发生在操作系统的temp 文件夹下,至少在第一次时。假设有多台服务器并且 session 在第一台服务上创建。当你再次发送请求并且这个请求落在另一台服务器上,session 信息并不存在并且会获得一个“未认证”的响应。我知道,你可以通过一个粘性 session 解决这个问题。然而,在基于 token 的认证中,这个问题很自然就被解决了。没有粘性 session 的问题,因为在每个发送到服务器的请求中这个请求的 token 都会被拦截。

这些就是基于 token 的认证和通信中最明显的优势。基于 token 认证的理论和架构就说到这里。下面上实例。

应用实例

你会看到两个用于展示基于 token 认证的应用:

  1. token-based-auth-backend
  2. token-based-auth-frontend

在后端项目中,包括服务接口,服务返回的 JSON 格式。服务层不会返回视图。在前端项目中,会使用 AngularJS 向后端服务发送请求。

token-based-auth-backend

在后端项目中,有三个主要文件:

  • package.json 用于管理依赖;
  • models\User.js 包含了可能被用于处理关于用户的数据库操作的用户模型;
  • server.js 用于项目引导和请求处理。

就是这样!这个项目非常简单,你不必深入研究就可以了解主要的概念。

{
    "name": "angular-restful-auth",
    "version": "0.0.1",
    "dependencies": {
        "express": "4.x",
        "body-parser": "~1.0.0",
        "morgan": "latest",
        "mongoose": "3.8.8",
        "jsonwebtoken": "0.4.0"
    },
    "engines": {
        "node": ">=0.10.0"
    }
}

package.json包含了这个项目的依赖:express 用于 MVC,body-parser 用于在 NodeJS 中模拟 post 请求操作,morgan 用于请求登录,mongoose 用于为我们的 ORM 框架连接 MongoDB,最后 jsonwebtoken 用于使用我们的 User 模型创建 JWT 。如果这个项目使用版本号 >= 0.10.0 的 NodeJS 创建,那么还有一个叫做 engines 的属性。这对那些像 HeroKu 的 PaaS 服务很有用。我们也会在另外一节中包含那个话题。

var mongoose     = require('mongoose');
var Schema       = mongoose.Scema;

var UserSchema   = new Schema({
    email: String,
    password: String,
    token: String
});

module.exports = mongoose.model('User', UserSchema);

上面提到我们可以通过使用用户的 payload 模型生成一个 token。这个模型帮助我们处理用户在 MongoDB 上的请求。在User.js,user-schema 被定义并且 User 模型通过使用 mogoose 模型被创建。这个模型提供了数据库操作。

我们的依赖和 user 模型被定义好,现在我们把那些构想成一个服务用于处理特定的请求。

// Required Modules
var express    = require("express");
var morgan     = require("morgan");
var bodyParser = require("body-parser");
var jwt        = require("jsonwebtoken");
var mongoose   = require("mongoose");
var app        = express();

在 NodeJS 中,你可以使用 require 包含一个模块到你的项目中。第一步,我们需要把必要的模块引入到项目中:

var port = process.env.PORT || 3001;
var User     = require('./models/User');
// Connect to DB
mongoose.connect(process.env.MONGO_URL);

服务层通过一个指定的端口提供服务。如果没有在环境变量中指定端口,你可以使用那个,或者我们定义的 3001 端口。然后,User 模型被包含,并且数据库连接被建立用来处理一些用户操作。不要忘记定义一个 MONGO_URL 环境变量,用于数据库连接 URL。

app.use(bodyParser.urlencoded({ extended: true }));
app.use(bodyParser.json());
app.use(morgan("dev"));
app.use(function(req, res, next) {
    res.setHeader('Access-Control-Allow-Origin', '*');
    res.setHeader('Access-Control-Allow-Methods', 'GET, POST');
    res.setHeader('Access-Control-Allow-Headers', 'X-Requested-With,content-type, Authorization');
    next();
});

上一节中,我们已经做了一些配置用于在 NodeJS 中使用 Express 模拟一个 HTTP 请求。我们允许来自不同域名的请求,目的是建立一个独立的客户端系统。如果你没这么做,可能会触发浏览器的 CORS(跨域请求共享)错误。

  • Access-Control-Allow-Origin 允许所有的域名。
  • 你可以向这个设备发送 POST 和 GET 请求。
  • 允许 X-Requested-With 和 content-type 头部。
app.post('/authenticate', function(req, res) {
    User.findOne({email: req.body.email, password: req.body.password}, function(err, user) {
        if (err) {
            res.json({
                type: false,
                data: "Error occured: " + err
            });
        } else {
            if (user) {
               res.json({
                    type: true,
                    data: user,
                    token: user.token
                }); 
            } else {
                res.json({
                    type: false,
                    data: "Incorrect email/password"
                });    
            }
        }
    });
});

我们已经引入了所需的全部模块并且定义了配置文件,所以是时候来定义请求处理函数了。在上面的代码中,当你提供了用户名和密码向 /authenticate 发送一个 POST 请求时,你将会得到一个 JWT。首先,通过用户名和密码查询数据库。如果用户存在,用户数据将会和它的 token 一起返回。但是,如果没有用户名或者密码不正确,要怎么处理呢?

app.post('/signin', function(req, res) {
    User.findOne({email: req.body.email, password: req.body.password}, function(err, user) {
        if (err) {
            res.json({
                type: false,
                data: "Error occured: " + err
            });
        } else {
            if (user) {
                res.json({
                    type: false,
                    data: "User already exists!"
                });
            } else {
                var userModel = new User();
                userModel.email = req.body.email;
                userModel.password = req.body.password;
                userModel.save(function(err, user) {
                    user.token = jwt.sign(user, process.env.JWT_SECRET);
                    user.save(function(err, user1) {
                        res.json({
                            type: true,
                            data: user1,
                            token: user1.token
                        });
                    });
                })
            }
        }
    });
});

当你使用用户名和密码向 /signin 发送 POST 请求时,一个新的用户会通过所请求的用户信息被创建。在 第 19 行,你可以看到一个新的 JSON 通过 jsonwebtoken 模块生成,然后赋值给 jwt 变量。认证部分已经完成。我们访问一个受限的后端服务器会怎么样呢?我们又要如何访问那个后端服务器呢?

app.get('/me', ensureAuthorized, function(req, res) {
    User.findOne({token: req.token}, function(err, user) {
        if (err) {
            res.json({
                type: false,
                data: "Error occured: " + err
            });
        } else {
            res.json({
                type: true,
                data: user
            });
        }
    });
});

当你向 /me 发送 GET 请求时,你将会得到当前用户的信息,但是为了继续请求后端服务器, ensureAuthorized 函数将会执行。

function ensureAuthorized(req, res, next) {
    var bearerToken;
    var bearerHeader = req.headers["authorization"];
    if (typeof bearerHeader !== 'undefined') {
        var bearer = bearerHeader.split(" ");
        bearerToken = bearer[1];
        req.token = bearerToken;
        next();
    } else {
        res.send(403);
    }
}

在这个函数中,请求头部被拦截并且 authorization 头部被提取。如果头部中存在一个不记名 token,通过调用 next()函数,请求继续。如果 token 不存在,你会得到一个 403(Forbidden)返回。我们回到 /me 事件处理函数,并且使用req.token 获取这个 token 对应的用户数据。当你创建一个新的用户,会生成一个 token 并且存储到数据库的用户模型中。那些 token 都是唯一的。

这个简单的例子中已经有三个事件处理函数。然后,你将看到;

process.on('uncaughtException', function(err) {
    console.log(err);
});

当程序出错时 NodeJS 应用可能会崩溃。添加上面的代码可以拯救它并且一个错误日志会打到控制台上。最终,我们可以使用下面的代码片段启动服务。

// Start Server
app.listen(port, function () {
    console.log( "Express server listening on port " + port);
});

总结一下:

  • 引入模块
  • 正确配置
  • 定义请求处理函数
  • 定义用来拦截受限终点数据的中间件
  • 启动服务

我们已经完成了后端服务。到现在,应用已经可以被多个终端使用,你可以部署这个简单的应用到你的服务器上,或者部署在 Heroku。有一个叫做 Procfile 的文件在项目的根目录下。现在把服务部署到 Heroku。

Heroku 部署

你可以在这个 GitHub 库下载项目的后端代码。

我不会教你如何在 Heroku 如何创建一个应用;如果你还没有做过这个,你可以查阅这篇文章。创建完 Heroku 应用,你可以使用下面的命令为你的项目添加一个地址:

git remote add heroku <your_heroku_git_url>

现在,你已经克隆了这个项目并且添加了地址。在 git add 和 git commit 后,你可以使用 git push heroku master 命令将你的代码推到 Heroku。当你成功将项目推送到仓库,Heroku 会自动执行 npm install 命令将依赖文件下载到 Heroku 的 temp 文件夹。然后,它会启动你的应用,因此你就可以使用 HTTP 协议访问这个服务。

token-based-auth-frontend

在前端项目中,将会使用 AngularJS。在这里,我只会提到前端项目中的主要内容,因为 AngularJS 的相关知识不会包括在这个教程里。

你可以在这个 GitHub 库下载源码。在这个项目中,你会看下下面的文件结构:

71122408bc5a0a1213f514bbd36fa9a7_b

ngStorage.js 是一个用于操作本地存储的 AngularJS 类库。此外,有一个全局的 layout 文件 index.html 并且在 partials 文件夹里还有一些用于扩展全局 layout 的部分。 controllers.js 用于在前端定义我们 controller 的 action。 services.js 用于向我们在上一个项目中提到的服务发送请求。还有一个 app.js 文件,它里面有配置文件和模块引入。最后,client.js 用于服务静态 HTML 文件(或者仅仅 index.html,在这里例子中);当你没有使用 Apache 或者任何其他的 web 服务器时,它可以为静态的 HTML 文件提供服务。

...
<script src="//cdnjs.cloudflare.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="//maxcdn.bootstrapcdn.com/bootstrap/3.2.0/js/bootstrap.min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/angular.js/1.2.20/angular.min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/angular.js/1.2.20/angular-route.min.js"></script>
<script src="/lib/ngStorage.js"></script>
<script src="/lib/loading-bar.js"></script>
<script src="/scripts/app.js"></script>
<script src="/scripts/controllers.js"></script>
<script src="/scripts/services.js"></script>
</body>

在全局的 layout 文件中,AngularJS 所需的全部 JavaScript 文件都被包含,包括自定义的控制器,服务和应用文件。

'use strict';

/* Controllers */

angular.module('angularRestfulAuth')
    .controller('HomeCtrl', ['$rootScope', '$scope', '$location', '$localStorage', 'Main', function($rootScope, $scope, $location, $localStorage, Main) {

        $scope.signin = function() {
            var formData = {
                email: $scope.email,
                password: $scope.password
            }

            Main.signin(formData, function(res) {
                if (res.type == false) {
                    alert(res.data)    
                } else {
                    $localStorage.token = res.data.token;
                    window.location = "/";    
                }
            }, function() {
                $rootScope.error = 'Failed to signin';
            })
        };

        $scope.signup = function() {
            var formData = {
                email: $scope.email,
                password: $scope.password
            }

            Main.save(formData, function(res) {
                if (res.type == false) {
                    alert(res.data)
                } else {
                    $localStorage.token = res.data.token;
                    window.location = "/"    
                }
            }, function() {
                $rootScope.error = 'Failed to signup';
            })
        };

        $scope.me = function() {
            Main.me(function(res) {
                $scope.myDetails = res;
            }, function() {
                $rootScope.error = 'Failed to fetch details';
            })
        };

        $scope.logout = function() {
            Main.logout(function() {
                window.location = "/"
            }, function() {
                alert("Failed to logout!");
            });
        };
        $scope.token = $localStorage.token;
    }])

在上面的代码中,HomeCtrl 控制器被定义并且一些所需的模块被注入(比如 $rootScope 和 $scope)。依赖注入是 AngularJS 最强大的属性之一。 $scope 是 AngularJS 中的一个存在于控制器和视图之间的中间变量,这意味着你可以在视图中使用 test,前提是你在特定的控制器中定义了 $scope.test=….。

在控制器中,一些工具函数被定义,比如:

  • signin 可以在登录表单中初始化一个登录按钮;
  • signup 用于处理注册操作;
  • me 可以在 layout 中生生一个 Me 按钮;

在全局 layout 和主菜单列表中,你可以看到 data-ng-controller 这个属性,它的值是 HomeCtrl。那意味着这个菜单的 dom 元素可以和 HomeCtrl 共享作用域。当你点击表单里的 sign-up 按钮时,控制器文件中的 sign-up 函数将会执行,并且在这个函数中,使用的登录服务来自于已经注入到这个控制器的 Main 服务。

主要的结构是 view -> controller -> service。这个服务向后端发送了简单的 Ajax 请求,目的是获取指定的数据。

'use strict';

angular.module('angularRestfulAuth')
    .factory('Main', ['$http', '$localStorage', function($http, $localStorage){
        var baseUrl = "your_service_url";
        function changeUser(user) {
            angular.extend(currentUser, user);
        }

        function urlBase64Decode(str) {
            var output = str.replace('-', '+').replace('_', '/');
            switch (output.length % 4) {
                case 0:
                    break;
                case 2:
                    output += '==';
                    break;
                case 3:
                    output += '=';
                    break;
                default:
                    throw 'Illegal base64url string!';
            }
            return window.atob(output);
        }

        function getUserFromToken() {
            var token = $localStorage.token;
            var user = {};
            if (typeof token !== 'undefined') {
                var encoded = token.split('.')[1];
                user = JSON.parse(urlBase64Decode(encoded));
            }
            return user;
        }

        var currentUser = getUserFromToken();

        return {
            save: function(data, success, error) {
                $http.post(baseUrl + '/signin', data).success(success).error(error)
            },
            signin: function(data, success, error) {
                $http.post(baseUrl + '/authenticate', data).success(success).error(error)
            },
            me: function(success, error) {
                $http.get(baseUrl + '/me').success(success).error(error)
            },
            logout: function(success) {
                changeUser({});
                delete $localStorage.token;
                success();
            }
        };
    }
]);

在上面的代码中,你会看到服务函数请求认证。在 controller.js 中,你可能已经看到了有类似 Main.me 的函数。这里的Main 服务已经注入到控制器,并且在它内部,属于这个服务的其他服务直接被调用。

这些函数式仅仅是简单地向我们部署的服务器集群发送 Ajax 请求。不要忘记在上面的代码中把服务的 URL 放到 baseUrl。当你把服务部署到 Heroku,你会得到一个类似 appname.herokuapp.com 的服务 URL。在上面的代码中,你要设置 var baseUrl = “appname.herokuapp.com”。

在应用的注册或者登录部分,不记名 token 响应了这个请求并且这个 token 被存储到本地存储中。当你向后端请求一个服务时,你需要把这个 token 放在头部中。你可以使用 AngularJS 的拦截器实现这个。

$httpProvider.interceptors.push(['$q', '$location', '$localStorage', function($q, $location, $localStorage) {
            return {
                'request': function (config) {
                    config.headers = config.headers || {};
                    if ($localStorage.token) {
                        config.headers.Authorization = 'Bearer ' + $localStorage.token;
                    }
                    return config;
                },
                'responseError': function(response) {
                    if(response.status === 401 || response.status === 403) {
                        $location.path('/signin');
                    }
                    return $q.reject(response);
                }
            };
        }]);

在上面的代码中,每次请求都会被拦截并且会把认证头部和值放到头部中。

在前端项目中,会有一些不完整的页面,比如 signin,signup,profile details 和 vb。这些页面与特定的控制器相关。你可以在 app.js 中看到:

angular.module('angularRestfulAuth', [
    'ngStorage',
    'ngRoute'
])
.config(['$routeProvider', '$httpProvider', function ($routeProvider, $httpProvider) {

    $routeProvider.
        when('/', {
            templateUrl: 'partials/home.html',
            controller: 'HomeCtrl'
        }).
        when('/signin', {
            templateUrl: 'partials/signin.html',
            controller: 'HomeCtrl'
        }).
        when('/signup', {
            templateUrl: 'partials/signup.html',
            controller: 'HomeCtrl'
        }).
        when('/me', {
            templateUrl: 'partials/me.html',
            controller: 'HomeCtrl'
        }).
        otherwise({
            redirectTo: '/'
        });

如上面代码所示,当你访问 /,home.html 将会被渲染。再看一个例子:如果你访问 /signup,signup.html 将会被渲染。渲染操作会在浏览器中完成,而不是在服务端。

结论

你可以通过检出这个实例看到我们在这个教程中所讨论的项目是如何工作的。

基于 token 的认证系统帮你建立了一个认证/授权系统,当你在开发客户端独立的服务时。通过使用这个技术,你只需关注于服务(或者 API)。

认证/授权部分将会被基于 token 的认证系统作为你的服务前面的层来处理。你可以访问并且使用来自于任何像 web 浏览器,Android,iOS 或者一个桌面客户端这类服务。

原文:Token-Based Authentication With AngularJS & NodeJS

Typescript中文教程-模块

1、第一步

让我们用我们通篇都用的这个例子来开始吧。我们将写一个最简单的验证器。就像当你检查一个用户给页面提交一个表单或检查外部提供的数据文件的格式时可能用到的那样。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
interface StringValidator {
    isAcceptable(s: string): boolean;
}
var lettersRegexp = /^[A-Za-z]+$/;
var numberRegexp = /^[0-9]+$/;
class LettersOnlyValidator implements StringValidator {
    isAcceptable(s: string) {
        return lettersRegexp.test(s);
    }
}
class ZipCodeValidator implements StringValidator {
    isAcceptable(s: string) {
        return s.length === 5 && numberRegexp.test(s);
    }
}
// Some samples to try
var strings = [‘Hello’, ‘98052’, ‘101’];
// Validators to use
var validators: { [s: string]: StringValidator; } = {};
validators[‘ZIP code’] = new ZipCodeValidator();
validators[‘Letters only’] = new LettersOnlyValidator();
// Show whether each string passed each validator
strings.forEach(s => {
    for (var name in validators) {
        console.log(‘”‘ + s + ‘” ‘ + (validators[name].isAcceptable(s) ? ‘ matches ‘ : ‘ does not match ‘) + name);
    }
});

 

2、添加模块

当我们添加更多的验证器时,我们想要一种能保持跟踪我们的类型并且不用担心和其他对象名发生冲突的组织结构。咱们把对象放进模块里而不用把很多不同的名字放进全局的命名空间里。

在这个例子里,我们把所有验证器相关的类型放进一个叫Validation的模块里。因为我们希望在模块外可见其类和接口,我们用关键字export引用之。相反的,变量lettersRegexp和numberRegexp是实现的细节,所以不会被引用并且在外部是不可见的。

Modularized Validators

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
module Validation {
    export interface StringValidator {
        isAcceptable(s: string): boolean;
    }
    var lettersRegexp = /^[A-Za-z]+$/;
    var numberRegexp = /^[0-9]+$/;
    export class LettersOnlyValidator implements StringValidator {
        isAcceptable(s: string) {
            return lettersRegexp.test(s);
        }
    }
    export class ZipCodeValidator implements StringValidator {
        isAcceptable(s: string) {
            return s.length === 5 && numberRegexp.test(s);
        }
    }
}
// Some samples to try
var strings = [‘Hello’, ‘98052’, ‘101’];
// Validators to use
var validators: { [s: string]: Validation.StringValidator; } = {};
validators[‘ZIP code’] = new Validation.ZipCodeValidator();
validators[‘Letters only’] = new Validation.LettersOnlyValidator();
// Show whether each string passed each validator
strings.forEach(s => {
    for (var name in validators) {
        console.log(‘”‘ + s + ‘” ‘ + (validators[name].isAcceptable(s) ? ‘ matches ‘ : ‘ does not match ‘) + name);
    }
});

 

3、分成多个文件

随着我们应用的增长,我们想将代码分成多个文件以易于维护。

这里我们将 Validation模块分成多个文件。甚至即使几个文件分割开来,在同一个地方定义的对同一个模块仍然互相有作用。因为有文件之间的依赖关系,我们添加了参考标签告诉编译器文件之间的关系。我们的测试代码仍然不变不变。

Validation.ts

1
2
3
4
5
module Validation {
    export interface StringValidator {
        isAcceptable(s: string): boolean;
    }
}

LettersOnlyValidator.ts

1
2
3
4
5
6
7
8
9
/// <reference path=”Validation.ts” />
module Validation {
    var lettersRegexp = /^[A-Za-z]+$/;
    export class LettersOnlyValidator implements StringValidator {
        isAcceptable(s: string) {
            return lettersRegexp.test(s);
        }
    }
}

ZipCodeValidator.ts

1
2
3
4
5
6
7
8
9
/// <reference path=”Validation.ts” />
module Validation {
    var numberRegexp = /^[0-9]+$/;
    export class ZipCodeValidator implements StringValidator {
        isAcceptable(s: string) {
            return s.length === 5 && numberRegexp.test(s);
        }
    }
}

Test.ts

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/// <reference path=”Validation.ts” />
/// <reference path=”LettersOnlyValidator.ts” />
/// <reference path=”ZipCodeValidator.ts” />
// Some samples to try
var strings = [‘Hello’, ‘98052’, ‘101’];
// Validators to use
var validators: { [s: string]: Validation.StringValidator; } = {};
validators[‘ZIP code’] = new Validation.ZipCodeValidator();
validators[‘Letters only’] = new Validation.LettersOnlyValidator();
// Show whether each string passed each validator
strings.forEach(s => {
    for (var name in validators) {
        console.log(‘”‘ + s + ‘” ‘ + (validators[name].isAcceptable(s) ? ‘ matches ‘ : ‘ does not match ‘) + name);
    }
});

 

4、编译

一旦涉及多个文件,我们需要确保所有的编译后的代码被加载。有两种方式实现。

首先,我们使用 –out 标签将所有输入文件串联起来成一个单独的 JavaScript 输出文件:

1
tsc –out sample.js Test.ts

编译器将按照文件内的参考标签自动排布文件顺序。你也可以单独制定各个文件:

1
tsc –out sample.js Validation.ts LettersOnlyValidator.ts ZipCodeValidator.ts Test.ts

我们可以使用文件编译(默认)为每个输入文件输出一个JavaScript文件。如果多个JS文件,我们需要在网页上使用<script>标签以合适的顺序加载输出文件。例如:

MyTestPage.html (excerpt)

1
2
3
4
  <script src=”Validation.js” type=”text/javascript” />
    <script src=”LettersOnlyValidator.js” type=”text/javascript” />
    <script src=”ZipCodeValidator.js” type=”text/javascript” />
    <script src=”Test.js” type=”text/javascript” />

 

5、使用外部模块

TypeScript 也有外部模块的概念。使用外部模块的两种情况:node.js 和require.js. 不使用 node.js和require.js 的应用不必使用外部模块也可以很好的用上面提到的内部模块来组织起来。

在外部模块,文件间的关系依照 imports和exports 在文件级别上指定。在TypeScript任何文件包含一个顶级import或export被认为是外部模块

下面,我们将之前的例子转变成使用外部模块的。注意我们不再使用module关键词,文件本身就是由其文件名构成的模块。

参考标签被换成 import声明来确认模块间的依存关系. Import声明有两部分:由文件名所确认的名字和require关键字所确认的模块路径。

1
import someMod = require(‘someModule’);

我们通过使用导出关键字在一个顶级声明指定哪些对象是可见的外部模块,类似于在内部模块里定义公共区域。

编译时,我们必须使用一个特殊的模块标记在命令行里。例如,node.js使用 –module commonjs;对于require.js使用 — uodele amd 。例如:

1
tsc –module commonjs Test.ts

当编译时,每个外部模块都将是一个独立的.js文件。跟参考标签一样,编译器将会根据输出声明来编译文件依存关系。

Validation.ts

1
2
3
export interface StringValidator {
    isAcceptable(s: string): boolean;
}

LettersOnlyValidator.ts

1
2
3
4
5
6
7
import validation = require(‘./Validation’);
var lettersRegexp = /^[A-Za-z]+$/;
export class LettersOnlyValidator implements validation.StringValidator {
    isAcceptable(s: string) {
        return lettersRegexp.test(s);
    }
}

ZipCodeValidator.ts

1
2
3
4
5
6
7
import validation = require(‘./Validation’);
var numberRegexp = /^[0-9]+$/;
export class ZipCodeValidator implements validation.StringValidator {
    isAcceptable(s: string) {
        return s.length === 5 && numberRegexp.test(s);
    }
}

Test.ts

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import validation = require(‘./Validation’);
import zip = require(‘./ZipCodeValidator’);
import letters = require(‘./LettersOnlyValidator’);
// Some samples to try
var strings = [‘Hello’, ‘98052’, ‘101’];
// Validators to use
var validators: { [s: string]: validation.StringValidator; } = {};
validators[‘ZIP code’] = new zip.ZipCodeValidator();
validators[‘Letters only’] = new letters.LettersOnlyValidator();
// Show whether each string passed each validator
strings.forEach(s => {
    for (var name in validators) {
        console.log(‘”‘ + s + ‘” ‘ + (validators[name].isAcceptable(s) ? ‘ matches ‘ : ‘ does not match ‘) + name);
    }
});

 

6、代码生成外部模块

根据编译时指定的目标模块,编译器会生为 node.js [commonjs]或 require.js [amd] 模块载入系统。更多关于生成代码时调用哪个定义和需求,查阅相应的模块载入器。

这个例子展示了输入和输出模块代码时时名字是如何被翻译的。

SimpleModule.ts

1
2
import m = require(‘mod’);
export var t = m.something + 1;

AMD / RequireJS SimpleModule.js:

1
2
3
define([“require”, “exports”, ‘mod’], function(require, exports, m) {
    exports.t = m.something + 1;
});

CommonJS / Node SimpleModule.js:

1
2
var m = require(‘mod’);
exports.t = m.something + 1;

 

7、Export =

在先前的例子里,当我们使用每个验证器时每个模块只输出一个值。在这样的情况下,当只使用一个符号也能很好的工作时使用这些他们限定的符号很麻烦。

export = syntax 指定一个模块里导出的单一对象。可以是类,接口,函数或枚举类型。当导入时,这个输出符号将被直接使用并且不会被任何名字限制。

下面,我们使用一个从每个模块里用export= syntax导出的对象简化Validator。这个简化代码简单的使用‘zipValidator’而不用’zip.ZipCodeValidator’。

Validation.ts

1
2
3
export interface StringValidator {
    isAcceptable(s: string): boolean;
}

LettersOnlyValidator.ts

1
2
3
4
5
6
7
8
import validation = require(‘./Validation’);
var lettersRegexp = /^[A-Za-z]+$/;
class LettersOnlyValidator implements validation.StringValidator {
    isAcceptable(s: string) {
        return lettersRegexp.test(s);
    }
}
export = LettersOnlyValidator;

ZipCodeValidator.ts

1
2
3
4
5
6
7
8
import validation = require(‘./Validation’);
var numberRegexp = /^[0-9]+$/;
class ZipCodeValidator implements validation.StringValidator {
    isAcceptable(s: string) {
        return s.length === 5 && numberRegexp.test(s);
    }
}
export = ZipCodeValidator;

Test.ts

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import validation = require(‘./Validation’);
import zipValidator = require(‘./ZipCodeValidator’);
import lettersValidator = require(‘./LettersOnlyValidator’);
// Some samples to try
var strings = [‘Hello’, ‘98052’, ‘101’];
// Validators to use
var validators: { [s: string]: validation.StringValidator; } = {};
validators[‘ZIP code’] = new zipValidator();
validators[‘Letters only’] = new lettersValidator();
// Show whether each string passed each validator
strings.forEach(s => {
    for (var name in validators) {
        console.log(‘”‘ + s + ‘” ‘ + (validators[name].isAcceptable(s) ? ‘ matches ‘ : ‘ does not match ‘) + name);
    }
});

 

8、别名

另一种可以简化这种模块是对常用对象使用 import q = x.y.z 这种短名字。不必为import x = require(‘name’) 这种载入模块的符号迷糊,这个语法创建了一个指定符号的别名。你可以使用任何一种符号给这种输入{普通参考至别名},包括输入的输出模块创建的对象。

Basic Aliasing

1
2
3
4
5
6
7
8
9
module Shapes {
    export module Polygons {
        export class Triangle { }
        export class Square { }
    }
}
import polygons = Shapes.Polygons;
var sq = new polygons.Square(); // Same as ‘new Shapes.Polygons.Square()’

 

9、使用其他JavaScript库

为了描述TypeScript没有的库的类型,我们需要声明这个库暴露的API。因为大多数JavaScript库只暴露一些顶层对象,模块是很好的表现他们的方法。我们称之为声明不能定义一个实现“环境”。通常这些定义在.d.ts文件中,如果你熟悉c或c++可以把这些想象成 .h或 ‘extern’。让我们看一下一些既有内部又有外部的例子。

内部模块环境

著名的库D3定义其功能在一个全局的对象叫做‘D3’里。因为这个库是通过一个 Script标签而不是一个模块载入的,其声明通过内部模块定义其形状。为了使TypeScript查看其形状,我们使用一个内部环境模块声明。例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
declare module D3 {
    export interface Selectors {
        select: {
            (selector: string): Selection;
            (element: EventTarget): Selection;
        };
    }
    export interface Event {
        x: number;
        y: number;
    }
    export interface Base extends Selectors {
        event: Event;
    }
}
declare var d3: D3.Base;

D3.d.ts (simplified excerpt)

外部环境模块

在 node.js,大多数任务通过载入一个或多个模块来完成。我们可以定义其每个自己的 .d.ts模块使用高级输出声明。但更方便一点的是将其写在一个长一点的.d.ts 文件里。为了这么做,我们引用模块的名字。其有可能成为一个长的输入。例子如下:

node.d.ts (simplified excerpt)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
declare module “url” {
    export interface Url {
        protocol?: string;
        hostname?: string;
        pathname?: string;
    }
    export function parse(urlStr: string, parseQueryString?, slashesDenoteHost?): Url;
}
declare module “path” {
    export function normalize(p: string): string;
    export function join(…paths: any[]): string;
    export var sep: string;
}

现在我们可以使用 /// <reference> node.d.ts 标签然后载入这个模块使用 例如

1
2
3
///<reference path=”node.d.ts”/>
import url = require(“url”);
var myUrl = url.parse(“http://www.typescriptlang.org”);

 

10、模块陷阱

这部分我们将讨论几种常见的使用内部和外部的模块陷阱和如何避免他们。

/// <reference> to an external module

一个常见的错误是使用///<reference> 标签来指定外部模块文件而不是使用import 。为了弄清楚其中的差别,我们先要了解三种编译器可以定位外部模块文件信息的方法。

提议中是找到一个用 import x = require(..) 声明命名的 .ts文件 。这个文件必须是一个有着顶级 import或export声明的文件。

第二种是找到一个.d.ts文件。跟上面一样,除了一个实际的文件,也要有顶级 import或export声明。

最后一种是使用“外部文件环境声明”。我们用一个合适的名字‘declare’之。

myModules.d.ts

1
2
3
4
// In a .d.ts file or .ts file that is not an external module:
declare module “SomeModule” {
    export function fn(): string;
}

myOtherModule.ts

1
2
/// <reference path=”myModules.d.ts” />
import m = require(“SomeModule”);

这个 reference标签让我们找到包含外部环境模块声明的文件。这是几个TypeScript例子里使用node.d.ts 文件的方法。

不必要的命名空间

如果你正把一个内部模块转换成外部模块,像下面这样来结束这个文件很容易:
shapes.ts

1
2
3
4
export module Shapes {
    export class Triangle { /* … */ }
    export class Square { /* … */ }
}

顶级模块形状shape包括三角Triangle 和四边形Square没什么用处。这将让使用你的模块的用户产生困惑和厌烦。

shapeConsumer.ts

1
2
import shapes = require(‘./shapes’);
var t = new shapes.Shapes.Triangle(); // shapes.Shapes?

TypeScript里外部模块的一个关键特性是两个外部模块从不贡献相同的命名范围。因为模块的用户决定如何设置他,没有必要在命名空间里主动的包含输出符号。

重申一下为什么不应该尝试给你的外部模块内容使用命名空间,命名空间的核心思想是保护本地组结构和防止命名冲突。因为外部模块本身就是一个本地组,他的顶级名称被其输入的代码所定义,所以没必要加一个额外的模块层给输出对象。

修订的例子:
shapes.ts

1
2
export class Triangle { /* … */ }
export class Square { /* … */ }

shapeConsumer.ts

1
2
import shapes = require(‘./shapes’);
var t = new shapes.Triangle();

 

权衡外部模块

就像JS文件和其模块有一对一的通信。TypeScript在额外模块源文件和他放出的JS文件也有一个一对一通信。其影响是不能使用 –out 编译器切换至链接多个外部模块源文件进一个单独的JavaScript文件。

Typescript中文教程-类

传统JavaScript关注功能和基于原型的继承为基本手段,建立可重用的组件,但这可能会觉得有点尴尬,对程序员来说当熟练使用面向对象的方法来开发时,类所继承的功能和对象是由其创建的。从ECMAScript 6,JavaScript程序员可以使用这种面向对象的基于类的方法构建应用程序。在typescript里,我们现在允许开发人员使用这些技术,无需等待下一个版本的JavaScript在所有主要的浏览器和平台JavaScript和编译它们。

1、类

让我们看看一个简单的基于类的例子:

1
2
3
4
5
6
7
8
9
10
11
class Greeter {
    greeting: string;
    constructor(message: string) {
        this.greeting = message;
    }
    greet() {
        return “Hello, ” + this.greeting;
    }
}
var greeter = new Greeter(“world”);

如果你用过C#或java的话对于以上语法会很熟悉。我们声明了一个新类‘Greeter’,这个类有三个成员,一个属性‘greeting’,一个构造器,一个方法‘greet’。
你需要注意的是当指定类的一个成员时在前面加上‘this’,这表示,这是一个成员的访问。
在最后一行我们构造一个Greeter的实例使用“new”。这要求我们前面定义的构造函数,创建一个新对象的Greeter类型,并运行构造函数来初始化它。

2、继承

在 TypeScript,我们可以使用常见的面向对象模式。当然,基于类的编程是最基本的。模式之一是能够使用继承扩展现有的类来创建新的

让我们看一个例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
class Animal {
    name:string;
    constructor(theName: string) { this.name = theName; }
    move(meters: number = 0) {
        alert(this.name + ” moved ” + meters + “m.”);
    }
}
class Snake extends Animal {
    constructor(name: string) { super(name); }
    move(meters = 5) {
        alert(“Slithering…”);
        super.move(meters);
    }
}
class Horse extends Animal {
    constructor(name: string) { super(name); }
    move(meters = 45) {
        alert(“Galloping…”);
        super.move(meters);
    }
}
var sam = new Snake(“Sammy the Python”);
var tom: Animal = new Horse(“Tommy the Palomino”);
sam.move();
tom.move(34);

这个例子中有很多其他语言所包含的继承特性。在这里,我们看到使用‘extends’的关键字来创建一个子类。你可以看到这个“Horse”和“Snake”子类基类‘Animal’,获得其功能。

示例还展示了能够覆盖基类中的方法与专门子类的方法。这里‘Snake’和‘Horse’都创建了一个‘move’方法覆盖了基类‘Animal’的‘move’,赋给每个类特定的功能。

3、私有/公有修改器

默认的公有

你可能注意到上面的例子我们没有使用‘public’关键字使类的每一个成员都可见。类似C#的语言需要明确的关键字‘public’来时每个成员可见。在TypeScript,默认每个成员都是公有的。

你还可以马克私有成员,所以你控制什么是公开可见的类。我们可以写前一节的“Animal”类如下:

1
2
3
4
5
6
7
class Animal {
    private name:string;
    constructor(theName: string) { this.name = theName; }
    move(meters: number) {
        alert(this.name + ” moved ” + meters + “m.”);
    }
}

 

4、智能化私有

TypeScript是一种结构化类型系统。当我们比较两种不同类型时,无需关注他们从哪里来,如果每个成员的类型相兼容,那么就说他们兼容。

当我们比较私有类型的成员时却有所不同。对于两种兼容的类型来说,如果其中一个是私有成员,那另一个也一定得是起源于相同的声明的私有成员。

让我们来看一个例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Animal {
    private name:string;
    constructor(theName: string) { this.name = theName; }
}
class Rhino extends Animal {
constructor() { super(“Rhino”); }
}
class Employee {
    private name:string;
    constructor(theName: string) { this.name = theName; }
}
var animal = new Animal(“Goat”);
var rhino = new Rhino();
var employee = new Employee(“Bob”);
animal = rhino;
animal = employee; //error: Animal and Employee are not compatible

在这个例子里,有两个类‘Animal’和‘Rhino’,‘Rhino’是‘Animal’的子类。我们还有一个新类‘Employee’,它的结构和‘Animal’完全相同。我们创建这些类的一些实例然后把他们互相赋值来看看会发生什么。由于‘Animal’和‘Rhino’都有来自‘Animal’的同一声明的私有属性名字(name):字符串(string),他们是兼容的。然而,对于‘Employee’是不同的。当我们试着将一个‘Employee’赋值给‘Animal’发生了一个类型不兼容的错误。甚至‘Employee’有同样的叫‘name’的私有成员,但这个跟‘Animal’里创建的并不是同一个。

5、参数属性

关键字“public”和“private”也给你一个通过创建参数属性为创建和初始化类的成员的简写。属性让您可以创建并初始化一个成员在同一步骤里。这里对前面的例子进一步修订。注意我们完全放弃“theName”,构造器里创建和初始化’name‘成员时只使用缩短的‘private name:string‘参数。

1
2
3
4
5
6
class Animal {
    constructor(private name: string) { }
    move(meters: number) {
        alert(this.name + ” moved ” + meters + “m.”);
    }
}

以这种方式使用“private”创建并初始化一个私有成员,公有成员也同理。

 6、存取标记

TypeScript支持getter / setter的拦截访问一个对象的成员。这将给你一种更精细的成员访问对象的方式。

让我们转换一个简单的类使用’get‘和’set‘的类。首先,我们从一个不用获取和设置的例子开始。

1
2
3
4
5
6
7
8
9
class Employee {
    fullName: string;
}
var employee = new Employee();
employee.fullName = “Bob Smith”;
if (employee.fullName) {
    alert(employee.fullName);
}

虽然允许人们直接改变fullName十分方便,但是如果人们可以随意改变名字这可能给我们带来麻烦。

在这种情况下,我们在修改Employee之前确保有个可用的密码。将直接进入fullName替换成检查密码的’set‘。我们添加一个相应的“get”让前面的示例无缝地继续工作。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
var passcode = “secret passcode”;
class Employee {
    private _fullName: string;
    get fullName(): string {
        return this._fullName;
    }
    set fullName(newName: string) {
        if (passcode && passcode == “secret passcode”) {
            this._fullName = newName;
        }
        else {
            alert(“Error: Unauthorized update of employee!”);
        }
    }
}
var employee = new Employee();
employee.fullName = “Bob Smith”;
if (employee.fullName) {
    alert(employee.fullName);
}

现在我们的访问器检查密码来验证我们自己,我们可以修改密码来看看当密码不匹配时警告框将告诉我们没有更新Employee的权限。

7、静态属性

到目前为止,我们只讨论了类的实例成员。当对象实例化的时候将会出现。我们也能建立类的静态static成员。将在类本身出现而不是类的实例里。在这个例子里,我们在’origin‘上使用’static‘,作为girds的通用值。每个实例通过类里预先设置好的名字来访问。跟’this‘类似,在静态实例访问处我们使用’Grid‘。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Grid {
    static origin = {x: 0, y: 0};
    calculateDistanceFromOrigin(point: {x: number; y: number;}) {
        var xDist = (point.x – Grid.origin.x);
        var yDist = (point.y – Grid.origin.y);
        return Math.sqrt(xDist * xDist + yDist * yDist) / this.scale;
    }
    constructor (public scale: number) { }
}
var grid1 = new Grid(1.0);  // 1x scale
var grid2 = new Grid(5.0);  // 5x scale
alert(grid1.calculateDistanceFromOrigin({x: 10, y: 10}));
alert(grid2.calculateDistanceFromOrigin({x: 10, y: 10}));

 

8、进阶技术

8.1构造函数

当你在TypeScript声明一个类,你实际上是同时创建多个声明。第一个是类的实例的类型。

1
2
3
4
5
6
7
8
9
10
11
12
13
class Greeter {
    greeting: string;
    constructor(message: string) {
        this.greeting = message;
    }
    greet() {
        return “Hello, ” + this.greeting;
    }
}
var greeter: Greeter;
greeter = new Greeter(“world”);
alert(greeter.greet());

在这里,当我们说“var greeter”,我们使用Greeter的类型实例的类。这几乎是其他面向对象语言的程序员的第二天性。

我们还创建另一个值,我们称之为constructor function当我们’new‘类的实例时的函数调用。在实践中看看这是什么样子,让我们看一下上面的例子创建的JavaScript:

1
2
3
4
5
6
7
8
9
10
11
12
13
var Greeter = (function () {
    function Greeter(message) {
        this.greeting = message;
    }
    Greeter.prototype.greet = function () {
        return “Hello, ” + this.greeting;
    };
    return Greeter;
})();
var greeter;
greeter = new Greeter(“world”);
alert(greeter.greet());

这里‘var Greeter’将设置构造函数。当我们调用‘new’操作符并运行该函数时,我们得到一个类的实例。构造函数也包含类的所有静态成员。另一种认识每个类的方法是其由实例instance和静态static两方面组成。

下面我们修改一下这个例子来看看这有什么不同:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class Greeter {
    static standardGreeting = “Hello, there”;
    greeting: string;
    greet() {
        if (this.greeting) {
            return “Hello, ” + this.greeting;
        }
        else {
            return Greeter.standardGreeting;
        }
    }
}
var greeter1: Greeter;
greeter1 = new Greeter();
alert(greeter1.greet());
var greeterMaker: typeof Greeter = Greeter;
greeterMaker.standardGreeting = “Hey there!”;
var greeter2:Greeter = new greeterMaker();
alert(greeter2.greet());

这个例子里,‘greeter’的工作方式和前面相同。我们实例化‘Greeter’类,然后使用这个对象。这在前面我们见过。

下一步,我们直接使用类。这里我们建一个变量叫做‘greeterMaker’。这个变量将会保存这个类自己,或者说这是另一种构造函数。这里我们使用‘typeof Greeter’,意思是‘把Greeter类他自己的类型给我’而不是实例化这个类型。或者更精确的说是“把符号Greeter的类型给我”,即构造函数的类型。这将包含创建Greeter类实例的构造函数的所有静态成员.。跟前面调用一样,我们通过使用‘new’操作符在‘greeterMaker’前面来创建新的‘Greeter’实例。

8.2使用一个类作为一个接口

正如我们在前一节中说,类声明创建两件事:一种代表类的实例和构造函数。因为类创建类型,你可以在使用接口的地方使用它:

1
2
3
4
5
6
7
8
9
10
class Point {
    x: number;
    y: number;
}
interface Point3d extends Point {
    z: number;
}
var point3d: Point3d = {x: 1, y: 2, z: 3};