Repository: hstarorg/HstarDoc Branch: master Commit: 0e54b7123b0e Files: 168 Total size: 618.9 KB Directory structure: gitextract_j4qofkey/ ├── .Net Platform/ │ ├── 01_Dotnet Core尝鲜.md │ ├── 02_Dotnet Core V2.md │ ├── C#中处理耗时任务的几种方式.md │ ├── [20140913]可替代反射的几种方式.md │ ├── 实战Asp.Net Core:DI生命周期.md │ ├── 实战Asp.Net Core:中间件.md │ ├── 实战Asp.Net Core:构建一个Core Lib.md │ └── 实战Asp.Net Core:部署应用.md ├── .gitignore ├── AngularJS相关/ │ ├── Angular1.x升级指南.md │ ├── AngularJS官方FAQ.md │ ├── AngularJS教程:1W字综合指南.md │ ├── AngularJS:Looking under the hood.md │ ├── Angular从0到1:function(上).md │ ├── Angular从0到1:function(下).md │ ├── Angular再回首(1)-Component组件.md │ ├── Angular再回首(2)-那些容易忽略的Component细节.md │ ├── Angular再回首(3)-我们来实现一个组件.md │ ├── Angular开发Tips.md │ ├── Angular:指令、Controller数据共享.md │ ├── [20140917]Angular:如何编写一个指令.md │ ├── 用AngularJS开发Web应用程序.md │ └── 详解angular之$q.md ├── Angular系列/ │ ├── 01_Angular2初体验.md │ ├── 02_Angular2组件生命周期.md │ ├── 03_Angular2的那些Decorator.md │ ├── 04_Angular2指令简析.md │ ├── 05_Angular2组件简析.md │ ├── 06_Angular2管道(Pipe)简析.md │ ├── 07_Angular2使用路由.md │ ├── 08_Angular2动态加载组件.md │ ├── 09_Angular2使用ui-router-ng2.md │ ├── Angular2知识点.xmind │ ├── Angular2踩坑大全.md │ ├── 利用Angular实现多团队模块化SPA开发框架.md │ └── 跟我学Angular2(1-初体验).md ├── CSS3学习之路/ │ ├── CSS3入门之文本与字体.md │ ├── CSS3入门之转换.md │ └── CSS3入门之边框与背景.md ├── Canvas学习札记/ │ └── 01_初识Canvas,绘制简单图形.md ├── ES6入门/ │ ├── ES6入门系列一(基础).md │ ├── ES6入门系列三(特性总览下).md │ ├── ES6入门系列二(特性总览上).md │ └── ES6入门系列四(测试题分析).md ├── GoLang学习笔记/ │ └── 01_开始GO.md ├── JS札记/ │ ├── ES6 Class如何管理私有数据.md │ ├── JS实现继承的几种方式.md │ ├── JavaScript之毒瘤.md │ ├── JavaScript之糟粕.md │ ├── JavaScript的深拷贝的实现.md │ ├── [20141121]JavaScript之Array常用功能汇总.md │ ├── 那些不常见的JavaScript题目(上).md │ └── 那些不常见的JavaScript题目(下).md ├── LICENSE ├── MongoDB入门基础/ │ ├── 01_记一次MongoDB裸奔.md │ └── 02_Mongo权限探索.md ├── Other/ │ ├── Go Go.md │ ├── NPM使用详解(上).md │ ├── NPM使用详解(下).md │ ├── Thrift简单实践.md │ ├── TypeScript札记:初体验.md │ ├── Windows下把Nginx,PM2包装为服务.md │ ├── 开发者讨厌你API的十个原因.md │ ├── 浅析12306前端优化点.md │ └── 程序集强签名.md ├── PHP学习之路/ │ ├── 01_PHP简易安装环境.md │ └── 02-PHP基础语法(上).md ├── README.md ├── React Native Cookbook/ │ ├── 章节一-new.md │ └── 章节一.md ├── React Native 开发笔记/ │ ├── RN Aspect-01-环境准备.md │ ├── RN Aspect-02-Hello React Native.md │ ├── RN Aspect-03-修改名称与icon.md │ ├── RN Aspect-04-打包App.md │ └── React Native开发之多屏适配.md ├── React面面观/ │ ├── JSX中的那些小细节.md │ ├── sources/ │ │ └── Redux交互流程.vsdx │ ├── 【译】参考手册-React组件.md │ ├── 【译】快速起步-JSX简介.md │ ├── 【译】快速起步-事件处理.md │ ├── 【译】快速起步-列表与KEY.md │ ├── 【译】快速起步-条件渲染.md │ ├── 【译】快速起步-渲染元素.md │ ├── 【译】快速起步-状态和生命周期.md │ ├── 【译】快速起步-状态提升.md │ ├── 【译】快速起步-组件与属性.md │ ├── 【译】快速起步-组合与继承.md │ ├── 【译】快速起步-表单.md │ ├── 【译】高级指南-不受控组件.md │ ├── 【译】高级指南-深入JSX.md │ └── 【译】高级指南-高阶组件.md ├── RxJS小记/ │ └── 02_RxJS之Observable.md ├── Sass学习之路/ │ ├── 01_Sass学习之路:Sass、Compass安装与命令行.md │ └── 02_Sass学习之路:注释、变量以及导入.md ├── Vue实践之路/ │ ├── 01_认识Vue.md │ └── 02_Vue组件(上).md ├── catalog_builder.js ├── jQuery拆解/ │ ├── 01-目录篇.md │ ├── 02-模块化加载&防冲突处理.md │ ├── 03-基础结构.md │ └── jQuery中那些有趣的代码.md ├── 从0开始Stylus/ │ └── 01_Stylus简介&基本使用.md ├── 从零开始H5/ │ ├── HTML5探索一(那些新增的标签和属性).md │ ├── 从零开始H5(一):升级你的HTML到HTML5.md │ └── 从零开始H5(二):HTML5新技术点.md ├── 前端相关/ │ ├── CORS详解.md │ ├── CSS布局(上).md │ ├── CSS布局(下).md │ ├── Google JavaScript Style Guide(上).md │ ├── Iframe跨域通信的几种方式.md │ ├── JSONP详解.md │ ├── JWT详解.md │ ├── Nginx常规用法解析.md │ ├── TypeScript配置文件tsconfig简析.md │ ├── VsCode简易配置手册.md │ ├── Web API接口之FileReader.md │ ├── Web API接口之Geolocation.md │ ├── Webpack In Angular2.md │ ├── Webpack初体验.md │ ├── Webpack小抄.md │ ├── Web前端基础测试题.md │ ├── Yarn vs. Npm.md │ ├── [20140311]前端构建之gulp与常用插件.md │ ├── [20141025]从0开始Grunt.md │ ├── [20150107]Web离线存储的几种方式.md │ ├── 一个元素实现3个回图形.md │ ├── 再说Promise.md │ ├── 前端模块化:RequireJS.md │ ├── 如何用Node编写命令行工具.md │ ├── 探索Decorator.md │ ├── 浏览器 Pointer Events.md │ ├── 浏览器关闭事件分析.md │ ├── 浏览器内容安全策略解析.md │ ├── 浏览器历史history对象.md │ ├── 简单学ES6.md │ ├── 认识AMD、CMD、UMD、CommonJS.md │ ├── 记一次Bug排查(Spider).md │ ├── 说说如何部署node程序.md │ ├── 这些年我们处理过的跨域.md │ ├── 那些容易出错的Dom操作.md │ └── 那些年我们认识的iframe.md ├── 微信小程序/ │ └── 微信小程序.xmind ├── 数据库之路/ │ ├── [20141114]这些年你需要注意的SQL.md │ └── 说说你所熟知的MSSQL中的substring函数.md ├── 最佳实践系列/ │ └── Express异步进化史.md ├── 正则表达式/ │ └── 你真的理解正则修饰符吗.md ├── 测试相关/ │ ├── MOCHA测试代码汇总.md │ ├── 使用chai-http实现API测试.md │ ├── 利用Karma、Mocha搭建测试环境.md │ └── 利用Nightwatch.js实现e2e测试.md ├── 编写高质量JS代码的68个有效方法-读书笔记/ │ ├── [20140926]编写高质量JS代码的68个有效方法(一).md │ ├── [20141011]编写高质量JS代码的68个有效方法(二).md │ ├── [20141030]编写高质量JS代码的68个有效方法(三).md │ ├── [20141129]编写高质量JS代码的68个有效方法(四).md │ ├── [20141205]编写高质量JS代码的68个有效方法(五).md │ ├── [20141213]编写高质量JS代码的68个有效方法(六).md │ ├── [20141220]编写高质量JS代码的68个有效方法(七).md │ ├── [20141227]编写高质量JS代码的68个有效方法(八).md │ ├── [20150110]编写高质量JS代码的68个有效方法(九).md │ ├── [20150123]编写高质量JS代码的68个有效方法(十).md │ ├── [20150214]编写高质量JS代码的68个有效方法(十一).md │ ├── [20150304]编写高质量JS代码的68个有效方法(十二).md │ └── [20150312]编写高质量JS代码的68个有效方法(十三).md └── 运维&部署/ ├── Docker容器管理平台Humpback进阶-私有仓库.md ├── Docker部署:Mysql Master-Slave.md ├── 一个简单易用的容器管理平台-Humpback.md ├── 前端监控系统实现.md └── 记一次Zookeeper数据找回.md ================================================ FILE CONTENTS ================================================ ================================================ FILE: .Net Platform/01_Dotnet Core尝鲜.md ================================================ --- title: 01_Dotnet Core尝鲜 date: 2017/02/21 14:47:10 --- # 0、About Dotnet Core `Dotnet Core` 是新一代的 `.Net Framework`,是一个具有跨平台能力的应用程序开发框架。它本身是由多个子项目组成的。包括 ``Core Fx``、``Core CLR``、``.Net Compiler Platform`` 等等。 `Dotnet Core` 具有高效的开发效率,高性能和跨平台能力,是 ``.Net平台`` 的一次大跃进。 # 1、尝试 Dotnet Core ### 1.1、Install ``Dotnet Core`` 从发布至今,已经有很长一段时间了。期间也发布了beta,rc等版本。就在前不久,正式版也已经发布了,经过了之前大量的api变化,现在core已经非常稳定了。这个阶段,已经值得我们去尝试、去使用它了。 要尝试 ``Dotnet core``, 我们先进入它的网站[https://dotnet.github.io/](https://dotnet.github.io/),[https://www.microsoft.com/net/core#windows](https://www.microsoft.com/net/core#windows) 。 根据我们的操作系统版本,选择合适的开发包。我这里是Windows下开发,理所当然的下载 [the .NET Core SDK for Windows](https://go.microsoft.com/fwlink/?LinkID=809122) 。 安装好之后,在命令行输入 ``dotnet --version`` ,如果输出了版本信息,那就表示安装成功了。 **注意:如果之前尝试过Dotnet Core,请保证在安装最新版本的SDK之前,先卸载干净。** ### 1.2、Console App 在安装好SDK之后,我们就可以开始创建项目了。新建一个文件夹,进入控制台,执行 ``dotnet new``,即可看到在目录下生成了如下文件结构: ``` Program.cs project.json ``` 内容如下: ```csharp //Program.cs using System; namespace ConsoleApplication { public class Program { public static void Main(string[] args) { Console.WriteLine("Hello World!"); } } } ``` ```json //project.json { "version": "1.0.0-*", "buildOptions": { "debugType": "portable", "emitEntryPoint": true }, "dependencies": {}, "frameworks": { "netcoreapp1.0": { "dependencies": { "Microsoft.NETCore.App": { "type": "platform", "version": "1.0.0" } }, "imports": "dnxcore50" } } } ``` 此时我们可以通过 ``dotnet restore`` 来安装依赖。 **注意:就算project.json中的dependencies属性为空对象,我们也要执行 ``dotnet restore``,该命令会生成project.lock.json文件。** **注意2:如果我们的网络环境的走的代理,那么可能会在安装依赖这个步骤遇到407错误,此时我们需要配置Nuget的代理设置,找到 ``%AppData%/NuGet/NuGet.Config`` 文件,然后添加如下配置项:** ```xml ``` **通过这样的设置,就可以使用代理来安装依赖了。** 当我们安装好依赖之后,通过执行 ``dotnet run`` 来编译和运行我们的程序,此时可以在控制台看到输出: ``Hello World!`` 以上,就是我们使用 ``Dotnet Core`` 的一般步骤了。 ### 1.3、Web App 在尝试了Console App之后,我们也来试试Web App 在 ``Dotnet Core`` 下是如何运行的。 首先,基本步骤如上,先创建一个项目模板。 接着,首先要使用Web功能,我们需要指定依赖,在project.json中的dependencies属性中增加依赖: ``` "dependencies": { "Microsoft.AspNetCore.Server.Kestrel": "1.0.0" } ``` **注意:在设定依赖的时候,一定要注意版本号,如果依赖库的版本号不兼容Core的版本,那么很可能会出现一些莫名其妙的错误,而找不到原因。** 增加依赖之后,我们再次通过 ``dotnet restore`` 来安装依赖。 这个时候,我们来编写Web宿主程序,内容如下: ```csharp //Program.cs using System; using Microsoft.AspNetCore.Hosting; namespace WebApplication { public class Program { public static void Main(string[] args) { var host = new WebHostBuilder() .UseKestrel() .UseStartup() //此处使用了类型Startup,来自于Startup.cs .UseUrls("http://localhost: 10000") .Build(); host.Run(); } } } ``` ```csharp //Startup.cs using System; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Http; namespace WebApplication { public class Startup { public void Configure(IApplicationBuilder app) { app.Run((context) => { return context.Response.WriteAsync("Hello, From Core."); }); } } } ``` 编写好如上代码之后,我们在执行 ``dotnet run``,访问 ``http://localhost:10000`` 就可以看到我们的Web程序已经成功运行起来了。 以上则是一个简单的Web程序所需要的代码。 # 2、Other ### 2.1、``Dotnet Core`` 与 ``Node.js`` 的性能测试。 在 ``Dotnet Core`` 的官方宣传中,号称比 ``Node.js`` 快8倍。实际上,通过计算从0到100000000的累加来看,两者的差距并不大(几十ms的差别,Node稍弱)。 由于时间环境关系,没有进行更复杂测试,但我想在高并发下,Node的机制可能会有更高的性能。在大量IO操作处理中,``Dotnet Core``会有绝对的优势【后续可验证】。 ### 2.2、如何直接运行Dotnet Core程序? 在开发模式下,我们通过 ``dotnet run`` 来运行程序,那我们如何来运行发布好的程序呢? 首先,我们可以通过 ``dotnet publish`` 来生成好我们的应用程序(在Windows下生成的是dll,其他平台未测试)。 在发布模式,我们的程序所依赖的包,也会被一同发布到目录下,我们可以在 ``/appRoot/bin/Debug/netcoreapp1.0/publish`` 中找到我们发布好的文件。 此时,我们就可以将publish目录拷贝到其他电脑运行了。 由于发布好的文件入口点是 ``.dll`` 文件,我们要运行它的话,需要通过 ``dotnet xxx.dll`` 来进行启动。 **注意:在publish的时候,我们可以使用参数``dotnet publish -c Release`` 生成Release版本的发布包,目录对应变更为 ``bin/Release/``** **注意2:请不要删除publish目录下的文件,否则可能导致无法运行。经测试,.deps.json 和 .pdb可以删除,但依赖和 .runtimeconfig.json是绝对不能删除的。** ### 2.3 后续 此文为 ``Dotnet Core`` 系列第一篇,后续计划将Web开发所需要用到的一些基本知识点,库等均在 ``Dotnet Core`` 调试通,且成文。 **加油,``Dotnet!``** **【6.30号更新】** ### 2.4、如何创建web项目 在 1.3 中,我们知道如何把一个Console App 改造为一个Web项目,但这对应开发一个Web应用来说还不够。 其实,``dotnet new`` 可以默认创建 Web 项目开发模板。 通过 ``dotnet new -t --help`` 我们可以看到 ``dotnet new`` 能帮我们创建的项目类型有如下四种: 1. Console 2. Web 3. Lib 4. xunittest 我们可以直接通过 ``dotnet new -t Web`` 来创建一个 Web 项目模板,简单快捷。 ### 2.5、如果在Linux下发布(CentOS7) ``Dotnet Core`` 开发的程序,具有跨平台能力,那如何在非Windows上发布呢?各大操作系统方式并不同。 在CentOS7(仅支持7+)上发布非常简单。 首先是在CentOS7上安装 ``Dotnet Core``,不知道如何安装?请查阅 [https://www.microsoft.com/net/core#centos](https://www.microsoft.com/net/core#centos) 。 安装好之后,只需要在Windows把开发好的程序,通过 ``dotnet publish`` 生成发布目录,然后将该目录拷贝到CentOS上即可。 最后,在CentOS上执行 ``dotnet xxx.dll`` 即可运行项目了。 **注意: xxx.dll是你开发的项目的主程序** ================================================ FILE: .Net Platform/02_Dotnet Core V2.md ================================================ --- title: 02_Dotnet Core V2 date: 2017-05-20 09:14:21 --- # 前言 时隔三个月,再来看 `.Net Core`,已经到 `2.x preview` 了,虽然这个版本有赶工的嫌疑,但并不妨碍它成为当前的主力版本(可用API翻倍,API相对更稳定)。 在 `.Net Core` 的开发中,可以采用 `VSC` 或者是 `VS`,在我个人体验来看,如果要开发稍大的(一个解决方案,多个子项目)项目,还是选择VS吧。对于`.Net Core` ,一个简单的编辑器,体验还是太弱了。 **注意:要在VS中使用Core2.x Preview,请安装 [VS 15.3 Preview](https://www.visualstudio.com/vs/preview/) 版本。** # V2命令行 在安装好 [.Net Core 2.0 Preview 1](https://www.microsoft.com/net/core/preview#windowscmd) 之后,通过 `dotnet new -h` 可以发现可以创建更多的模板了,其中一部分是项目模板: 1. Console Application (控制台应用程序) 2. Class Library (类库) 3. Unit Test Project (使用微软测试库的单元测试) 4. xUnit Test Project (使用xUnit作为测试库的单元测试) 5. ASP.NET Core Empty (Core的空项目) 6. ASP.NET Core Web App(MVC) (网站项目-Application) 7. ASP.NET Core Web App(Razor Pages) (网站项目 - WebPage,和Application不一样的是一个cshtml对应一个.cs,两者之前的区别类似.Net的WebApplication和WebPage) 8. ASP.NET Core Web API (Web API项目) 还有另外一部分是一些其他的单个文件,如: 1. Nuget Config 2. Web Config 3. Solution Config 4. Razor Page 5. MVC ViewImports 6. MVC ViewStart 我们最常用的还是上面的一些项目模板(如果用VS,这都不是事~) 除了这些,还提供了不少的命令行,不用去死记硬背,用的时候通过帮助命令查看使用说明即可,如 `dotnet -h` 列出所有的命令, `dotnet build -h` 列出命令的具体用法。 # Console App 在升级到2.0之后,曾今的 `project.json` 就不再使用了,回归到了 `.csproj` 这样的项目文件,但这个和 `.Net` 中的 `.csproj` 并不一样,最大的一个区别是,`.Net Core` 中,并不会将文件路径映射到 `.csproj` 中。这对于开发Web非常有好处(现代Web有构建等一大套工具链)。 通过 `dotnet new console -o ConsoleDemo` (创建一个控制台项目,并输出到ConsoleDemo目录中)创建一个项目,我们可以看到一个基本的 `ConsoleDemo.csproj` 内容如下: ``` Exe netcoreapp2.0 ``` 仅仅是标记一下我们的Framework版本。 **Console不做过多尝试,相信和.Net差不多。更多的,我们还是使用 `.Net Core` 来做Web应用。`` # Web Application 我们还是先使用常用的MVC方式创建一个WebApplication,`dotnet new mvc -o MvcDemo`,创建好之后,可以看到如下结构: ``` Controllers/ --标准MVC三大目录 Models/ --标准MVC三大目录 Views/ --标准MVC三大目录 wwwroot/ --此处用来放置静态资源 .bowerrc --Bower配置文件 appsettings.Development.json 特定环境配置文件(Development环境) appsettings.json 标准配置文件 bower.json -- Bower项目文件 bundleconfig.json -- 资源打包配置 MvcDemo.csproj -- 项目文件 Program.cs -- 自托管入口文件 Startup.cs -- 启动配置 ``` 其中,`bower` 相关和 `bundleconfig`,并没有什么大用,直接干掉。至于 `bower` 是什么,查看这里:[Bower官网](https://bower.io/)。 抛开MVC三大目录不说,我是建议创建一个src目录,用于放置相关的前端资源文件(JS,CSS,LESS,TS等),然后通过构建打包工具(gulp,webpack这类),将代码生成到 `wwwroot` 中。 另,需要注意项目配置文件 `appsettings.json` 是采用的类似继承的方式读取值,也就是说:最终的配置 = (appSettings.json).extend(appsettings..json)。如果将环境设置为 `Development`,那么会根据节点,先从 `appsettings.Development.json` 中找,找不到则从 `appsettings.json` 中找。 注意看 `Program.cs` 和 `Startup.cs` 的内容。其中 `Program.cs` 是非常独立的一个文件,仅依赖 `Startup.cs`,而 `Startup.cs` 则全是Web相关的配置文件。 看一下 `Program.cs` 的关键代码: ```csharp namespace MvcDemo { public class Program { public static void Main(string[] args) { BuildWebHost(args).Run(); } public static IWebHost BuildWebHost(string[] args) => WebHost.CreateDefaultBuilder(args) .UseStartup() .Build(); } } ``` 为什么是这样的结构呢?`.Net Core` 支持IIS托管和自托管。在 `.Net` 中,我们知道Web程序是不会有 `Program.cs`,也就没有 `Main` 方法的。 实际上,这个文件在 `.Net Core` 中的作用就是自托管Web程序,从它的内容中,可以理解到自托管就是利用控制台程序创建了一个WebHost,然后将我们Web程序的真正入口文件 `Startup` 传递到Web容器中运行。 在IIS中托管程序的话,这个 `Program.cs` 通过调试,发现也会执行,但其中的一些配置则不会生效,如:`.UseUrls("http://*:5000")`。 **注意:具体在IIS中是如何执行的,还有待分析。** ## Web Application QA 1、如何设置环境? 在上面的内容中,有提到实际配置和环境是有关系的,那我们如何设定环境呢?最简单的方式,就是在 `Program.cs` 中,通过如下方式设置: ```csharp public static IWebHost BuildWebHost(string[] args) => WebHost.CreateDefaultBuilder(args) .UseEnvironment("Development") // 设置环境 .UseStartup() .Build(); ``` 2、如果在自托管的时候设置多个IP/端口绑定? 同上,也是在 `Program.cs` 中进行配置: ```csharp public static IWebHost BuildWebHost(string[] args) => WebHost.CreateDefaultBuilder(args) .UseUrls("http://*:5000;http://*:6000") // 多个用分号隔开 .UseStartup() .Build(); ``` 3、如何在视图中根据不同的环境加载不同的内容? MVC为我们提供了相关的Helper,所以我们可以在View(一般是Layout视图)中,通过 `` 来实现不同的内容加载,如下: ```html ``` 4、既然有配置文件,那我们就需要读取配置,应该如何读取呢? 在 `.Net Core` 中,配置是以JSON字符串的方式存在的,那么我们怎么来读取呢? 在 `Startup.cs` 中,构造函数中,注入了一个 `IConfiguration` 对象,并赋值到了这个类的 `Configuration` 属性上,这个属性就类似于 `.Net` 下的 `ConfigurationManager`。所以我们可以用它来进行读取, 假设有如下配置文件: ```json { ...忽略默认配置, "v1": "xxx", "s2": { "v2": 1 }, "s3": { "Key1": "key1", "Key2": "key2" } } ``` 那么读取方式如下: ```csharp Configuration.GetValue('v1'); // 读取指定的配置项,返回 "xxx" Configuration.GetSection('s2').GetValue('v2'); // 从嵌套对象中读取配置项, 返回 1 ``` 当配置项过多的时候,这种方式可以比较麻烦,这个时候,我们可以创建一个配置类来映射,如下: ```csharp public class S3 { public string Key1{ get; set; } public string Key2{ get; set; } } // 然后我们可以获取到s3这个节点,然后绑定到S3上。 var s3 = new S3(); Configuration .GetSection("s3").Bind(s3); Console.WriteLine(s3.Key1); // 可以打印出 "key1" 了。 ``` # 总结 `.Net Core` 正在变得越来越好,赶紧跟上吧~ ================================================ FILE: .Net Platform/C#中处理耗时任务的几种方式.md ================================================ --- title: C#中处理耗时任务的几种方式 date: 2017/02/21 14:47:10 --- ## 0、准备 首先,我们先创建几个耗时任务: public class TestTasks { //无参、无返回值任务 public void Task1() { Console.WriteLine("task1."); Thread.Sleep(5000); Console.WriteLine("task1 completed."); } //有参数、无返回值任务 public void Task2(int x) { if (x < 2000) { x += 2000; } Console.WriteLine("task2."); Thread.Sleep(x); Console.WriteLine("task2 completed."); } //有参数,有返回值任务 public int Task3(int x) { if (x < 2000) { x += 2000; } Console.WriteLine("task3."); Thread.Sleep(x); Console.WriteLine("task3 completed."); return x; } } ## 1、创建新线程执行方法 var tt = new TestTasks(); new Thread(tt.Task1).Start(); //针对有参数的任务,需要用Lambda进行包装或者使用ParameterizedThreadStart对象 new Thread(x=>tt.Task2((int)x)).Start((object)1000); //使用ParameterizedThreadStart,要求要执行的方法参数必须为object,同时无返回值。 //new Thread(new ParameterizedThreadStart(tt.Task2)).Start((object)1000); **注意:使用该方式无法执行带返回值的方法。** **推荐指数:★★** ## 2、使用异步调用方式执行方法 var tt = new TestTasks(); Action ac = tt.Task1; Action ac2 = tt.Task2; ac.BeginInvoke(null, null); ac2.BeginInvoke(1000, null, null); //以下是调用有参数,有返回值的方法 //代码一 private delegate int AsyncCaller(int x); //该代码放在方法体外部 AsyncCaller ac = new AsyncCaller(tt.Task3); var asyncResult = ac.BeginInvoke(1000,null,null); int result = ac.EndInvoke(asyncResult); //接收返回值 //代码二,使用Func简化代码 Func ac = tt.Task3; var asyncResult = ac.BeginInvoke(1000,null,null); int result = ac.EndInvoke(asyncResult); **注意:通过这种方式生成新线程是运行在后台的(background),优先级为normal** **推荐指数:★★** ## 3、通过ThreadPool(线程池)执行方法 var tt = new TestTasks(); ThreadPool.QueueUserWorkItem(o => tt.Task1()); ThreadPool.QueueUserWorkItem(o => tt.Task2(1000)); **注意:该方式不支持返回值,可以将返回值保存在引入类型的参数上,然后进行迂回实现** **推荐指数:★★★** ## 4、通过BackgroundWorker(后台Worker)执行方法 var tt = new TestTasks(); var bw = new BackgroundWorker(); bw.DoWork += (sender, e) => tt.Task1(); bw.DoWork += (sender, e) => tt.Task2(1000); //要接收返回值,必须将返回值赋值给Result。 bw.DoWork += (sender, e) => e.Result = tt.Task3(1000); bw.RunWorkerAsync(); //注册事件使用返回值 bw.RunWorkerCompleted += (sender, e) => Console.WriteLine(e.Result); **注意:使用BackgroundWorker注册DoWork事件的任务只能挨个执行,如果要同时执行多个任务,需要多个BackgroundWorker。要使用返回值,一定要记得赋值给Result。** **推荐指数:★★** ## 5、同时Task执行方法 var tt = new TestTasks(); var t1 = Task.Factory.StartNew(tt.Task1); var t2 = Task.Factory.StartNew(() => tt.Task2(1000)); var t3 =Task.Factory.StartNew(() => tt.Task3(1000)); //等待t1,t2,t3执行完成 Task.WaitAll(t1,t2,t3); Console.WriteLine(t3.Result); **注意:Task具有灵活的控制能力,同时可以单个等待,多个等待。** **推荐指数:★★★★★** ## 6、使用async/await执行方法 private async void AsyncRunTask() { var tt = new TestTasks(); await Task.Factory.StartNew(tt.Task1); await Task.Factory.StartNew(() => tt.Task2(1000)); var result = await Task.Factory.StartNew(() => tt.Task3(1000)); Console.WriteLine(result); } AsyncRunTask(); Console.WriteLine("不用等待,我先执行了。"); **注意:需要Framework4.5的支持** **推荐指数:★★★★** ## The End **没有原理,没有言语,相信以大家聪明的大脑,已经学会如何在C#中执行耗时任务和使用多线程了。** ================================================ FILE: .Net Platform/[20140913]可替代反射的几种方式.md ================================================ --- title: C#可替代反射的几种方式 date: 2014/09/13 --- ##标准的反射代码## var type = obj.GetType(); var fieldInfo = type.GetField("age", BindingFlags.Instance | BindingFlags.NonPublic); fieldInfo.SetValue(obj, 20); // Console.WriteLine("设置年龄成功:{0}", (obj as ModelTest).Age); var s1 = type.InvokeMember("TestMethod1", BindingFlags.InvokeMethod, null, obj, null); var s2 = type.InvokeMember("TestMethod2", BindingFlags.InvokeMethod | BindingFlags.NonPublic | BindingFlags.Instance, null, obj, null); // Console.WriteLine(s1); // Console.WriteLine(s2); ###说明### 1. 能动态获取对象属性、方法、字段等信息 2. 能访问对象的私有字段,属性,方法等成员 3. 能动态修改对象属性 4. **注:**访问public方法使用BindingFlags.InvokeMethod,访问私有方法时,必须加上BindingFlags.NonPublic|BindingFlags.Instance,否则会出现找不到方法的异常 ##采用dynamic对象达到发射的效果## dynamic d = obj; var s1 = d.TestMethod1(); Console.WriteLine(s1); ###说明### 1. 可使用公开的属性,字段,方法等成员 2. 代码足够简洁 3. **注:**不能访问非公开的成员 ##依赖Microsoft的测试组件Microsoft.VisualStudio.TestTools.UnitTesting来达到反射的效果## var privateObj = new PrivateObject(obj); privateObj.SetField("age", 20); var age = privateObj.GetProperty("Age"); Console.WriteLine(age); privateObj.Invoke("TestMethod1"); privateObj.Invoke("TestMethod2"); ###说明### 1. 采用第三方组件实现 ##性能说明## 测试代码如下: private static void Main(string[] args) { RunSpecialTest(new SimpleReflection(), 1000); RunSpecialTest(new DynamicReflection(), 1000); RunSpecialTest(new PrivateObjectReflection(), 1000); Console.ReadKey(); } private static void RunSpecialTest(ITest test, int runCount) { var modelTest = new ModelTest(); var stopwatch = new Stopwatch(); stopwatch.Start(); for (int i = 0; i < runCount; i++) { test.TestRun(modelTest); } stopwatch.Stop(); Console.WriteLine("运行{0} {1}次,共耗时:{2}ms", test.Name, runCount, stopwatch.ElapsedMilliseconds); } 结果: 运行SimpleReflection 1000次,共耗时:2ms 运行DynamicReflection 1000次,共耗时:717ms 运行PrivateObjectReflection 1000次,共耗时:14ms ##疑问## 1. 采用标准的反射用法,除了第一次耗时较慢意外,后面耗时都很短,可以说是最快的方式,猜测是缓存,具体未知。 2. PrivateObjectReflection这个和发射有类似的情况,初次慢,后面快。何解? ================================================ FILE: .Net Platform/实战Asp.Net Core:DI生命周期.md ================================================ --- title: 实战Asp.Net Core:DI生命周期 date: 2018-11-30 21:54:52 --- # 1、前言 `Asp.Net Core` 默认支持 `DI(依赖注入)` 软件设计模式,那使用 `DI` 的过程中,我们势必会接触到对象的生命周期,那么几种不同的对象生命周期到底是怎么样的呢?我们拿代码说话。 **关于 DI 与 IOC:** 个人理解:`IOC(控制反转)` 是目的(降低代码、服务间的耦合),而 `DI` 是达到该目的的一种手段(具体办法)。 # 2、DI生命周期 DI的生命周期,根据框架、库的不同,会略有差异。此处,我们就以微软的DI扩展为例,来说下DI中常用的几种生命周期。 首先,我们想象一个这样一个场景。假设我们有寄快递的需求,那么我们会致电快递公司:“我们要寄快递,派一个快递员过来收货”。接着,快递公司会如何做呢? 1. 一直派遣同一个快递员来收货。 2. 第一周派遣快递员A、第二周派遣快递员B收货。 3. 每次都派遣一个新的快递员收货。 这对应到生命周期就是: 1. 单例(Singleton),单一实例,每次使用都是该实例。 2. 作用域实例(Scoped),在一个作用域(比如单次请求)内是同一个实例,不同的作用域实例不同。 3. 瞬时实例(Transient),每次使用都创建新的实例。 快递公司也就是我们在DI中常说的容器(Container)了。 ## 2.1、验证准备 首先,我们需要三个Services(Service1\Service2\Service3)内容一致,如下: ```c# // Service1.cs,Service2、Service3除类名以外,内容一致 public class Service1 { private int value = 0; public int GetValue() { this.value++; return this.value; } } ``` 然后,我们需要一个业务类,再一次注入这三个Service,内容如下: ```c# // DefaultBusiness.cs public class DefaultBusiness { private readonly Service1 s1; private readonly Service2 s2; private readonly Service3 s3; public DefaultBusiness(Service1 s1, Service2 s2, Service3 s3) { this.s1 = s1; this.s2 = s2; this.s3 = s3; } public int GetS1Value() { return this.s1.GetValue(); } public int GetS2Value() { return this.s2.GetValue(); } public int GetS3Value() { return this.s3.GetValue(); } } ``` 最后,还需要在Startup.cs进行注入 ```c# // Startup.cs public void ConfigureServices(IServiceCollection services) { // 单例模式 services.AddSingleton(); // 作用域模式 services.AddScoped(); // 瞬时模式 services.AddTransient(); // 为了保证结果,将Business类注册为瞬时模式,每次注入都是全新的。 services.AddTransient(); } ``` ## 2.2、验证单例(Singleton) 对于单例来说,我们期望,在整个程序启动期间,都是同一个实例,所以,我们只需要在Service中,增加一个局部变量,做累加就可以验证。 ```c# // DefaultController.cs [Route("singleton")] public IActionResult SingletonTest() { this.defaultBiz.GetS1Value(); return Json(new { s1 = s1.GetValue() }); } ``` 然后我们访问 `http://localhost:5000/singleton` 多次,输入如下: ```c# // 第一次 { s1: 2 } // 第二次 { s1: 4 } // 第三次 { s1: 6 } ``` 可以发现,每请求一次,value都会增加2。分析下怎么来的呢? 1. 首先是执行 `defaultBiz.GetValue()` 中,根据内部代码,此处会对注入的实例,value + 1。 2. 之后,`Json()`中的,`s1 = s1.GetValue()`,此处再一次增加了1。 3. 综上,一次请求,s1 中的value值会增加2,由于是单例模式,在整个服务运行期间,都是一个实例,所以,每次请求都会累加2。 ## 2.3、验证作用域实例(Scoped) ```c# // DefaultController.cs [Route("scoped")] public IActionResult ScopedTest() { this.defaultBiz.GetS2Value(); return Json(new { s2 = s2.GetValue() }); } ``` 然后我们访问 `http://localhost:5000/scoped` 多次,输入如下: ```c# // 第一次 { s2: 2 } // 第二次 { s2: 2 } // 第三次 { s2: 2 } ``` 从结果可以看出,每次请求的返回值是固定的,都为2,也就是证明了Service2中,value++执行了两次。对于执行value++的代码,只有 `defaultBiz.GetS2Value()` 和 `s2 = s2.GetValue()`,所以这两处操作的是同一个实例。这也就证明了,对于 `Scoped` 生命周期,在作用域(可以简单理解为单次请求,实际上并不准确,**注意,此处为考虑多线程的情况**)内,都是使用的同一个实例。在不同的请求之间,则是不同的实例。 ## 2.4、验证瞬时实例(Transient) ```c# // DefaultController.cs [Route("transient")] public IActionResult TransientTest() { return Json(new { s3 = s3.GetValue() }); } ``` 然后我们访问 `http://localhost:5000/transient` 多次,输入如下: ```c# // 第一次 { s3: 1 } // 第二次 { s3: 1 } // 第三次 { s3: 1 } ``` 从结果来看,每次请求的都是相同的返回值,`s3 = 1`,这说明了,两次操作的value++,是针对的不同实例。也就是每次使用 Service1,都是全新的实例。 # 3、扩展(Autofac DI 类库) Asp.Net Core中默认的DI,相对还是比较简单的,只有三个生命周期。对于时下比较的依赖注入库,一般都会有更多的生命周期,有些还会有生命周期事件可以监控。 以 `Autofac` 为例,该类库提供了如下一些生命周期,可以做到更精细化的控制: 1. 单次依赖(Instance Per Dependency)- 也就是Transient,每次获取实例都是全新的。 2. 单例(Single Instance) - 也就是单例,整个服务周期都是一个实例。 3. 作用域隔离的实例(Instance Per Lifetime Scope) - 也就是一个作用域一个,示例代码如下: ```c# // 先创建作用域 using(var scope1 = container.BeginLifetimeScope()) { for(var i = 0; i < 100; i++) { // 在作用域内,Resolve 的都是同一个实例 var w1 = scope1.Resolve(); } } // 创建另一个作用域 using(var scope2 = container.BeginLifetimeScope()) { for(var i = 0; i < 100; i++) { // 在作用域内,Resolve 的都是同一个实例,但是这个实例和 scope1 作用域中的 w1 不是同一个。 var w2 = scope2.Resolve(); } } ``` 4. 带标签的作用域隔离实例(Instance Per Matching Lifetime Scope) 5. 单次请求作用域实例(Instance Per Request) - 每个请求作为一个作用域。 6. 指定Owner的作用域实例(Instance Per Owned)- 对于同一个Owner,实例保持一致 7. 线程作用域实例(Thread Scope) **更多 Autofac 生命周期相关内容,请参考:[https://autofac.readthedocs.io/en/latest/lifetime/instance-scope.html](https://autofac.readthedocs.io/en/latest/lifetime/instance-scope.html)** # 4、总结 本文主要简单演示了 Asp.Net Core 中默认的几种服务生命周期效果,也抛砖引玉的说了下 Autofac 中的服务生命周期。合理的利用生命周期,可以减少对象的创建,提交程序性能。但是,用错了生命周期,则容易产生隐含的bug。在使用 DI 类库的时候,一定要理解清楚不同的生命周期的应用场景。 本文示例代码:[https://github.com/hstarorg/HstarDemoProject/tree/master/dotnet_demo/servicelifttime-demo/ServicelifttimeDemo](https://github.com/hstarorg/HstarDemoProject/tree/master/dotnet_demo/servicelifttime-demo/ServicelifttimeDemo) ================================================ FILE: .Net Platform/实战Asp.Net Core:中间件.md ================================================ ================================================ FILE: .Net Platform/实战Asp.Net Core:构建一个Core Lib.md ================================================ ================================================ FILE: .Net Platform/实战Asp.Net Core:部署应用.md ================================================ # 1、前言 某一刻,你已经把 .Net Core 的程序写好了。接下来,还可以做什么呢?那就是部署了。 作为一名开发工程师,如果不会部署自己开发的应用,那么这也是不完整的。接下来,我们就来说说,如何部署我们的 .Net Core 应用程序(主要是 Asp.Net Core 应用)。 # 2、Asp.Net Core 的部署方式 对于虚拟机中执行的语言来说,大都会有 SDK(Software Development Kit) 以及 XRE(X Runtime Environment)。对于 C#来说,也不例外。在 C#中,这两者的区别在于: - .Net Core SDK 用于开发者构建 App,包含有较多开发相关的工具包(实际上,SDK 也是包含 Runtime) - .Net Core Runtime 仅作为运行时使用,不包含开发工具包,体积较小。 既然要部署 Asp.Net Core,自然离不开 Runtime(如果装 SDK 也能跑,不过不推荐在运行环境装 SDK)。以下的部署方式的前提都是已经安装 Runtime 环境。 **下载地址:[https://dotnet.microsoft.com/download](https://dotnet.microsoft.com/download)** ## 2.1、控制台直接运行 Asp.Net Core 程序在发布后,会产生一个入口 dll 文件,要运行该程序,只需要通过 dotnet 命令执行该 dll 文件。所以,第一种方式就是直接找到 dll 文件,并使用 dotnet 命令来运行。(你说 dotnet 命令哪来的?安装了 Runtime 就有了。) ```bash # 进行控制台执行 dotnet xxx.dll ``` **优势:** 1. 足够简单,拷贝文件就开整。 2. 兼容多平台部署。 **缺陷:** 1. 想象一下,如果控制台关掉了,服务器重启了会怎样?此时需要手动重新去重新执行。(你说,可不可以设置为开机启动?如果创建一个批处理,放在启动项中,还是能做到的) ## 2.2、IIS 部署 用 .Net Framework 开发的应用,大家都比较熟悉用 IIS 来部署。那 .Net Core 呢?虽然两者的运行模式并不相同,但微软为了减少迁移难度,自然也提供了用 IIS 的部署方法。 与 Asp.Net 不同,ASP.NET Core 不再是由 IIS 工作进程(w3wp.exe)托管,而是使用自托管 Web 服务器(Kestrel)运行,IIS 则是作为反向代理的角色转发请求到 Kestrel 不同端口的 ASP.NET Core 程序中,随后就将接收到的请求推送至中间件管道中去,处理完你的请求和相关业务逻辑之后再将 HTTP 响应数据重新回写到 IIS 中,最终转达到不同的客户端(浏览器,APP,客户端等)。 如果要使用 IIS 部署 Asp.Net Core 程序,步骤如下: 1. 首先,需要安装 .Net Core 托管捆绑包,[点此下载捆绑包](https://www.microsoft.com/net/permalink/dotnetcore-current-windows-runtime-bundle-installer) 2. 进行 IIS 控制台,确保能在模块中找到 AspNetCoreModule 托管模块。(如果执行了步骤 1,未找到,可以尝试控制台执行 `iisreset`) 3. 按照常规部署 .Net Framework 程序的方式,创建应用,在选择应用程序池的时候,注意,一定要选择`无托管代码`,如图:![img](https://docs.microsoft.com/en-us/aspnet/core/host-and-deploy/iis/index/_static/edit-apppool-ws2016.png?view=aspnetcore-2.2) 4. 至此,就算部署成功了。 **优势:** 1. 学习成本低,和 .Net Framework 应用的部署方式保持类似。 2. 能够自动开机自启。 3. 可以在可视化界面中,配置端口域名绑定。 **劣势:** 1. 该方式仅能在 Windows 服务器上使用。 2. 通过 IIS 桥接一层,有性能损失。 **了解更多,请参考:[IIS 部署.Net Core 应用](https://docs.microsoft.com/zh-cn/aspnet/core/host-and-deploy/iis/?view=aspnetcore-2.2#install-the-net-core-hosting-bundle)** ## 2.3、部署为 Windows Service 在 2.2 的部署方式中,较大的缺陷就是性能损失。那么,有没有什么办法能够可以避开这个问题呢?。答案就是 Windows Service,通过 Windows Service,我们能够解决 2.1 中的开机启动和持久运行问题,也能避开 2.2 中的性能损失。具体如何做呢?如下提供一种方式(当然,也可以用其他方式来部署 Windows Service): 1. 借助 nssm 来管理 Windows Service,[Nssm](https://nssm.cc/),用法,请参考:[https://nssm.cc/usage](https://nssm.cc/usage) 2. 配置 Service 开机启动。 **优势:** 1. 高性能部署,稳定性好。 2. 支持开机启动。 **劣势:** 1. 仅能用于 Windows 服务器。 2. 引入了一个外包依赖 NSSM。 ## 2.4、Linux 部署 由于 .Net Core 天生支持跨平台,如果在廉价又稳定的 Linux 上部署 .Net Core 程序逐渐成为主流。对于 Linux 上的部署,和 Windows 上并没有什么区别。首先是安装 Runtime 环境,然后拷贝程序,并通过命令行运行。 再进一步,可以使用后台模式,让程序在后台运行。 更进一步,也可以效仿 Windows,把程序启动管理作为一个服务,来达到开机启动和灵活管理的目的。 ## 2.5、Docker 部署 作为当前个人认为的最棒的 .Net Core 应用部署方式,建议大家都了解下。 首先,是 Docker 的基本使用: 1. 编写 Dockerfile 2. 使用 `docker build` 构建镜像 3. 使用 `docker run` 创建容器并运行 好,我们来依次说明,对于 Docker 来说,需要先安装 Docker 环境。 接着,我们假设发布包路径如下: ```bash root-folder/ app/ # 发布包目录 xxx.dll # 程序入口点 Dockerfile # Dockerfile文件 ``` 然后针对该程序,编写如下 Dockerfile: ```bash # 根镜像 FROM microsoft/dotnet:2.2-runtime # 拷贝程序发布包 COPY app /app # 设置工作目录 WORKDIR /app # 导出的端口 EXPOST 80 # 程序运行命令 CMD ["dotnet", "xxx.dll"] ``` 接下来,通过在 `root-folder` 中执行 `docker build -t xxx:0.0.1 .` 来构建一个镜像。 接着,再通过 `docker run -it -p 8000:80 --name xxx-demo xxx:0.0.1` 来创建并运行容器。 这样,就可以通过 `http://localhost:8000` 来访问到你的应用程序了。 此处只是大概写下 Docker 部署的步骤,抛砖引玉。真正需要将其用于产线,还需要去学习下足够的 Docker 知识。 **额外提一下,如何选择基础镜像** > 对于 .Net Core 来说,一般有如下几类基础镜像: * sdk -- 相信这个都比较容易理解,就是包含了 .Net Core SDK。 * runtime -- 这个也相对容易理解,包含了.Net Core Runtime。 * runtime-deps --这个就不是很好理解, runtime? deps? 什么意思呢?就是说,这个连 Runtime都不是全的,需要你在打包的时候,选择自寄宿模式,把Runtime也打进去。 **综上,我个人推荐大家选择 runtime 这类作为基础镜像。** # 参考文档 1. [https://docs.microsoft.com/zh-cn/aspnet/core/host-and-deploy/iis/?view=aspnetcore-2.2#install-the-net-core-hosting-bundle](https://docs.microsoft.com/zh-cn/aspnet/core/host-and-deploy/iis/?view=aspnetcore-2.2#install-the-net-core-hosting-bundle) 2. [https://hub.docker.com/r/microsoft/dotnet](https://hub.docker.com/r/microsoft/dotnet) 3. [https://docs.microsoft.com/en-us/dotnet/core/docker/building-net-docker-images?view=aspnetcore-2.2](https://docs.microsoft.com/en-us/dotnet/core/docker/building-net-docker-images?view=aspnetcore-2.2) ================================================ FILE: .gitignore ================================================ .DS_Store Thumbs.db db.json *.log node_modules/ public/ .deploy*/ ================================================ FILE: AngularJS相关/Angular1.x升级指南.md ================================================ --- title: Angular1.x升级指南 date: 2017/02/21 14:47:10 --- ## 0、导言 Angular从1.2.x到1.3.x是一个大的跳跃。在1.3.x之后,也有1.4.x,1.5.x这么两个大版本。 Newkit从1.2.x跳跃到1.3.x经历过一次大的变化,在之后的较大版本升级中,基本上没有大多大改动。但是其中的差异点,我们也需要去了解。 1.3.x以前的版本,我们就不去深究了,毕竟现在不是主流,我们就从1.3.x开始,来看看Angular到底有了哪些变化。 ## 1、从1.3.x到1.4.x 从1.3到1.4,Angular的变化涉及到很多个方面,一一列举如下。 ### 1.1、动画 在1.4中,动画功能进行了很大的重构,但是API基本保持一致。在新的版本中,我们通过注入 ``$animationCss`` 来实现用JS创建CSS动画。 **1.1.1、定义CSS动画如下:** ```javascript angular.module('app', ['ngAnimate']) .animation('.slide-animation', ['$animateCss', function ($animateCss) { return { enter: function (element, doneFn) { var animation = $animateCss(element, { from: { background: 'black' }, to: { background: 'blue' }, duration: 10 // one second }); animation.start().done(doneFn); }, leave: function(element, doneFn){ var animation = $animateCss(element, { from: { fontSize: '12px' }, to: { fontSize: '25px' }, duration: 10 // one second }); animation.start().done(doneFn); } } }]); ``` 如何使用? ```html
AAAAAAAAAAA
``` 只需要在元素上设置一个class,然后当元素从隐藏到显示,则会执行 ``enter`` 动画,从显示到隐藏,则会执行 ``leave`` 动画。 **1.1.2、监听动画事件** ```javascript // < 1.4 element.on('$animate:before', function(e, data) { if (data.event === 'enter') { ... } }); element.off('$animate:before', fn); // 1.4+ $animate.on('enter', element, function(data) { //... }); $animate.off('enter', element, fn); ``` 在1.4版本之前,我们需要通过在element上去监听动画开始和结束,另外还必须要通过 ``data.event`` 来判断动画类型。 在1.4及以后,我们可以直接通过 ``$animate`` 来监控了。 **1.1.3、触发动画** ```javascript var $el = $('.slide-animation'); $animate.enter($el, $el.parent()).then(function(){ console.log('enter'); }); ``` 我们需要使用如上的方式,来手动启动动画,此时会触发元素的对应动画事件。 **注意:触发动画的回调中,如果要操作$scope,在<1.4中会失败,需要借助$apply,但在1.4+,就不需要了。示例如下:** ```javascript $animate.enter(element, elementParent).then(function() { $scope.$apply(function() { $scope.explode = true; }); }); // 1.4+ $animate.enter(element, elementParent).then(function() { $scope.explode = true; }); ``` **1.1.4、启用/禁用动画** ```javascript // < 1.4 $animate.enabled(false, element); // 1.4+ $animate.enabled(element, false); ``` 实现该操作的方法参数1.4+刚好和小于1.4相反。 ### 1.2、表单 **1.2.1、ngMessages** ```javascript
Your message is required
Your message is required
``` 在1.3版本中,``ng-messages-include`` 跟随在 ``ng-messages`` 元素上,这样并不灵活。 在1.4+中,``ng-messages-include`` 不允许更随在 ``ng-messages`` 元素上,必须放在内部,这样使得使用远程模板非常灵活。 另外,当存在多个form时,我们在使用ng-messages获取指定form的方法也有变化,如下: ```javascript // < 1.4
...
// 1.4 +
...
ctrl.getMessages = function($index) { return ctrl.form['field_' + $index].$error; } ``` **1.2.2、ngOptions** ngOptions仅仅只是内部实现变化,在使用上并没有多大差异。其中当遍历Object时,之前的版本是用的 ``for in``,导致输出的key是字符序的。新版本采用 ``Object.keys``,输出的key是定义时候的顺序。 另外当ngOptions表达式执行之后,将不会再触发ngOptions了。 **1.2.3、select** 在 **select** 元素中,这是一个非常大的变更。简单理解,如下:在1.3中,ngModel和<option>的value比较仅仅是 ``==``,所以 ``200 == '200' //true``。在1.4+中,比较方式成了 ``===``,所以 ``200 === '200' //false``。 这个时候我们可以通过如下方式处理: ```javascript ngModelCtrl.$parsers.push(function(value) { return parseInt(value, 10); // Convert option value to number }); ngModelCtrl.$formatters.push(function(value) { return value.toString(); // Convert scope value to string }); ``` 实现指令如下: ```javascript app.directive('convertNumber', function() { return { require: 'ngModel', link: function(scope, el, attr, ctrl) { ctrl.$parsers.push(function(value) { return parseInt(value, 10); }); ctrl.$formatters.push(function(value) { return value.toString(); }); } } }); ``` 当然,如果我们保证ngModel的值为string类型,那就没啥问题了(在不使用ng-value的情况下)。 **1.2.4、Form** 表单的变化,主要是name属性,在 < 1.4 的版本中,我们可以设置name为 "my:form1",在1.4+版本中,不在允许这种特殊的用法。 ### 1.3、模板相关 **1.3.1、ngRepeat** < 1.4 版本中,ng-repeat的遍历顺序是字母序。 1.4+版本中,顺序是有浏览器的 ``for in`` 来返回的。 **1.3.2、ngInclude** ```javascript // < 1.4
$scope.findTemplate = function(templateName) { return $sce.trustAsResourceUrl(templateName); }; // 1.4+ var templateCache = {}; $scope.findTemplate = function(templateName) { if (!templateCache[templateName]) { templateCache[templateName] = $sce.trustAsResourceUrl(templateName); } return templateCache[templateName]; }; // Or angular.module('myApp', []).config(function($sceDelegateProvider) { $sceDelegateProvider.resourceUrlWhitelist(['self', 'https://example.com/templates/**']) }); ``` ### 1.4、Cookie 在1.4+中,cookie增加了新的API: ```javascript get put getObject putObject getAll remove ``` 另外,``$cookieStore`` 将不推荐使用。 ### 1.5、HTTP 在1.4+中,$http的 ``transformRequest`` 将不允许修改请求header。但是我们可以使用如下方式进行动态的header设置: ```javascript $http.get(url, { headers: { 'X-MY_HEADER': function(config) { return 'abcd'; //you've got access to a request config object to specify header value dynamically } } }) ``` ### 1.6、Filter ``fliter``如果作用在非数组上,将会抛出一个异常,之前则是返回一个空数组。 ``limitTo``作用在不合法的value上,现在将会原样返回,之前是返回空对象,空数组。 ## 2、从1.4.x到1.5.x 从 1.4.x 到 1.5.x 是一个为升级ng2做准备的变更版本。 该版本主要是增加了一些功能,所以从1.4.x升级到1.5.x基本不需要太大的改动。 在 1.5.x 版本中,增加了一些接近ng2的新特性,如: 1. angular.component() //一种偏向于angular2风格的组件(特殊的指令) 2. $onInit //生命周期钩子 3. ngAnimateSwap // ngAnimate中一个新的指令 接着,我们来看下它的一些具体变更。 ### 2.1、Core **2.1.1、$parse** ``$parse`` 增加新特性,可以使用locals来覆盖原本的context。用法如下: ```javascript var context = { user: { name: 'Jay' } }; var locals = { user: { name1: 'Local Jay' } }; var getter = this.$parse('user.name'); console.log(getter(context)); // 'Jay' console.log(getter(context, locals)); // 'Local Jay' ``` **2.1.2、ngOptions** 如果元素上有 ``ngOptions`` 指令,那么 ``ngModel`` 指令也必须存在,否则就会抛出错误。 另外 ``ngOptions`` 接受假值('', 0, false, null) **2.1.3、orderBy** 对 ``undefined`` 或者 ``null`` 进行 ``orderBy`` 将会抛出错误。 ### 2.2、ngAria 取消了部分元素的可访问性设置。 ### 2.3 ngMessages 将 ``ngMessages`` 指令的优先级设置为1,如果有优先级低于1的指令有transclude功能,那么需要设置为更高的优先级。 ### 2.4、ngResource ``$resource`` 增加了 ``$cancelRequest()`` 方法。 ### 2.5、ngRoute 增加了 ``$resolveAs`` 配置属性,允许对 ``$resolve`` 指定别名。 ### 2.6、ngTouch 默认禁用了 ``ngClickOverrideEnabled``,在触摸屏上,可能还有300ms的延迟。 如果要启用,那么可以使用如下方式: ```javascript angular.module('myApp').config(function($touchProvider) { $touchProvider.ngClickOverrideEnabled(true); }); ``` 另外,建议使用FastClick来实现该功能。 需要注意,某些现代浏览器在某些场景下已经删除了300ms延迟: Chrome 和 Firefox for Android 发现设置了 ```` 将会删除300ms延迟。 IE当设置 ``touch-action`` 为 ``none`` 或者 ``manipulation`` 移除延迟。 **注意:这些变化并不影响ngSwipe指令**。 ## 3、总结 Angular1.x基本已经进入维护期了,很少会有新特性加入了。现在的重心在angular2,所以我们也可以优先对angular2做一些技术储备。 ================================================ FILE: AngularJS相关/AngularJS官方FAQ.md ================================================ --- title: AngularJS官方FAQ date: 2015/3/13 11:24:39 --- ## 相关:最佳实践,反模式 ### 1、为什么会觉得jQuery插件缺失? 请记住:当你在使用jQuery插件时,请在AngularJS之前加载jQuery库 **分析:** 因为AngularJS自带jqLite(可以理解为jQuery的精简版),如果先引入AngularJS的话,那么AngularJS会采用jqLite,而不是完整的jQuery库。 ### 2、如何从一个Controller中访问DOM元素? 不要从Controller中执行DOM的选择/遍历。HTML还没有被渲染。查一查”directive“ ### 3、为什么Angular说 controller/directive等缺失? 调用 `angular.module('myApp',[])` 总是会创建一个新的模块(同时干掉已有的重名模块)。相反,使用一个参数的方式调用 angular.module('myApp') 来引用已经存在的模块。 ### 4、如何渲染未转义的数据? ```javascript $sce.trustAsHtml(data) 如何禁用$sce? app.config(['$sceProvider', function($sceProvider) { $sceProvider.enabled(false); }]); ``` ### 5、当array/object/$resource-result变化时,应该如何监视? ```javascript $scope.$watch 有第三个参数设置来监视值变化(非引用变化) $watch(watchExpression, listener, [objectEquality]); [objectEquality]设置为true,则使用angular.equals对象相等,而不是使用引用相等比较。 ``` ### 6、怎样才能序列化表单数据提交? **不要这么做!** 不要尝试手动收集输入框值。仅仅只需要在每一个表单元素上附加 `ng-model="data.myField`,在需要使用的地方,使用 `$scope.data` 即可 ### 7、总是在 ng-models 上使用(.) =>最佳实践 ```html ``` ### 8、应该如何从 service 中访问 scope ? `$rootScope` 相当于 `ng-app` 标记,它能够被引导或者是服务注入,可以用于在所有的 scopes 上添加新功能和值 **注意:避免这样做--这相当于定义全局变量** ### 9、`module().factory()` 和 `module().service()` 不同点是什么? [查看讨论信息](https://groups.google.com/forum/?fromgroups#!topic/angular/56sdORWEoqg) ### 10、如何防止无样式的内容闪现(页面显示双大括号绑定表达式)? 在一些地方使用 `ng-bind` 来替换双括号表达式 ### 11、为什么 `` 不工作? 仅有的 `ng-*` 属性中,需要 `{{xxx}}` 的只有 `ng-src` 和 `ng-href`,因为最终的结果必须是一个字符串,不是一个表达式。所以其他的不能工作。 ### 12、嵌套 routes/views? 或许吧 ### 13、可以在行内指定模板或者是分部视图吗? 可以。可以采用 `` ,Angular会使用它来替换。 ### 14、 如何在 ngResource 地址中使用端口? 如下:$resource('example.com\\:8080') ### 15、为什么插件触发的change事件似乎不工作? Angular监视 [input](https://developer.mozilla.org/en-US/docs/Web/Events/input) 事件,不是'change' 事件。 ### 16、不要使用jQuery来切换crap(待定:无效元素),在行内使用一些变量标记。 ```html ...
``` ### 17、如何从DOM检查上查看 scope ? Google Chrome:安装 Batarang extension,检查一个DOM元素,然后在console中键入$scope Firefox/Firebug:检查一个DOM元素,然后在console中键入 `angular.element($0).scope()` 或者 `$($0).scope()` IE10+: 使用F12工具,检查一个元素。然后在console中键入 `angular.element($0).scope()` 或者 `$($0).scope()` ### 18、你有一些好的指令示例/库吗? [AngularUI](http://angular-ui.github.com/) 是非常棒的AngularJS工具集合(甚至是更好的示例代码) ### 19、IE? 针对IE8.0或者更早,你需要[阅读这个](https://docs.angularjs.org/guide/ie)和[使用这个](http://angular-ui.github.io/#ieshiv) ### 20、必须对路由使用#? 参考 [$locationProvider](https://docs.angularjs.org/api/ng/provider/$locationProvider) ### 21、你应该在尝试用指令包装jQuery插件前,优先尝试使用[AngularUI Passthru Directive (uiJq) ](http://angular-ui.github.io/#directives-jq) ### 22、为什么我的 $scope.$watch() 递归触发? 如果你在 $scope.$watch(newVal,oldVal)中改变 newVal ,它会重复触发。在 $watch 运行后,$scope 会重新评估,被观察对象将被重新触发。 ### 23、何时我需要使用 $scope.$apply() ? 仅仅需要在没有angular 事件/回调时 使用 $scope.$apply()。它通常不属于任何地方。 ### 24、启用了 html5Mode ,如何获取<a href />的后退行为? 如果你想一个链接能够全页面刷新,那么只需要在a标记上添加 target="_self" ### 25、如何 .preventDefualt() 或 .stopPropagation() ? 所有的 ng-click 和相关的绑定都注入了 $event 事件对象,你可以用它来调用 .preventDefualt(),甚至是对象传递给你的方法。 ### 26、AngularJS在我的Chrome扩展中不工作! 你需要使用 [ng-csp](http://docs.angularjs.org/api/ng.directive:ngCsp) ### 27、如何缓存 $http 和html 分部视图 ```javascript //使用装饰器,添加缓存功能 myAppModule.config(function($routeProvider, $provide) { $provide.decorator('$http', function($delegate){ var get = $delegate.get; $delegate.get = function(url, config){ url += (url.indexOf('?') !== -1) ? '?' : '&'; url += 'v=' + cacheBustVersion; return get(url, config); }; return $delegate; }); }); ``` ## 测试 ### 1、拒绝/解决一个 $q.defer() 不通过 你必须在处理它们的时候添加 `$scope.$apply()` ### 2、Jasmine spyOn() 不执行 spy'd 功能 不一定是AngularJS的问题,但是你需要追加 `.addCallThrough()` ================================================ FILE: AngularJS相关/AngularJS教程:1W字综合指南.md ================================================ --- title: AngularJS教程:1W字综合指南 date: 2015/3/13 11:24:39 --- # AngularJSj教程:1W字指南(译) **原文地址:**[http://www.airpair.com/angularjs](http://www.airpair.com/angularjs) ## 1、AngularJS简介 Angular 是用于编写引人注目的Web应用程序的是客户端 MVW JavaScript框架。它由Google创建好维护,(offers a futuristic spin on the web and its upcoming features and standards. Read more at http://www.airpair.com/angularjs#tY7q00WpGrTLB71Z.99) MVW 即 Model-View-Whatever,它是能在开发应用程序时,为我们提供灵活性的一种设计模式。我们可以选择MVC(Model-View-Controller)或者是MVVM(Model-View-ViewModel)方式。 本教程可以作为一个最终的资源来开始学习AngularJS,它的概念和它背后的API,同时能帮助您学习如何实现现代的Web应用程序。 AngularJS自身作为增强HTML的一个框架。它从多种语言包括JavaScript和服务端语言中获得灵感,使得HTML也成为了动态语言。这意味着我们获得了一个完全的数据数据方式来开发应用程序,不再需要刷新实体,更新DOM和其他费时任务如浏览器bug和不一致。我们可以只关注数据,让数据关心HTML的方式来编写我们的应用程序。 ## 2、JavaScript框架中的工程概念 AngularJS在处理提供数据绑定和其他工程概念上,和其他框架如Backbone.js和Ember.js采取了不同了做法。我们坚持使用熟悉的、令人喜欢的HTML,使Angular拦截它,并增强它。Angular将纯粹的JavaScript对象用于数据绑定,保证任何模型变化都会更新DOM。当模型值更新了一次,Angular会更新来自应用程序的状态来源对象。 ### 2.1、MVC 和 MVVM 如果你已经习惯了构建静态网站,你可能更熟悉手动一块一块的构建HTML,通过数据一遍一遍的打印相同的HTML。这可能是grid中的列,一个导航结构,一个链接列表或者是图片等等。在这个实例中,你需要习惯一点小东西的变化都需要手动更新HTML的痛苦,你必须更新模板来保持其他用途的一致性。你还要为每个导航项目杜绝相同的HTML。 深呼吸一下,通过Angular我们能实现恰当的关注点分离以及动态HTML。这意味着数据在模Model中,HTML是作为一个微小的模板被渲染为View,我们能使用Controller来连接它们两个,并驱动Model和View值的变化。 ================================================ FILE: AngularJS相关/AngularJS:Looking under the hood.md ================================================ --- title: AngularJS:Looking under the hood date: 2015/3/13 11:24:39 --- 原文地址:[https://www.binpress.com/tutorial/angular-js-looking-under-the-hood/153](https://www.binpress.com/tutorial/angular-js-looking-under-the-hood/153) **用AngularJS写得越多,你就越惊叹于它的神奇。我对Angular能做的一些奇妙的事情非常好奇,然后我决定分析它的源代码,看看我能否揭示它的一些秘密。我记录了我在23000多行Angular源码中发现的真正有用的,能够解释Angular先进(和隐藏)的方面的一些内容。** ## 1、Dependency Injection annotation process 依赖注入(DI)是除开用代码获取或创建依赖之外的一条不同的请求依赖的方式。简单的说,依赖是作为一个注入对象传递给我们的。Angular允许我们在我们的应用程序中通过像Controllers和Directives的方法来使用DI。我们能创建自己的依赖,同时允许Angular在请求它们的时候被注入。 在Angular中,一个最常用的被请求的依赖是 *$scope*。例如: function MainCtrl ($scope){ //access to $scope } angular.module('app').controller('MainCtrl', MainCtrl); 对于没有使用过Angular提供的依赖注入的JavaScript开发者来说,这看起来像一个局部变量名。实际上,它仅仅是我们所请求的依赖名称的一个占位符。Angular查找这些占位符,然后通过DI将它们转换为真正的依赖对象,让我们来仔细看看。 ### 方法参数 ### 直到我们压缩我们的应用前,方法参数都运行正常。当你压缩你的代码,你的方法定义将会用字符表示参数而不是单词-这意味着Angular不能找到你想要的!Angular使用了一个方式来解决,调用function的 *toString()* 方法。这将返回函数的字符串形式!接下来我们就能访问正在被请求的参数。Angular var FN_ARGS = /^function\s*[^\(]*\(\s*([^\)]*)\)/m; var FN_ARG_SPLIT = /,/; var FN_ARG = /^\s*(_?)(\S+?)\1\s*$/; var STRIP_COMMENTS = /((\/\/.*$)|(\/\*[\s\S]*?\*\/))/mg; Angular做的第一件事就是将函数转换为字符串,这是JavaScript中非常有用的一个特性。这将给我们一个字符串类型的函数,如: 'function MainCtrl ($scope) {...}' 接下来,Angular使用如下方法,移除所有的注释: fnText = fn.toString().replace(STRIP_COMMENTS, ''); 紧接着,Angular从处理好的function中分割参数来创建真正有用的部分, argDecl = fnText.match(FN_ARGS); Angular接下来使用 *.split()* 来移除空白字符,同时返回我们请求的参数数组。为了更完美,Angular使用了一个内部的forEach方法来迭代这个数组,并匹配参数名称然后将它们添加到 *$inject* 数组中。 forEach(argDecl[1].split(FN_ARG_SPLIT), function(arg) { arg.replace(FN_ARG, function(all, underscore, name) { $inject.push(name); }); }); 这是你能想象的一个昂贵的处理流程。对每个函数的4个正则查找和一很多转换会造成性能损耗。当我们得到了Angular抽象的 *$inject* 数组,我们可以直接切入且田填充 *$inject* 数组来保存Angular困难和开销时间长的操作。 ### $inject 对象 ### 我们可以通过在函数上添加 *$inject*属性来指定依赖自身,其中,如果存在的话,Angular使用DI注解。这是很容易的最可读的语法。例子如下: function SomeCtrl ($scope) { } SomeCtrl.$inject = ['$scope']; angular.module('app', []) .controller('SomeCtrl', ['$scope', SomeCtrl]); 这样节省了Angular的许多工作-替代了检查方法参数,或者是操纵数组(详情请查看下一章节:Array Arguments),它仅仅返回和运行指定的 *$inject* 数组。简单,高性能。 理想情况下,由于依赖注入在我们自己的时间和Angular的转换时间上开销很大,我们可以使用任务运行工具如Grunt.js或者是Gulp.js 来自动化注入任务或者是数组语法。 **Note:这个并没有实例化被依赖的所有服务,Angular所做的只是标注相关的名字-框架的其他部分关心对象注入。** ### Array Arguments 最后一个例子使用了我们通常看见的数组索引对应函数参数序号的语法,例如: ['$scope', function($scope){}] 数组的顺序是非常重要的,因为函数的参数将会按照同样的顺序,以此来避免依赖被错误的实例化和可能引发的错误。 function SomeCtrl ($scope, $rootScope) { } angular.module('app', []) .controller('SomeCtrl', ['$scope', ‘$rootScope’, SomeCtrl]); 我们需要做的是传递函数作为数组的最后一个项,Angular会删除这个函数,并遍历数组所注明的依赖名称,就好像我们创建的 *$inject* 属性。当Angular解析一个方法的时候,它会检查参数是不是一个数组,如果是,那么最后一项是函数,其他的则是依赖。 else if (isArray(fn)) { last = fn.length - 1; assertArgFn(fn[last], 'fn'); $inject = fn.slice(0, last); } ## 2、Factory vs Service ## Factory 和 Service 非常类似,但往往开发人员都难以理解它们。 当 *.service()* 已经实例化,那么 *new Service()* 将被引擎调用,返回一个新实例给我们。本质上,*。.service()* 是作为构造函数被使用的。 service 基本上是一个 factory,然而它是创建时被实例化,因为,你需要在 service 中使用this来注册变量和函数,来替代在factory中返回一个对象的方式。 factory 是非常接近面向对象中的“工厂模式”,当你注入了这个 factory ,你就获得了完整的方法,允许你创建你需要的新的实例-本质上是通过一个对象创建多个新对象。 你可以看下在Angular源码中的内部的工作: function factory(name, factoryFn) { return provider(name, { $get: factoryFn }); } function service(name, constructor) { return factory(name, ['$injector', function($injector) { return $injector.instantiate(constructor); }]); } ## 3、New $scope creation from $rootScope Angular中所有的scope都是 *$rootScope* 的下级。 *$rootScope* 是通过 *new Scope()*创建的,进一步的子 scope 是通过 *$scope.$new()* 创建的。 var $rootScope = new Scope(); 在 *$new* 方法里面,Angular设置了一个原型链来允许允许 scope 引用它们的父亲,它们的自己跟踪(作为生命周期),和以前的兄弟 scope 。 从下面的代码,如果你请求了一个隔离的 scope ,它会创建一个 *new Scope()* ,否则,它会创建一个从父级继承的子 scope 。 我省略了一些不必要的代码,但这里的是重点: $new: function(isolate) { var child; if (isolate) { child = new Scope(); child.$root = this.$root; } else { // Only create a child scope class if somebody asks for one, // but cache it to allow the VM to optimize lookups. if (!this.$$ChildScope) { this.$$ChildScope = function ChildScope() { this.$$watchers = null; }; this.$$ChildScope.prototype = this; } child = new this.$$ChildScope(); } child['this'] = child; child.$parent = this; return child; } 当你使用*$scope.$new()*来测试Controller的时候,这也是非常好的能了解测试目的。这有助于明确对我来说Angular是如何创建新的scope的,为什么用Angular mocks 模块来嘲笑测试驱动开发(TDD)。 ## 4、Digest Cycle Digest Cycle 经常作为 *$digest* 被我们看到,这是Angular双向绑定的能力。当一个模型值更新的时候,它会运行,检查它最后已知的值,如果值有变化,呼叫适当的监听器。这是基本的脏检查 - 它针对所有有可能的值来检查,如果是脏值,那么呼叫相关的监听器,直到他没有脏值。我们快速看一下它是如何工作的: $scope.name = 'Todd'; $scope.$watch(function() { return $scope.name; }, function (newValue, oldValue) { console.log('$scope.name was updated!'); } ); 当你调用 *$scope.$watch*,你注册了两件事。参数一是一个函数,返回你想要监视的值(当你提供一个字符串的时候,Angualr会将他转换为函数)。当 $digest 运行时,监视的参数将被调用,返回任何你想要的值。参数二是当你的参数一变化时,想要执行的函数。看一下Angular是怎样注册watch的。 $watch: function(watchExp, listener, objectEquality) { var get = $parse(watchExp); if (get.$$watchDelegate) { return get.$$watchDelegate(this, listener, objectEquality, get); } var scope = this, array = scope.$$watchers, watcher = { fn: listener, last: initWatchVal, get: get, exp: watchExp, eq: !!objectEquality }; lastDirtyWatch = null; if (!isFunction(listener)) { watcher.fn = noop; } if (!array) { array = scope.$$watchers = []; } // we use unshift since we use a while loop in $digest for speed. // the while loop reads in reverse order. array.unshift(watcher); return function deregisterWatch() { arrayRemove(array, watcher); lastDirtyWatch = null; }; } 这个函数推送你提供的参数到 scope 的 *$$watchers* 数组中,同时,返回一个方法允许你停止watch。 然后,每当*$scope.$apply*或者*$scope.$digest* 运行时,digest cycle将被运行。 ### 未完...待续... ================================================ FILE: AngularJS相关/Angular从0到1:function(上).md ================================================ --- title: Angular从0到1:function(上) date: 2017/02/21 14:47:10 --- ## 1、前言 Angular作为最流行的前端MV*框架,在WEB开发中占据了重要的地位。接下来,我们就一步一步从官方api结合实践过程,来学习一下这个强大的框架吧。 Note:每个function描述标题之后的★标明了该function的重要程度(1~5星)。 ## 2、function(上) Angular封装了一系列公共方法,帮助我们更简单的使用JS。这些就是Angular的function。 ### 2.1、angular.bind(★) angular.bind类似于Function.prototype.bind,实现函数柯里化,返回一个函数代理。eg: 函数原型 angular.bind(/*this对象*/self, /*要封装的function*/fn, /*参数列表*/args); //原始函数 function fun(arg1, arg2, arg3) { console.log(this); console.log(arg1); console.log(arg2); console.log(arg3); } fun('arg1', 'arg2', 'arg3'); //如果几个常用参数都是固定的,而且该函数被调用频繁,那么就可以包装一下 var fun2 = angular.bind(window, fun, 'arg1', 'arg2'); //新的调用方式 fun2('arg3'); ### 2.2、angular.bootstrap(★★★★) 用于使用angular执行渲染元素。也是angular的启动方法,如果没有在页面上指定ng-app,必须要手动调用该函数进行启动。 angular.bootstrap(/*Dom元素*/element, /*Array/String/Function*/[modules], /*Object*/[config]); //常规用法 angular.bootstrap(document, ['app']) //带配置项 angular.bootstrap(document, ['app'], {strictDi: true/*Defaults: false*/}) ### 2.3、angular.copy(★★★★★) Angular.copy用于复制对象,由于angular的双向绑定特点,那么如果直接操作$scope对象,那么很容易就会改变ui的显示,这个时候就需要借助angular.copy来创建一个对象副本,并进行操作。 //原型 angular.copy(source, [destination]); var obj = {a: 1}; var obj2 = angular.copy(obj); var obj3; angular.copy(obj, obj3); console.log(obj2 === obj) //false console.log(obj3 === obj) //false var obj4; //第二个和参数和返回值是相等的,而且第二个参数不管以前是什么,都会被重新赋值 var obj5 = angular.copy(obj, obj4); console.log(obj4 === obj5); //true ### 2.4、angular.element(★★★★) 等价与jQuery的选择器,如果在angular之前没有引入jQuery,那么就使用jqLite包装. angular.element('body'); //等价于 $('body'); //用法 var $body = angular.element('body'); ### 2.5、angular.equals(★★) 用于比较两个对象是否相等,如下示例的规则和JS有区别,注意识别。 var obj1 = {a: 1}; var obj2 = obj1; //引用一致,则相等 console.log(angular.equals(obj1, obj2)); // true obj2 = {a: 1}; //引用不一致,对象表现一致,则相等 console.log(angular.equals(obj1, obj2)); // true obj2 = {a: 1,$a: 2}; //对象比较时,忽略以$开头的属性 console.log(angular.equals(obj1, obj2)); // true obj1 = /aa/; obj2 = /aa/; //正则表达式表现相等,则相等 console.log(angular.equals(obj1, obj2)); // true //NaN与NaN比较,则相等 console.log(angular.equals(NaN, NaN)); // true ### 2.6、angular.extend(★★) 功能上和jQuery.extend没多大差异 //原型-第一个参数为目标,之后的参数为源,同时返回dst angular.extend(dst, src); //示例 var obj1 = {a: 1}; var obj2 = angular.extend(obj1, {a: 2}, {b: 3}); console.log(obj1) console.log(obj1 === obj2); //true ### 2.7、angular.forEach(★★★★★) angular.forEach用于遍历对象或者数组,类似于ES5的Array.prototype.forEach //原型 angular.forEach(obj, iterator, [context]); var values = {name: 'misko', gender: 'male'}; var arr = ['misko', 'male']; angular.forEach(values, function (value, key) { console.log(key + ' = ' + value); }); angular.forEach(arr, function (value, i) { console.log(i + ' = ' + value); }); //还可以传递this var obj = {}; angular.forEach(values, function (value, key) { obj[key] = value; }, obj); console.log(obj); ### 2.8、angular.fromJson(★★★) angular.fromJson将JSON字符串转换为JSON对象,注意,必须严格满足JSON格式,比如属性必须双引号,该函数内部实现是利用JSON.parse()。 //原型 angular.fromJson(/*string*/ jsonString) var jsonString = '{"p1": "xx", "p2": 1, "p3": true}'; var jsonObj = angular.fromJson(jsonString); console.log(jsonObj); ### 2.9、angular.toJson(★★★) angular.toJson可以将对象,数组,日期,字符串,数字转换为json字符串 //原型 angular.toJson(obj, pretty); var obj = {p1: 1, p2: true, p3: '2'}; var jsonString = angular.toJson(obj); console.log(jsonString); //美化输出格式(设置为true,默认采用2个字符缩进) jsonString = angular.toJson(obj, true); console.log(jsonString); //还可以设置缩进字符数 jsonString = angular.toJson(obj, 10); console.log(jsonString); ### 2.10、angular.identity(★) angular.identity返回该函数参数的第一个值。编写函数式代码时,非常有用(待使用)。 //官方示例 function transformer(transformationFn, value) { return (transformationFn || angular.identity)(value); }; //简单演示 var value = angular.identity('a', 1, true); console.log(value); // 'a' ### 2.11、angular.injector(★★) angular.injector能够创建一个injector对象,可以用于检索services以及用于依赖注入。 //原型,[strictDi]默认false,如果true,表示执行严格依赖模式, //angular则不会根据参数名称自动推断类型,必须使用['xx', function(xx){}]这种形式。 angular.injector(modules, [strictDi]); //定义模块A var moduleA = angular.module('moduleA', []) .factory('F1', [function () { return { print: function () { console.log('I\'m F1 factory'); } } }]); var $injector = angular.injector(['moduleA']) $injector.invoke(function (F1) { console.log(F1.print()); }); //此种写法会报错,因为是严格模式 var $injector2 = angular.injector(['moduleA'], true) $injector2.invoke(function (F1) { console.log(F1.print()); }); //可以采用如下写法 $injector2.invoke(['F1', function (F1) { console.log(F1.print()); }]); ### 2.12、angular.module(★★★★★) angular.module可以说是最常用的function了。通过它,可以实现模块的定义,模块的获取。 //定义模块A var moduleA = angular.module('moduleA', []); //定义模块B,模块B依赖moduleA var moduleB = angular.module('moduleB', ['moduleB']); //可以在第三个参数上设置配置函数 var moduleB = angular.module('moduleB', ['moduleB'], ['$locationProvider', function ($locationProvider) { console.log($locationProvider); }]); //等价于 var moduleB = angular.module('moduleB', ['moduleB']) .config(['$locationProvider', function ($locationProvider) { console.log($locationProvider); }]); //获取模块 angular.bootstrap(document, ['moduleB']); ================================================ FILE: AngularJS相关/Angular从0到1:function(下).md ================================================ --- title: Angular从0到1:function(下) date: 2017/02/21 14:47:10 --- ## 1、前言 ## 2、function(下) ### 2.13、angular.isArray(★★) ``angular.isArray``用于判断对象是不是数组,等价于``Array.isArray`` console.log(angular.isArray([])); // true console.log(angular.isArray({0: '1', 1: '2', length: 2})); // false ### 2.14、angular.isDate(★★) 通过判断toString.call(value)是不是等于'[object Date]' 来判断对象是个是一个Date对象. console.log(angular.isDate(new Date())); // true console.log(angular.isDate(223)); // false ### 2.15、angular.isDefined(★★) 判断对象或者属性是否定义 var obj = {a: 1, b: null, c: undefined}; console.log(angular.isDefined(obj.a)); // true console.log(angular.isDefined(obj.b)); // true console.log(angular.isDefined(obj.c)); // false console.log(angular.isDefined(obj.d)); // false ### 2.16、angular.isElement(★★) 此方法判断元素是不是一个元素(包含dom元素,或者jquery元素) console.log(angular.isElement(document.getElementsByTagName('body')[0])); // true console.log(angular.isElement($('body'))); // true ### 2.17、angular.isFunction(★★) 此方法判断对象是不是一个function ,等价于 typeof fn === 'function' console.log(angular.isFunction(new Function('a', 'return a'))); // true console.log(angular.isFunction(function(){})); // true console.log(angular.isFunction({})); // false ### 2.18、angular.isNumber(★★) 判断数字是否为number function isNumber(value) { return typeof value === 'number'; } ### 2.19、angular.isObject(★★) function isObject(value) { return value !== null && typeof value === 'object'; } ### 2.20、angular.isString(★★) function isString(value) { return typeof value === 'string'; } ### 2.21、angular.isUndefined(★★) function isUndefined(value) { return typeof value === 'undefined'; } ### 2.22、angular.lowercase(★★) 转换字符串为小写模式,如果参数不是字符串,那么原样返回 var lowercase = function(string) { return isString(string) ? string.toLowerCase() : string; }; console.log(angular.lowercase(1)); // 1 console.log(angular.lowercase('ABCdef')); // 'abcdef' ### 2.23、angular.uppercase(★★) 转换字符串为大写模式,如果参数不是字符串,那么原样返回 var uppercase = function(string) { return isString(string) ? string.toUpperCase() : string; }; console.log(angular.uppercase(1)); // 1 console.log(angular.uppercase('ABCdef')); // 'ABCDEF' ### 2.24、angular.merge(★★) 将多个对象进行深度复制,与extend()不同,merge将会递归进行深度拷贝。该拷贝是完全深拷贝,就连对象引用也不一样。 var o = {}; var obj1 = {a1: 1, a2: 2, a3: [1]}; var obj2 = {b1: [2], b2: /abc/}; var obj3 = [o]; var obj4 = {d: o}; var result = angular.merge({}, obj1, obj2, obj3); console.log(result); console.log(result[0] === o); // false console.log(result.d === o); // false ### 2.25、angular.noop(★★) 一个空函数,调试时非常有用。可以避免callback未定义引发的error。 function foo(callback) { var result = calculateResult(); (callback || angular.noop)(result); } ### 2.26、angular.reloadWithDebugInfo(★★) 启用DebugInfo,该设置优先级高于``$compileProvider.debugInfoEnabled(false)`` ================================================ FILE: AngularJS相关/Angular再回首(1)-Component组件.md ================================================ --- title: Angular再回首(1)-Component组件 date: 2017/02/21 14:47:10 --- ## 0、再谈组件 ``Component(组件)`` 在 ``Angular1`` 就已经有雏形了,那就是指令。在 ``Angular2`` 中,组件的概念被大大的强化,甚至是Angular2的核心概念。 在前端这么多年的演变中,组件也反哺到 ``Angular1``,成为 ``Angular1`` 的一种重要特性,在此之前,我们仅仅可以用 ``Directive`` 来实现类似组件的效果。 ## 1、Angular组件与指令 在 ``Angular 1.5.x`` 中,新增加了 ``angular.component`` 方法,用于实现组件的构造。 在此之前,我们可能用 ``angular.directive`` 来实现类似的效果。 这个时候我们可能就会疑惑,它们有什么区别呢? | Feature | Directive | Component | |---|---|---| | bindings | No | Yes (binds to controller) | | bindToController | Yes | (default: false) No (use bindings instead) | | compile function |Yes | No | | controller | Yes | Yes (default function() {}) | | controllerAs | Yes | (default: false) Yes (default: $ctrl)| | link functions | Yes | No| | multiElement | Yes| No| | priority| Yes| No| | require | Yes| Yes| | restrict |Yes| No (restricted to elements only)| | scope |Yes (default: false) |No (scope is always isolate)| | template |Yes |Yes, injectable| | templateNamespace| Yes| No| | templateUrl |Yes |Yes, injectable| | terminal| Yes| No| | transclude |Yes (default: false) |Yes (default: false)| 更多信息,请参考 [Angular 官方说明](https://docs.angularjs.org/guide/component) 从上表我们可以看出,对于 ``Directive``,``Component`` 从设计思路上更加完善,也更加纯粹。总得来说,组件显得更易理解,更简单易用。 ## 2、组件生命周期 在 ``angular.directive()`` 中,是没有生命周期这个概念的,我们无法在指令的特定阶段插入自己的逻辑。 但是在 ``angular.component()`` 中,则是具有特定的生命周期,以方便我们进行控制。 生命周期如下: 1. $onInit -- 指令初始化时执行(放置初始化代码) 2. $onChanges(changesObj) -- 组件数据变化时执行,并可获取变更对象 3. $doCheck() -- 执行变更检测时执行 4. $onDestroy() -- 组件释放时执行(放置清理代码) 5. $postLink() -- 类似后连接函数 (一般放置dom操作,因为此时组件已经渲染好) 实例: ```javascript ((angular, window) => { class AlertComponent { constructor() { } $onInit() { console.log('init'); } $onChanges(changesObj) console.log('change', changesObj); } $doCheck() { console.log('check'); } $onDestroy() { console.log('destroy'); } $postLink() { console.log('post link'); } } AlertComponent.$inject = []; // 配置依赖项 angular.module('components').component('jAlert', { templateUrl: 'components/alert/alert.html', // scope绑定语法,< 单向绑定(变量),@ 单向绑定(纯字符串), = 双向绑定,& 事件绑定 bindings: { menuData: '<' }, controller: AlertComponent, controllerAs: '$ctrl', require: '', transclude: false }); })(window.angular, window); ``` 在页面使用该指令后,可以在控制台看出如下输出: ``` init check post link N个check(脏检查) ``` 在切换路由,或者其他会删掉该组件的操作时,会看出控制台输出 ``destroy``。 如果中途有数据变化,控制台还会输出 ``change``。 这就是整个组件的生命周期。 ## 3、属性绑定 在 ``directive`` 中,我们要获取数据,一般会采用 ``$scope`` 传参,或者通过link函数来捕获参数。 在新的组件申明中,我们只需要通过 ``bindings`` 就可以实现复杂的参数绑定。 简单思考下,我们可能需要哪些绑定呢? 1. 双向绑定 (双向) 2. 单向绑定变量 (从外到内) 3. 单向绑定属性(字符串)(从外到内) 4. 输出绑定 (从内到外) 在组件的 ``bindings`` 属性中,我们也刚好有四种语法,来一一对应这四种绑定。 具体写法如下: ```javascript bindings: { model: '=', // 双向绑定 title: '@', // 单项绑定字符串(直接用组件上的属性值) key: '<', // 单项绑定变量,取到属性值,然后返回$scope[属性值] onClick: '&' // 输出绑定,执行外部函数 } ``` 假设组件标签为 ````,那么用法如下: ```javascript $scope = { model: '1', key: 'abc', onClick: () => { } }; ``` ```html ``` 此时,我们在组件中,就能获取到对应的值: ```javascript { model: 1, // 从scope中取 key: 'abc', // 从scope中取 title: 'Title', // 直接用string onClick: fn // 执行该onClick会触发外部函数$scope.onClick } ``` **注意:关于输出函数传递参数,需要有特定的写法(一定要注意!!!)** *在组件中的写法* 在组件中,要给该函数传参,必须使用: ```javascript this.onClick({ param1: 'xxx', param2: 'BBB' }); ``` 的写法,并建议参数名使用 ``$`` 开头,如:``$event``。 *在组件绑定中的写法* ```html ``` 注意onClick的写法,里面的参数名称,必须和组件中参数对象中的key匹配。 ## 4、给组件设定外部HTML 在使用组件过程中,我相信很容易遇到需要使用外部html的组件,如 ``Tabs, Panel`` 等,那我们给组件内部传入自定义的HTML呢? 这个时候,我们可以使用 ``ng-transclude`` ### 4.1、传递单个HTML片段 首先,主要在注册组件时,开启 ``transclude``(设置transclude为true),然后我们就可以在组件html中,设定占位符,有如下两种方式: ```html
``` 然后在使用组件的地方,就可以直接把要使用的HTML放在组件标记中,如: ```html 我会被传递到主键内部 ``` ### 4.2、传递多个HTML片段 以上,我们知道了如何传递单个HTML片段,但传递多个HTML片段也是非常有必要的,如 ``Dialog``组件, 我们很可能会传递 ``dialog-header``, ``dialog-body`` 等等,那此时又应如何呢? 这个场景,我们可以借助 ``ng-transclude`` 的 ``slot`` 功能实现, 首先,是占位符的变化,如下: ```html
``` 其次是组件配置的变化,因为有多个 ``transclude``,那么仅仅设置为 ``true``,就不太能满足需求了。 需要修改如下: ```javascript transclude: { header: '?panelHeader', // panelHeader表示内部标签,?表示是可选的 body: 'panelBody' // 没有问号,表示该节点必选 } ``` 接下来,就应该是调用时的改变,调用如下: ```html 我是Panel Header(可选) 我是Panel Body(必须) ``` ## 5、组件 ``require`` 同 ``Directive`` 一样,组件也可以相互依赖,只需要在注册组件时,设置require属性即可,写法如下: ```javascript require: { componentCtrl: '^parentComponent' } ``` ## 6、小结 新增的 ``angular.component`` 就是这么一个东西,比起 ``directive`` 更加纯粹,更加强大,更加易用。 建议在后续使用中,多多尝试该方式。 ================================================ FILE: AngularJS相关/Angular再回首(2)-那些容易忽略的Component细节.md ================================================ --- title: Angular再回首(2)-那些容易忽略的Component细节 date: 2017/02/21 14:47:10 --- ## 0、前言 在 ``Angular 1.5.x`` 中,增加的组件方法,相当实用和易用。但也有许多小细节问题值得注意, 以下为本人在组件实践过程中遇到的问题,或者是需要注意的小细节。 ## 1、问题/小细节(需要注意的点) ### 1.1、如何判断是否添加了可选的 ``transclude`` 元素? 在很多时候,我们会给一个组件设定多个 ``transclude``,可能其中有一部分是可选的,那如何判断可选的 ``transclude`` 被用户设置了值呢? 此时,我们可以依靠 ``$transclude`` 来进行判断: ```javascript class XXXComponent{ constructor($transclude){ this.$transclude = $transclude; } $onInit(){ // 判断transclude是否存在 let transcludeName = 'xxx'; let hasXXX = this.$transclude.isSlotFilled(transcludeName); } } XXXComponent.$inject = ['$transclude']; ``` ### 1.2、如何监控绑定属性的变更? 属性绑定,分为一次性绑定(@)(也算是单向绑定),单向绑定(<),双向绑定(=)。 **# 监控单向绑定属性** 对于单向绑定的属性,可以通过生命周期钩子 ``$onChanges(changesObj)`` 来进行监控。 ```javascript class XXXController{ $onChanges(changesObj){ console.log(changesObj); } } ``` 其中参数 ``changesObj`` 是所有变更属性的一个汇总,数据结构如下: ```json changesObj = { key1: { // 有变更的绑定属性 currentValue: any // 当前值 (变化后的值) previousValue: any // 上一次的值 (变化前的值) isFirstChange(): fn // 方法,用于判断是否是第一次变更。 } } ``` **注意:``$onChanges`` 无法监控双向绑定属性,切记!** **# 监控双向绑定** 由于 ``$onChanges`` 无法监控双向绑定属性,那么我们就必须另外想办法来进行监控,可以有以下几种方案: *方案一:利用 ``$interval``* 既然是双向绑定,那么肯定变化是直接生效的,关键就在于我们无法监视到,这个时候我们可以利用 ``$interval`` 来实现定时监控。 ```javascript class XXXController{ constructor($interval){ this.$interval = $interval; this.init(); } init(){ let previousValue = null; this.$interval(() => { if(previousValue !== this.value){ previousValue = this.value; console.log('value changed'); } }, 200); } } XXXController.$inject = ['$interval']; angular.module('xxx').component('xxx', { bindings: { value: '=' }, controller: XXXController, controllerAs: 'vm' }); ``` 优点: 1. 易于理解 缺点: 1. 浪费资源 2. 需要自己书写逻辑 推荐指数: ☆ *方案二:利用 ``$scope.$watch(keyString)``* 组件也有独立的 ``$scope``,那么借助 ``$scope.$watch`` 也可以实现监听属性变化,代码如下: ```javascript class XXXController{ constructor($scope){ this.$scope = $scope; this.init(); } init(){ this.$scope.$watch('vm.value', (newVal, oldVal) => { console.log('value changed); }); } } XXXController.$inject = ['$scope']; angular.module('xxx').component('xxx', { bindings: { value: '=' // 双向绑定属性 }, controller: XXXController, controllerAs: 'vm' }); ``` 优点: 1. 使用简单 缺点: 1. 字符串形式的 ``$watch``,依赖 ``controllerAs``,不易理解 2. 实质仍然是定时器,只不过是使用的 ``angular`` 自身的 ``$diget`` 循环 推荐指数: ☆☆ *方案三:利用 ``$scope.$watch(fn)``* ``$scope.$watch`` 也接受函数类型的参数,相对于字符串形式,没有 ``controllerAs`` 的相关性,而且更灵活,代码如下: ```javascript class XXXController{ constructor($scope){ this.$scope = $scope; this.init(); } init(){ this.$scope.$watch(() => this.value, (newVal, oldVal) => { console.log('value changed); }); } } XXXController.$inject = ['$scope']; angular.module('xxx').component('xxx', { bindings: { value: '=' // 双向绑定属性 }, controller: XXXController, controllerAs: 'vm' }); ``` 优点: 1. 使用简单 缺点: 1. 实质仍然是定时器,只不过是使用的 ``angular`` 自身的 ``$diget`` 循环 推荐指数: ☆☆☆☆ *方案四:利用 ``getter & setter``* 因为我们使用了 ``ES6 Class``,那么 ``ES6`` 的 ``getter setter`` 特性,我们也是能够使用的,方式如下: ```javascript class XXXController{ set value(val){ this._value = val; console.log('value changed'); } get value(){ return this._value; } } XXXController.$inject = []; angular.module('xxx').component('xxx', { bindings: { value: '=' // 双向绑定属性 }, controller: XXXController, controllerAs: 'vm' }); ``` 优点: 1. 没有额外的开销,性能高 缺点: 1. 使用相对较为复杂 推荐指数: ☆☆☆☆ ================================================ FILE: AngularJS相关/Angular再回首(3)-我们来实现一个组件.md ================================================ --- title: Angular再回首(3)-我们来实现一个组件 date: 2017/02/21 14:47:10 --- ## 0、前言 前两文写了 ``Component`` 的一些方面,但没有一个比较线性的串联关系,本文,就来从一个实例出发,来尝试概括一个组件的方方面面。 ## 1、 ## 2、组件实现 ### 2.1、先整一个组件 ```javascript angular.module('app', []) .component('finalComponent', {}); ``` 这个组件啥都不干,就提供了一个新的标签,显得毫无意义,但是我们可以从这里看到如何定义一个组件。 **注意:组件名称,请使用小驼峰命名法,在HTML中,请使用连字符+小写字母,这种实现是为了处理js和html大小写敏感的差异(js区分大小写,html不区分)** **注意2:如果在组件标签中,嵌入有效的标签,是会显示出来的,如下:** ```html

Hello

``` 会显示出大号的 “Hello”。 ### 2.2、带模板的组件 接着,来实现一个有意义的组件,比如我要渲染一个特定的字符串,代码如下: ```javascript angular.module('app', []) .component('finalComponent', { template: '

Hello World.

' }); ``` 现在我们再使用:`ABC`,则会显示 "Hello World" 内容了。 **注意:当组件指定了模板属性后,其内部的标签,将不会生效(transclude除外,)** ### 2.3、复杂模板的组件 以上,我们已经实现了带模板的组件,可是我们的模板可能会比较复杂,这个时候直接写 `template` 就不太好用了,此时,我们会考虑把模板拆分到一个独立的 `.html` 文件中,代码如下: ```html

Hello World.

``` 然后,使用 `templateUrl` 属性进行关联 ```javascript angular.module('app', []) .component('finalComponent', { templateUrl: '/app/template.html' }); ``` 该代码可以达到 2.2 同样的效果,只是把模板内容拆分到独立文件中了。 **注意:模板路径可以是相对路径,也可以是绝对路径,需要注意路径的写法,否则会出现找不到模板** **注意2:如果使用 `gulp` 构建,可以考虑使用 `gulp-angular-embed-templates` 将独立的模板文件,打包到组件中。** ### 2.4、组件属性绑定 之前实现的组件,感觉太死板了,我想改下文字,都不好实现(你非要用js强制操作dom,我拿你也没办法,不过后果自负),这个时候,我们迫切的需要能给组件传递参数。 `Angular` 组件中,有多个参数传递方式,如下: * @ 单向绑定字符串(原值绑定) - 传什么就是什么,不做任何处理 * < 单向绑定变量(取scope的值绑定) - 传的值会先用 `$scope` 转换,把结果传递给组件 * = 双向绑定 - 组件内外变化都会通知另一方 #### 2.4.1 直接传递字符串参数 使用 `@` 进行单向字符串绑定 ```javascript angular.module('app', []) .component('finalComponent', { templateUrl: '/app/template.html' bindings: { name: '@' } }); ``` ```html

Hello {{$ctrl.name}}.

``` 此时,将会显示“Hello Jay”,可以看到,设定的参数值会原样显示了。 **注意:在模板中,要使用变量,需要加$ctrl前缀,先这样用着,后面会提到** #### 2.4.2 使用单向绑定变量 ```javascript class TestController{ constructor(){ this.name = 'Jay' } } TestController.$inject = []; // 依赖 angular.module('app', []) .component('finalComponent', { templateUrl: '/app/template.html' bindings: { name: '<' } }) .controller('TestController', TestController); ``` ```html

Hello {{$ctrl.name}}.

``` 此时,也将会显示“Hello Jay”,可以看到,此时 `t.name` 会拿到 `$scope` 中进行解析。 **注意:推荐使用 `controller as` 写法** #### 2.4.3 双向绑定 ```javascript class TestController{ constructor(){ this.name = 'Jay' } } TestController.$inject = []; // 依赖 angular.module('app', []) .component('finalComponent', { templateUrl: '/app/template.html' bindings: { name: '=' } }) .controller('TestController', TestController); ``` ```html

Hello {{$ctrl.name}}.

{{t.name}}

``` 此时,在文本框输入值之后,可以看到组件内外都会及时变更。 ================================================ FILE: AngularJS相关/Angular开发Tips.md ================================================ --- title: Angular开发Tips date: 2017/02/21 14:47:10 --- 1、在使用$routeProvider的时候,需要让模块依赖ngRoute,否则会提示找不到服务,示例: angular.module('module1', ['ngRoute']) .config(['$routeProvider', function($routeProvider){ //do something... }]); 2、在页面中需要绑定有风险的html的时候,可以使用 `ng-bind-html="html"(version>=1.3)`,如果遇到错误,控制器中可以使用`html = $sce.trustHtml(unsafeHtml)`。 3、 如何动态的向页面添加带指令的HTML?通入如下代码: $compile(html)($scope); 4、如果阻止事件冒泡?示例如下: //方式一,利用一个自定义指令实现 .directive('stopEventPropagation', function(){ return { restrict: 'A', link: function(scope, iElement, iAttrs){ //通过获取事件对象,来阻止调用 iElement.bind('click', function(e){ e.stopPropagation(); }); } } });
Click me //方式二,直接引用$event对象 Click me 5、关于$route和$location的事件顺序,如下: $routeChangeStart -> $locationChangeStart -> $locationChangeSuccess -> $routeChangeSuccess 6、有关select标签的使用,当options的来源是ajax时,那么如果指定选中项呢?如下: //如上HTML代码,如果sysOptions来自ajax请求,而selectSystem又不是的话,往往会选中一个空值。 //可以使用如下方式避免: ```javascript .controller('TestCtrl', ['$scope', '$http', function($scope, $http){ $http.get(url).success(function(data){ $scope.sysOptions = data; //在异步回调函数中,对ng-model赋值。 $scope.selectSystem = 'Test'; }); }]); ``` 7、在编写指令时,属性的匹配大小写需要注意:如果在html中使用 `showName="xx"`,那么在指令的iAttrs中,应该使用 `showname` 获取。如果要在指令中使用showName获取的话,那么必须在html中使用 `show-name="xx"`。 8、要生成安全链接时,需要修改配置,代码如下: ```javascript 需要将如下代码: ng-href="{{true: 'javascript:void(0);' : 'url'}}" 生成为: href="javascript:void(0);" ``` ```javascript .config(['$compileProvider', function($compileProvider){ $compileProvider.aHrefSanitizationWhitelist(/^\s*(https?|ftp|mailto|file|javascript):/) }]); ``` 9、在ng-click等ng事件中,如果拿到事件源对象?如下: ```javascript $scope.click = function($event){ var target = $event.target; }; //注意,如果使用ng-click="click($event.target)",将会导致angular解析错误。 ``` 10、判断angular的模块是否存在,可以使用如下代码: var isAngularModuleExists = function(moduleName){ try{ angular.module(moduleName) }catch{ return false } return true; }; 11、在使用coffee编写使用provider方式编写服务时,当心写在最后的this.$get,coffee会将最后一句编译为return this.$get,而这刚好不符合provider的要求,所以应该在末尾手动加上return或者放置一个undefined在最后,放置编译出return this.$get这样的代码。 12、如果要动态控制是否启用非空验证,可以使用ng-required="true|false"指令。 13、当心ng-if指令,在使用ng-if指令时,会创建独立的作用域,如果要在$scope监视ng-if包含的变量,那么是无法成功的。如果一定要监视,可以考虑使用ng-show。 14、注意.value()与.constant的区别,前者只能注入和用于服务或者控制器中,后则可以被注入到配置(.config(['xx']))中。 ================================================ FILE: AngularJS相关/Angular:指令、Controller数据共享.md ================================================ --- title: Angular:指令、Controller数据共享 date: 2015/3/13 11:24:39 --- ## 1、Directive与Controller数据共享 在指令中,不仅仅需要指令配置信息,很多时候也需要获取$scope的相关数据。那么,如何在指令中拿到$scope的数据呢? ### 1.1、Directive和Controller使用同一个scope Angular Demo
执行以上代码,页面显示Hi Jay,并在控制台打印 controller scope id = 2 directive scope id = 2 在指令中,默认会直接使用上级的scope,从控制台来看,先执行controller的scope,再执行directive的scope。因为id一致,所以是同一个scope。既然是同一个scope,那么共享数据自然就不是问题了。该方式,适合业务性质的directive,如果是公共的directive,不建议使用此方式,可能会导致scope杂乱。 ### 1.2、在指令作用域中使用@,将当前属性作为字符串传递 Angular Demo
以上代码,主要修改了指令的scope,从输出来看,指令和controller各自是自己独有的作用域。 ``scope = {name: '@'}``,等价于 link:function(scope, iElement, iAttrs){ scope.name = iAttrs.name; } Controller中的key的变化,会即时影响到Directive的变化,但是Directive的变化并不会反向影响到Controller,结果近似于单向绑定。 ### 1.3、在指令的作用域中使用=,进行数据的双向绑定 Angular Demo
key = {{key}}
以上代码的变化在于,使用了scope: {name: '='},该代码将父作用域的属性和指令的属性进行双向绑定。所以指令中文本框的值的变化,将会同步影响controller中key的变化。 **注意:在使用指令的时候,html代码,并不是和示例1.1一致了,如果是双向绑定,那么应该使用 Angular Demo
key = {{key}}
点击指令生成的按钮,会执行controller的show方法,利用在scope: {showName: '&'},可以将父级作用域的方法绑定到指令中。 **注意,一定要注意属性命令,在html中书写showName,那么在iAttrs中对应showname,只有在html中书写show-name,在会在iAttrs中对应showName。** ## 2、在controller中,拿到directive的作用域 ### 2.1、拿到scope的元素,调用isolateScope获取scope Angular Demo
key = {{key}}
此代码中,利用$('#d1').isolateScope,拿到了该指令的scope,所以可以随时方式,该方式在多种指令中也有效。 **如果判断应该用isolateScope()还是scope()获取作用域?一个最简单的方式,用F12查看源码,找到该元素,然后查看class是ng-isolate-scope还是ng-scope** ## 3、 指令之间相互获取数据 ### 3.1、通过directive依赖来共享数据 ### 3.2、通过如2.1的方式获取数据 ## 4、 其他Hacky的方式 1. 通过``$parent``访问父级作用域 2. 通过``$$prevSibling``访问该作用域的上一个兄弟作用域 3. 通过``$$nextSibling``访问该作用域的下一个兄弟作用域 4. 通过``$$childHead``访问儿子作用域的第一个 5. 通过``$$childTail``访问儿子作用域的最后一个 ## 5、参考资料 1. [SHARING DATA BETWEEN CHILD AND PARENT DIRECTIVES AND SCOPES (IN ANGULARJS)](http://tech.blinemedical.com/sharing-data-between-child-and-parent-directives-and-scopes-in-angularjs/) 2. [directive和controller如何通信](http://www.cnblogs.com/bigdataZJ/p/AngularJS1.html) ================================================ FILE: AngularJS相关/[20140917]Angular:如何编写一个指令.md ================================================ --- title: Angular:如何编写一个指令 date: 2015/3/13 11:24:39 --- ## Angular是什么? AngularJS是一个用JavaScript编写的客户端MVC框架,它运行于Web浏览器,能够极大的帮助我们(开发者)编写模块化,单页面,Ajax风格的Web Applications。 PS:**AngularJS适合开发CRUD的SPA** ## Angular Directive是什么? Angular Directive是构建在DOM元素(属性、标签名、注释和CSS类)上的标记,告诉AngularJS的HTML编译器($compile) 附加指定的行为到元素或者甚至变换这个元素和它的子集。 PS:**通过扩展HTML标签的方式提供可复用的web组件** PS2:**指令的作用:提供语义化标签** ## 完整的Directive参数 var directiveModule=angular.module('Newkit.negHotkeys'); directiveModule.directive('negHotkeys',function(injectables){ var directiveDefineObject={ restrict:(string), priority:(number), template:(string), templateUrl:(string), replace:(bool), transclude:(bool), scope:(bool or object), controller:(function), require:(string), link:(function) compile:(function) }; return directiveDefineObject; }); ##### 参数说明 - restrict:(string)指令的使用方式,可选值:元素[E]、属性[A]、样式类[C]、注释[M],并且可以采用组合的方式使用,示例:'AE' - priority:(number)优先级,描述了多个指令时,指令的执行顺序。数字越大,优先级越高,默认值0。 - template:(string)文本模板 - templateUrl:(string)模板文件地址,如果设置了该属性,那么将会忽略template的配置。 - replace:(bool)指示是否替换元素,如果设置为true,则替换,否则(设置为false或不设置)追加到元素内部 - transclude:(bool)是否将指令的子节点移动到一个新模板内部,如果在模板中指定了ng-transclude,那么会将元素原本的内容移动到新的模板内部,具体看示例二 - scope:(bool or object)设置作用域,如果设置为false[默认值],则使用现有的作用域;如果设置为true,则创建一个新的作用域。设置为object时,设定作用域绑定策略 - controller:创建一个控制器,它会暴露一个API,实现在多个指令之间进行通信 - require:设置依赖的指令。不设置,则无依赖,示例:'?\^testDirective',其中,?表示该指令可选,^表示需要遍历DOM树查找指令 - link:链接函数,function(scope,iElement,iAttrs){},其中的i表示实例,所以在link中接收的是实例元素和实例元素属性 - compile:编译函数,function(tElement,tAttrs,transclude){},其中t表示模板,所以在compile中使用的是模板元素。在编译过程中,可以返回preLink(链接前),postLink(链接后)函数,compile函数只会调用一次,而link函数的调用次数等于things中的元素个数,所以多余共同的东西,那么最好放在compile函数中实现(出于效率考虑) **注:设置了compile属性之后,指令将忽略link属性,同时compile函数的返回值将作为link函数使用** ## Angular Directive 示例 ### 示例一(简单指令) ```javascript angular.module('app').directive('demo1',function(){ return { restrict:'AE',/*标签或者属性*/ template:'
Hello
', replace:true } }); ...
Hello World!
Hello World!
``` ### 操作步骤分析 1. 定义一个模块app,并创建了一个指令demo1。 2. 设定该指令可采用元素的标签和属性申明,并设置了一个文本模板,同时设置了replace=true。 3. 在html中,采用标签如*<demo1> angular.module('app.directive.demo2',[]).directive('demo2',function(){ return { restrict:'E', template:'
This is Demo2
', transclude:true } }); 原始的内容,
还会在这里。
This is Demo2
原始的内容,
还会在这里。
This is Demo2
``` #### 分析 1. 通过在指令中设置transclude=true,同时在template中包含*<div ng-transclude>*,实现了将元素内部元素移动到了ng-transclude元素内部,并创建了新的作用域 ### 示例三(link与compile) ```javascript /*指令*/ angular.module('app.directive.demo3',[]).directive('demo3Link',function(){ return { restrict:'E', template:'
This is Demo3Link
', link:function(scope,iElement,iAttrs){ iElement.html('
good link
'); } } }).directive('demo3Compile',function(){ return { restrict:'E', template:'
This is Demo3Compile
', compile:function(tElement,tAttrs,transclude){ tElement.html('
test demo3 compile
'); return function(scope,iElement,iAttrs){ //iElement.html('
good compile
'); }; } } }); /*使用*/ /*页面生成的HTML*/
good link
good link
test demo3 compile
``` #### 分析 compile用于在编译期处理模板内容,并能设置preLink和postLink函数,此时将不能设置link函数,代码如下: ``` compile:function(tElement,tAttrs,transclude){ tElement.html('
test demo3 compile
'); return { pre:function preLink(scope,iElement,iAttrs){ console.log('preLink'); }, post:function postLink(scope,iElement,iAttrs){ console.log('postLink'); } }; } ``` link用于对替换后的元素进行操作,如果参数是iElement。 ### 示例四(简单加法计算器) ``` /*代码在这里*/ angular.module('app.directive.demo4',[]).directive('demo4',function(){ return { restrict:'E', template:'
计算两个数之和' + '
+={{total}}
' + '
', replace:true, link:function(scope,iElement,iAttrs){ scope.num1=0; scope.num2=0; scope.total=0; scope.$watch('num1+num2',function(to,from){ scope.total=+scope.num1+(+scope.num2) }) } } }); /*HTML在这里*/ 、 /*效果请自行测试*/ ``` #### 分析 可以利用指令完成特定的功能了。 ### 示例五(negHotkeys指令代码) [代码在这里](http://trgit/backend_framework/web_platform/blob/master/src/framework/js/directives/custom/negHotKeys.coffee) ## 总结 1. 指令依附于模块 2. 一个模块可以有多个指令,但是需要采用示例三的写法 3. 指令可以语义化标签,实现html组件化 4. 其他... ================================================ FILE: AngularJS相关/用AngularJS开发Web应用程序.md ================================================ --- title: 用AngularJS开发Web应用程序 date: 2015/3/13 11:24:39 --- ##章节一:Angular 禅道## ###本章生词### serve = 提供 take a brief = 先简要的 introduction = 介绍 concept = 概念 a lot of = 许多 material = 材料 cover = 概括 painless = 无痛的 plenty = 丰富、大量 unique = 独特的 doubt = 疑问 shape = 塑造 explain = 解释 expect = 预计 get familiar with = 熟悉 become aware = 察觉 sophisticated = 复杂 dependency injection = 依赖注入nuance nuance = 细微之处 general = 一般 purpose = 目的 shines = 耀眼 recent = 最近 addition = 此外 mostly = 主要的 due = 由于 innovative = 创新 yet = 但 attract = 吸引 ease = 缓解 solid = 扎实 engineering = 工程 practice = 实践 indeed = 的确 respects = 方面 explicit = 明确的 capable = 能 figure out = 弄清楚 interesting = 有趣的 interpret = 解析 mistaken = 错误,谬 several = 几个,数个 typically = 通常 treasure = 宝藏 testability = 可测试性 built-in support 内置支持 thoroughly = 彻底的 relatively = 比较的 actor = 演员 personal = 个人的 turned out = 横空出世 --- 这个章节介绍了AngularJS,包括框架和它背后的项目。首先,我们先简要的了解项目本身:谁become aware驱动了它,在哪儿可以找到源代码和文档,如何寻求帮助等等。 这个章节的大部分是介绍AngularJS框架,它的核心概念和编码模式。包含有许多概括(总结)性的材料,使得学习进程快速无障碍,同时也有丰富的代码示例。 AngularJS是一个独特的框架,毫无疑问的引领一个Web开发潮流。这也是为什么章节的最后部分解释了是什么让AngularJS如此特别,它和其它外部框架之间的差异和我们能在未来如何设想它。 这个章节包含了以下几个主题: 1. 如何用AngularJS书写一个简单的Hello World 程序。在做这个的过程中,你将了解到在哪儿找到框架源代码、文档以及社区。 2. 熟悉AngularJS应用程序的基本构造块:Templates、Directives、Scopes和Controllers。 3. 察觉AngularJS复杂的依赖注入系统以及它所有的细微之处。 4. 理解AngularJS与其他框架或库(特别是jQuery)之间的差异,是什么使得它如此特别。 ###AngularJS简介### AngularJS是一个用JavaScript编写的客户端MVC框架,它运行于Web浏览器,能够极大的帮助我们(开发者)编写模块化,单页面,Ajax风格的Web Applications。它是一个平常的框架,不过如果用于编写CRUD类型的web app,那么它将非常耀眼。 ###熟悉框架### AngularJS是最近的客户端mvc框架的例外,但是它吸引了许多注意力,主要是由于它创新的模板系统,减轻了开发,同时有很扎实的工程实践。的确,它的模板系统独特于许多方面: 1. 使用HTML作为模板语言 2. 不要求明确的DOM刷新,AngularJS 能跟踪用户操作、浏览器事件和模型变化,来选择何时和那个模板将被刷新 3. 它有非常有趣的和可扩展的组件子系统,它能教会浏览器如何解析新的HTML标签和属性 模板子系统可能是AngularJS中最常见的部分,但是不要错误的认为AngularJS是单页Web程序所需要的包含数个工具和常用服务的完整框架包。 AngularJS同样有一些隐藏的宝藏,依赖注入(DI=dependency injection)和可测试特性的强烈关注。DI的内置支持能够非常容易的访问从一个极小的、彻底的可测试服务创建的web app。 ###项目发展路线### AngularJS是客户端MVC框架中比较新的成员;它的1.0版本发布于2012年6月。实际上,这个框架作为谷歌雇员Misko Hevery的个人项目开始于2009年。最初的idea是如此的好,在写作本文的同时,这个项目已经被Google正式支持,并且有Google的完整团队全职维护这个框架。 AngularJS是托管在[GitHub](https://github.com/angular/angular.js)上的,基于MIT协议的开源项目 ###社区### ================================================ FILE: AngularJS相关/详解angular之$q.md ================================================ --- title: 详解angular之$q date: 2017/02/21 14:47:10 --- ## 0、什么的Promise Promise(承诺)是用于改善异步编程体验的一种编程模型,它提供了一些列的API的方法论,让你能更优雅的解决异步编程中出现的一些问题。 ## 1、Promise的核心竞争力 在处理有依赖性的回调的时候,我们的代码是这样写的: step1(function (value1) { step2(value1, function(value2) { step3(value2, function(value3) { step4(value3, function(value4) { // Do something with value4 }); }); }); }); 这就是我们所谓的回调地狱。 如果用Promise的方式来实现,是怎么样呢? step1().then(step2).then(step3).then(step4) 代码更简单逻辑也清晰,异步的回调嵌套变成了同步写法,孰优孰劣相信大家都一目了然。 ## 2、Angular服务$q 在angular中,基于nodejs中流行的Q提供了一个简化版本的Q,对外的话提供一个service $q。 以下列举出angular中的$q提供的API #### 1、Promise.then() 将回调变成链式调用,then可以接两个参数,successCallback, errorCallback,示例如下: var deferred = $q.defer(); var promise = deferred.promise; promise.then(successCallback, errorCallback); #### 2、Promise.catch 捕获Promise异常,Promise.catch(errorCallback)等价于Promise.then(null, errorCallback) #### 3、Promise.finally(callback, notifyCallback) promise结束后要做的事情和接收通知信息 #### 4、Deferred.resolve(val) 通知promise请求处理完毕,并将处理结果传给回调函数(successCallback),示例如下: var deferred = $q.defer(); setTimeout(function(){ deferred.resolve('abc'); //会将abc传递给successCallback }, 1000); var promise = deferred.promise; promise.then(successCallback, errorCallback); #### 5、Deferred.reject(msg) 通知promise请求出现异常,将异常信息传给回调函数(errorCallback),示例如下: var deferred = $q.defer(); setTimeout(function(){ deferred.reject('abc'); //会将abc传递给errorCallback }, 1000); var promise = deferred.promise; promise.then(successCallback, errorCallback); #### 6、Deferred.notify(value) 内部执行有变化时,对外发起通知。将会在Promise.finally中捕获到 var deferred = $q.defer(); setTimeout(function(){ deferred.reject('abc'); //会将abc传递给errorCallback }, 1000); var promise = deferred.promise; promise.then(successCallback, errorCallback); #### 7、$q.when(val/fn) 将任意对象/函数包装成promise,返回包装好的promise。 #### 8、$q.all(promises).then() 当所有的promise都成功解析后,流程才继续往下走。示例如下: $q.all($http.get('xxx'), $http.post('xxx',{})) .then(successCallback, errorCallback); ## 3、$q的使用 常规使用 //定义开关变量 var canSuccess = false; //定义一个Promise var buildPromise = ()=>{ var deferred = $q.defer(); setTimeout(()=>{ if(canSuccess){ deferred.resolve('promise执行成功!') }else{ deferred.reject('promise执行失败!') } },5000); return deferred.promise; }; //使用它 var promise = buildPromise(); promise.then(()=>{ console.log('执行成功啦!'); }, ()=>{ console.log('执行失败了!'); }) 使用$q.all var p1 = $http.get('xxxx'); var p2 = $http.get('xxxx2'); $q.all(p1, p2).then(() =>{ console.log('两次请求都成功了!'); }); ## 4、$q源码分解 //Deferred定义 function Deferred() { this.promise = new Promise(); //Necessary to support unbound execution :/ this.resolve = simpleBind(this, this.resolve); this.reject = simpleBind(this, this.reject); this.notify = simpleBind(this, this.notify); } //函数柯里化 function simpleBind(context, fn) { return function(value) { fn.call(context, value); }; } 通过这种方式,就能将resolve,reject和promise关联起来了。既然我们最终要返回promise,那我们来看已看Promise的实现: function Promise() { this.$$state = { status: 0 }; } extend(Promise.prototype, { then: function(onFulfilled, onRejected, progressBack) { if (isUndefined(onFulfilled) && isUndefined(onRejected) && isUndefined(progressBack)) { return this; } var result = new Deferred(); this.$$state.pending = this.$$state.pending || []; this.$$state.pending.push([result, onFulfilled, onRejected, progressBack]); if (this.$$state.status > 0) scheduleProcessQueue(this.$$state); return result.promise; }, "catch": function(callback) { return this.then(null, callback); }, "finally": function(callback, progressBack) { return this.then(function(value) { return handleCallback(value, true, callback); }, function(error) { return handleCallback(error, false, callback); }, progressBack); } }); 从这里很明显可以看出,catch就是一个语法糖,调用的还是then。finally也是一个语法糖,就是不关成功,还是失败,都会调用callback。那这个时候,我们主要关注的方法就放到then这个方法的实现上。 为了实现链式调用,在then方法内部,又实例化了Deferred对象,并返回Defferrd.promise。 接下来就来看处理过程: this.$$state.pending = this.$$state.pending || []; this.$$state.pending.push([result, onFulfilled, onRejected, progressBack]); if (this.$$state.status > 0) scheduleProcessQueue(this.$$state); extend(Deferred.prototype, { resolve: function(val) { if (this.promise.$$state.status) return; if (val === this.promise) { this.$$reject($qMinErr( 'qcycle', "Expected promise to be resolved with value other than itself '{0}'", val)); } else { this.$$resolve(val); } }, $$resolve: function(val) { var then, fns; fns = callOnce(this, this.$$resolve, this.$$reject); try { if ((isObject(val) || isFunction(val))) then = val && val.then; if (isFunction(then)) { this.promise.$$state.status = -1; then.call(val, fns[0], fns[1], this.notify); } else { this.promise.$$state.value = val; this.promise.$$state.status = 1; scheduleProcessQueue(this.promise.$$state); } } catch (e) { fns[1](e); exceptionHandler(e); } }, reject: function(reason) { if (this.promise.$$state.status) return; this.$$reject(reason); }, $$reject: function(reason) { this.promise.$$state.value = reason; this.promise.$$state.status = 2; scheduleProcessQueue(this.promise.$$state); }, notify: function(progress) { var callbacks = this.promise.$$state.pending; if ((this.promise.$$state.status <= 0) && callbacks && callbacks.length) { nextTick(function() { var callback, result; for (var i = 0, ii = callbacks.length; i < ii; i++) { result = callbacks[i][0]; callback = callbacks[i][3]; try { result.notify(isFunction(callback) ? callback(progress) : progress); } catch (e) { exceptionHandler(e); } } }); } } }); 在调用then的时候,就将锅中回调写到$$state的pending数组中,让defferred.resolve的时候就会调用Deferred的内部方法,调用我们传递的回调函数。 **源码分解实在是说不明白,后期再发一篇如何实现一个简易的Promise,希望能更简洁易懂** ## 5、 了解更多 [JavaScript Promise迷你书(中文版)](http://liubin.github.io/promises-book/) [Angular $q api](https://docs.angularjs.org/api/ng/service/$q) ================================================ FILE: Angular系列/01_Angular2初体验.md ================================================ --- title: 01_Angular2初体验 date: 2017/02/21 14:47:10 --- ## 0、关于Angular2 Angualr2是前端最流行的MV*框架AngularJS的革命性更新版本,官网:[https://angular.io/](https://angular.io/),号称一个框架统一移动版和桌面。 ## 1、背景 将AngularJS升级为Angular2,是大势所趋。在之前,我们就必须要对Angular2有足够的了解。所以这一系列文章,希望从各个点将angular2分而破之。 另外,由于Angular2当前处于Beta阶段,所以代码的时效性不高。所以每篇都会注明相关版本。 Angular2推荐的开发语言是TypeScript [http://www.typescriptlang.org/](http://www.typescriptlang.org/),所以我们这一系列文章也使用TypeScript开发(实际是使用JavaScript,半天没弄成功,丧气ing...)。 不要害怕TypeScript,因为TypeScript是ES6的超集,我们完全可以使用ES6的方式来编写TypeScript代码。对我们来说,仅仅是文件名后缀变化了。 ## 2、Angular2 Hello-World Angular2并不仅仅只有一个JS文件,要想成功运行Angular2,需要包含如下内容: 1. systemjs --模块加载器 2. Rxjs --对Js的扩展,至今不知道它是做什么的 3. angular2 如果要支持IE,那么还需要 1. es6-shim 2. systemjs 中的system-polyfills.js文件 3. angular2中的shims_for_IE.js文件 接着就直接创建项目吧,结构如下: ``` components/ hello_world.html hello_world.ts bootstrap.ts index.html package.json ``` 首先第一步,我们要通过npm安装我们的依赖项: ``npm install angular2 rxjs systemjs es6-shim typescript --save`` 接着,实现我们的``index.html``内容: ```html Angular2 Hello World ``` 然后是我们的``hello_world.html``和``hello_world.ts``,内容如下: ```html

Hello Angular2

My name is:
Angular2: Hello, {{username}} ``` ```javascript import {Component} from 'angular2/core'; @Component({ selector: 'hello_world', //此处指明了组件的标记,我们就可以使用来使用这个组件了。 templateUrl: 'components/hello_world.html' }) //export的意思是导出这个组件,在使用的地方,就可以使用import {xx} from 'xxx'来获取到了。 export class HelloWorldComponent{ constructor(){ } } ``` 最后,是我们的``bootstrap.ts``入口JS: ```javascript import {bootstrap} from 'angular2/platform/browser'; import {HelloWorldComponent} from 'components/hello_world'; bootstrap(HelloWorldComponent); ``` 通过``anywhere``启动静态服务器,就可以看到我们的页面了。 **But,理想很丰满,现实很骨感,为嘛不兼容IE11???** 错误提示如下: ``` "'Symbol' is undefined" ``` **坑你没商量*!最终发现是Rx的版本必须用angular2提供的那个版本** 地址是:[https://code.angularjs.org/2.0.0-beta.12/Rx.js](https://code.angularjs.org/2.0.0-beta.12/Rx.js) 所以把Rx.js文件替换下,就可以在IE11中跑起来了。 **另外,经测试,Angular2可兼容IE9及以上版本。** ## 3、结尾 [Demo源码](https://github.com/hstarorg/HstarDemoProject/tree/master/angular2_demo/04) ================================================ FILE: Angular系列/02_Angular2组件生命周期.md ================================================ --- title: 02_Angular2组件生命周期 date: 2017/02/21 14:47:10 --- ## 0、Angular2 组件 Angular1并不是围绕组件的概念来实现的。所以,我们需要controller、$scope,同时也需要封装自定义指令。 在Angular2中,把之前的这些东西都丢弃了,使用了一种更面向对象的组件模型。 一个组件控制着我们称之为View的显示部分。组件同时也是自描述的。 **在Angular2中,指令也是存在的,组件只是指令的一种。** ## 1、定义一个组件 最基本的组件只需要提供一个selector和template就足够了。代码如下: ```javascript import {Component} from 'angular2/core'; @Component({ selector: 'basic-info', template: '

Basic Info

' }) export class AboutComponent{ constructor() { } } ``` 要实现输入和输出呢? ```javascript import {Component, Input, Output, EventEmitter} from 'angular2/core'; @Component({ selector: 'basic-info', template: `

Basic Info, {{abc}}

` }) export class BasicInfo{ @Input('test') set value(val){ this.abc = val; this.callback.next([val]); } @Output('callback') callback = new EventEmitter(); constructor(){ this.abc = 'aaaa'; } } ``` 如何使用? ```html ``` ## 2、组件生命周期 Angular2会管理组件的整个生命周期,包括组件的创建、渲染、子组件的创建和渲染、数据绑定属性变化时的校验、从DOM移除之前的销毁等等。 那如果我们想在某个状态时,进行一些操作应该怎么办呢?Angular2提供了组件生命周期的钩子,供我们在这些时间点添加自定义的操作。 在``angular2/core``中提供了多个Lifecycle Hook接口,我们可以实现一个或多个接口,来设置自定义操作。每一个接口,都会有一个钩子方法,钩子方法的名称是接口的名称加上前缀ng。如OnInit的钩子方法如下: ```javascript import {Component, Input, Output, EventEmitter} from 'angular2/core'; @Component({ selector: 'basic-info', template: `

Basic Info, {{abc}}

` }) export class BasicInfo{ @Input('test') set value(val){ this.abc = val; this.callback.next([val]); } @Output('callback') callback = new EventEmitter(); constructor(){ this.abc = 'aaaa'; } // 组件Init时创建 ngOnInit(){ console.log('basic info init.'); } } ``` **生命周期钩子(组件和指令都有的)** 1. ngOnInit //组件初始化,在Angular初始化数据绑定输入属性之后 2. ngOnChanges // 3. ngDoCheck 4. ngOnDestroy **生命周期钩子(组件特有的)** 1. ngAfterContentInit // Angular将外部内容放入视图后 2. ngAfterContentChecked // 在Angular检测放到视图内的外部内容的绑定后 3. ngAfterViewInit // Angular创建视图之后 4. ngAfterViewChecked //Angular检测了组件视图的绑定之后 **执行顺序** 1. ngOnChanges //绑定属性变化时 2. ngOnInit //在第一次ngOnChanges之后,初始化时 3. ngDoCheck //每次Angular变化检测时 4. ngAfterContentInit //组件内容初始化之后 5. ngAfterContentChecked //组件内容变化后 6. ngAfterViewInit //初始化组件视图和子视图之后 7. ngAfterViewChecked //在数组视图和子视图检查之后 8. ngOnDestroy 我们将组件设定上钩子函数如下: ```javascript import {Component, Input, Output, EventEmitter} from 'angular2/core'; @Component({ selector: 'basic-info', template: `

Basic Info, {{abc}}

` }) export class BasicInfo{ @Input('test') set value(val){ this.abc = val; this.callback.next([val]); } @Output('callback') callback = new EventEmitter(); constructor(){ this.abc = 'aaaa'; } ngOnInit(){ console.log('basic info init.'); } ngDoCheck(){ console.log('basic info do check.'); } ngOnChanges(){ console.log('basic info changes.'); } ngOnDestroy(){ console.log('basic info destroy'); } ngAfterContentInit(){ console.log('basic info after content init'); } ngAfterContentChecked(){ console.log('basic info after content checked'); } ngAfterViewInit(){ console.log('basic info after view init'); } ngAfterViewChecked(){ console.log('basic info after view checked'); } } ``` 控制台打印的结果是: ```html basic info changes. test.component.js:23 basic info init. test.component.js:26 basic info do check. test.component.js:35 basic info after content init test.component.js:38 basic info after content checked test.component.js:41 basic info after view init test.component.js:44 basic info after view checked test.component.js:26 basic info do check. test.component.js:38 basic info after content checked test.component.js:44 basic info after view checked test.component.js:26 basic info do check. test.component.js:38 basic info after content checked test.component.js:44 basic info after view checked test.component.js:26 basic info do check. test.component.js:38 basic info after content checked test.component.js:44 basic info after view checked ``` ================================================ FILE: Angular系列/03_Angular2的那些Decorator.md ================================================ --- title: 03_Angular2的那些Decorator date: 2017/02/21 14:47:10 --- ## 0、Decorator ``Decorator`` 是ECMAScript中建议的标准,使得我们可以在设计时对类和属性进行注解和修改。 ## 1、Angular2的Decorator 在Angular2的早期版本(使用AtScript)中,我们是使用Annotation(注解),它以一个声明的方式将元数据添加到代码中。 在后来迁移到TypeScript的时候,我们可以使用 Decorator 和 Annotation 。作为使用者来说,使用 Decorator 和 Annotation 几乎是一样的,唯一的区别是我们没有去控制 Annotation 如何将元数据添加到我们的代码中,而 Decorator 是对 这些 Annotation 的最终实现。 从长远看,我们更应该多关注 Decorator ,因为它才是真正的标准建议。 ## 2、Angular2的那些Decorator ### 2.1、In angular2/core #### 2.1.1、Component ``Component`` 用于声明可重用的UI构建模块(组件),每个 Angular component都要求有一个 ``@Component`` 注解,它指定了组件何时被实例化,哪些属性和 hostListeners 被绑定。 当组件实现(implements)了一些生命周期钩子(lifecycle-hooks),那么将在特定的时间点访问这些钩子的回调函数。 **如何使用** ```javascript @Component({ selector: 'demo', // 配置选择器 inputs: [], outputs: [], properties: [], events: [], host: {}, providers: [], // 设定所依赖的Providers(ng1中的service,provider,factory) exportAs: '', moduleId: '', //设定模块ID viewProviders: [], queries: {}, //changeDetection templateUrl : '', // 指定模板文件URL,和template冲突 template: 'Hello {{name}}!', //指定模板内容,和templateUrl冲突 styleUrls: [], // 设定组件依赖的样式表文件 styles : [], //设定组件依赖的样式 directives: [], //设定所依赖的Directives(ng1中的directives) pipes: [] //设定所依赖的Pipes(ng1中的filter) //encapsulation }) export class Demo { private name: string = 'World'; } ``` **注:从继承关系来看,Component extends Directive。** #### 2.1.2、Directive ``Directive`` 允许你在DOM元素上附加行为。如果指令带有内嵌视图,那么就成为了组件。 指令同样也有生命周期钩子。使用方式和 Component 雷同。 指令允许多种注入方式来实例化: 1、无注入 -- 该指令没有外部依赖 ```javascript // 空构造,无注入 @Directive({ selector: '[my-directive]' }) class MyDirective { constructor() { } } ``` 2、组件级别的注入 -- 该指令依赖一些外部服务 ```javascript import {User} from 'xxx'; @Directive({ selector: '[my-directive]' }) class MyDirective { constructor(user: User) { //依赖外部服务 } } ``` 3、注入当前元素的其它指令 --该指令依赖当前元素上的其他指令,搭配其他指令一起使用 ```javascript import {User} from 'xxx'; @Directive({ selector: '[my-directive]' }) class MyDirective { constructor(depDirective: DepDirective) { //依赖当前元素上的其他指令 } } ``` ```html
``` 4、注入当前元素、父元素、更上层的父元素上的指令 --该指令依赖上层元素的指令 ```javascript import {User} from 'xxx'; @Directive({ selector: '[my-directive]' }) class MyDirective { //要使用 @Host() constructor(@Host() depDirective: DepDirective) { //可以依赖父辈元素上的指令 } } ``` ```html
``` 5、注入直接子集集合元素的的指令 --该指令依赖直接子元素的指令 ```javascript import {User} from 'xxx'; @Directive({ selector: '[my-directive]' }) class MyDirective { //使用 @Query ,依赖直接子元素上的指令 constructor(@Query(DepDirective) depDirective: QueryList) { } } ``` ```html

``` 6、注入后代集合元素的指令 --该指令依赖后代元素的指令 ```javascript import {User} from 'xxx'; @Directive({ selector: '[my-directive]' }) class MyDirective { //使用 @Query ,依赖直接子元素上的指令 constructor(@Query(DepDirective, {descendants: true}) depDirective: QueryList) { } } ``` ```html

``` 7、可选注入 --该指令的依赖是可选的。 ``` @Directive({ selector: '[my-directive]' }) class MyDirective { // 使用 @Optional 标记,依赖是可选的。 constructor(@Optional() depDirective:DepDirective) { } } ``` **注:以上多种注入方式也适用于 Component 。** #### 2.1.3、Injectable ``Injectable`` 允许使用注入。在编写组件/指令时,如果有注入,那么就需要将指令/组件标记为可注入的。 #### 2.1.4、Pipe ``Pipe`` 允许我们定义管道方法,实现ng1中filter类似的功能。 如何编写一个Pipe? ```javascript @Pipe({name: 'lowercase'}) class Lowercase { transform(v: string, args: any[]) { return v.toLowerCase(); } } ``` ### 2.2、In angular2/router #### 2.2.1、CanActivate ``CanActivate`` 允许我们在使用路由时,检查组件的权限,来确定是否可以使用。 ```javascript @Component({selector: 'control-panel-cmp', template: `
Settings: ...
`}) @CanActivate(checkIfWeHavePermission) class ControlPanelCmp { } ``` #### 2.2.2、RouteConfig ``RouteConfig`` 用于我们配置路由。 使用如下: ``` @Component({ selector: 'dojo-app', moduleId: module.id, templateUrl: 'app.html', styleUrls: ['app.css'], directives: [ROUTER_DIRECTIVES, HeaderComponent] }) @RouteConfig([ {path: '/', name: 'Home', component: HomeComponent}, {path: '/about', name: 'About', component: AboutComponent} ]) export class AppComponent{ constructor() { } } ``` ## 3、参考 1. [https://angular.io/docs/ts/latest/api/index.html#!?apiType=Decorator](https://angular.io/docs/ts/latest/api/index.html#!?apiType=Decorator) 2. [https://angular.io/docs/ts/latest/api/index.html#!?apiFilter=metadata](https://angular.io/docs/ts/latest/api/index.html#!?apiFilter=metadata) ================================================ FILE: Angular系列/04_Angular2指令简析.md ================================================ --- title: 04_Angular2指令简析 date: 2017/02/21 14:47:10 --- ## 0、Angular2指令 在Angular1中,就已经有了指令的概念。Angular1中的指令用于实现可复用UI部件,也用于操作dom元素。 那么在Angular2中的指令是一样的东西么? Angular2中有组件的概念,指令这个东西就变得更加纯粹。 Angular2的指令有三种: - 组件 - 属性指令 - 结构指令 组件是有模板的指令,是指令的中一个另类,因为它使用@Component来装饰,而不是@Directive。 属性指令用于改变现有元素的展现和行为,使用的时候它们看起来像是正常的HTML属性,所以称之为属性指令。如ngModel指令。 结构指令通过添加、删除和替换DOM树中的元素来改变布局,由于可以更改DOM结构,所以称之为结构指令。如ngIf,ngSwitch。 由于Angular2的API好不够稳定,书写该文时,采用的是Angular2 rc1(@angular rc.1)版本,其他版本请自行测试。 ## 1、属性指令 接着,我们就一步步来实现一个属性指令 dynamicColor 。 首先,我们需要创建一个ts文件,然后把指令的骨架搭建起来。 ```typescript import {Directive} from '@angular/core'; @Directive({ selector: '[dynamicColor]' }) export class DynamicColorDirective{ constructor(){ } } ``` 以上代码中,我们创建了一个dynamicColor指令。 接下来,我们来实现具体的功能,可以设置元素的背景色和前景色,并能实现事件通知。 要实现动态背景色和前景色,那我们需要额外附加两个属性bgColor和color。 要想在指令中获取这两个属性值,那么我们可以通过@Input方式或者是inputs属性,代码如下: ```typescript import {Directive, Input} from '@angular/core'; @Directive({ selector: '[dynamicColor]' }) export class DynamicColorDirective{ @Input() private bgColor: string; @Input() private color: string; constructor(){ } } ``` 或者是: ```typescript import {Directive, Input} from '@angular/core'; @Directive({ selector: '[dynamicColor]', //注意,在之前的版本中,使用properties属性,而且,当前还可以使用。 inputs: [ 'bgColor: bgColor', //字符串以冒号隔开,前者是DynamicColorDirective的属性,后者的html元素的属性 'color: color' ] }) export class DynamicColorDirective{ // @Input() private bgColor: string; // @Input() private color: string; constructor(){ } } ``` 那么html中又应该如何传递值给指令呢? ```html
Hi!
``` **注意:在html元素的属性上,我们可以有两种写法。一种是直接书写属性,此时会把属性值原样传递给指令。第二种是使用[属性],此时属性值应该是表达式(可以使用变量,判断等语句),传递给指令的是表达式的结果。** 我们又如何在后端查看这两个值呢? 直接在constructor中console?明确的说是不行的,因为constructor的代码会先于绑定执行。 这个时候,我们就需要借助指令的生命周期钩子。 指令的生命周期钩子有如下几个: 1. ngOnInit --初始化时 2. ngOnChanges -- 属性绑定之时(会有一次inputs属性绑定先于初始化) 3. ngDoCheck -- 执行属性检查时 4. ngOnDestroy -- 指令释放时 了解了生命周期钩子,我们就可以通过ngOnInit来查看绑定好的属性值了。 ```typescript import {Directive, Input, OnInit} from '@angular/core'; @Directive({ selector: '[dynamicColor]', inputs: [ 'bgColor: bgColor', //字符串以冒号隔开,前者是DynamicColorDirective的属性,后者的html元素的属性 'color: color' ] }) export class DynamicColorDirective implements OnInit{ // @Input() private bgColor: string; // @Input() private color: string; constructor(){ } ngOnInit(){ console.log('bgColor', this.bgColor); console.log('color', this.color); } } ``` 接下来,我们需要设置元素的background color和color样式,那么我们必须要拿到这个而元素的引用, 并在初始化之后进行绑定。 ```typescript import {Directive, Input, OnInit, ElementRef} from '@angular/core'; @Directive({ selector: '[dynamicColor]', inputs: [ 'bgColor: bgColor', //字符串以冒号隔开,前者是DynamicColorDirective的属性,后者的html元素的属性 'color: color' ] }) export class DynamicColorDirective implements OnInit{ private nativeElement: any; // @Input() private bgColor: string; // @Input() private color: string; constructor(el: ElementRef){ this.nativeElement = el.nativeElement; } private _setElementStyle(): void{ this.nativeElement.style.backgroundColor = this.bgColor; this.nativeElement.style.color = this.color; } ngOnInit(){ console.log('bgColor', this.bgColor); console.log('color', this.color); this._setElementStyle(); } } ``` 当从元素上绑定的属性变化时,又应该从哪里获取到变更呢?这就需要借助生命周期里面的OnChanges函数,代码如下: ```typescript import {Directive, Input, ElementRef, OnInit, OnChanges} from '@angular/core'; @Directive({ selector: '[dynamicColor]', inputs: [ 'bgColor: bgColor', //字符串以冒号隔开,前者是DynamicColorDirective的属性,后者的html元素的属性 'color: color' ] }) export class DynamicColorDirective implements OnInit, OnChanges{ private nativeElement: any; // @Input() private bgColor: string; // @Input() private color: string; constructor(el: ElementRef){ this.nativeElement = el.nativeElement; } private _setElementStyle(): void{ this.nativeElement.style.backgroundColor = this.bgColor; this.nativeElement.style.color = this.color; } ngOnInit(){ console.log('bgColor', this.bgColor); console.log('color', this.color); this._setElementStyle(); } ngOnChanges(){ console.log('bgColor-change', this.bgColor); console.log('color-change', this.color); this._setElementStyle(); } } ``` 由于每次变化都会触发OnChanges,那么为了提高性能,我们可以在这里加入一个节流函数。 ```typescript private _setElementStyle(): void { clearTimeout(this.timeoutId); this.timeoutId = setTimeout(() => { this.nativeElement.style.backgroundColor = this.bgColor; this.nativeElement.style.color = this.color; }, 500); } ``` 有了节流函数,我们就不太确定到底执行了几次更新操作了。这个时候,我们可以加入事件通知。这就涉及到指令的@Output了。 ```typescript @Output() private updated: EventEmitter = new EventEmitter(); ``` 也等同于: ```typescript outputs: [ 'updated: updated' ] private updated: EventEmitter = new EventEmitter(); ``` **注意:在之前的版本中,也可以用events属性来替代outputs,现在也还可以使用** 要对外发出通知,只需要使用如下代码: ```typescript this.updated.emit('updated'); this.updated.next('updated2'); ``` HTML标签使用时,代码如下: ```html
Hi!
``` 至此,我们这个指令就已经完成了,所有代码如下: ```typescript //dynamicColor.directive.ts import {Directive, Input, Output, ElementRef, EventEmitter, OnInit, OnChanges} from '@angular/core'; @Directive({ selector: '[dynamicColor]', inputs: [ 'bgColor: bgColor', //字符串以冒号隔开,前者是DynamicColorDirective的属性,后者的html元素的属性 'color: color' ], outputs: [ 'updated: updated' ] }) export class DynamicColorDirective implements OnInit, OnChanges { private nativeElement: any; private timeoutId: any; // @Input() private bgColor: string; // @Input() private color: string; // @Output() private updated: EventEmitter = new EventEmitter(); constructor(el: ElementRef) { this.nativeElement = el.nativeElement; } private _setElementStyle(): void { clearTimeout(this.timeoutId); //先清除已有的timeout //保证只执行最后一次。 this.timeoutId = setTimeout(() => { this.nativeElement.style.backgroundColor = this.bgColor; this.nativeElement.style.color = this.color; this.updated.emit('updated'); this.updated.next('updated2'); }, 500); } ngOnInit() { console.log('bgColor', this.bgColor); console.log('color', this.color); this._setElementStyle(); } ngOnChanges() { console.log('bgColor-change', this.bgColor); console.log('color-change', this.color); this._setElementStyle(); } } ``` ```html //test.html

Dynamic Color Directive

Hi!
``` ```typescript //test.component.ts export class TestComponent{ private testBgColor: string = 'blue'; private testColor: string = 'red'; constructor(){ } private test(data){ console.log('data', 'my', data); } private notify(data){ console.log('notify = ', data); } } ``` *思考一下?我们还有没有更简单的方式实现以上的效果呢?* ```typescript import {Directive, EventEmitter} from '@angular/core'; @Directive({ selector: '[dynamicColor]', inputs: [ 'bgColor: bgColor', //字符串以冒号隔开,前者是DynamicColorDirective的属性,后者的html元素的属性 'color: color' ], outputs: [ 'updated: updated' ], host: { '[style.backgroundColor]': 'bgColor', '[style.color]': 'color' } }) export class DynamicColorDirective { private bgColor: string; private color: string; private updated: EventEmitter = new EventEmitter(); constructor() { } } ``` 通过host直接在元素上添加绑定。 ## 2、结构指令 结构指令帮助我们修改dom结构,我们就简单实现一个templateInclude指令。 ```typescript import {Directive, Input, Output, ElementRef, EventEmitter, OnChanges} from '@angular/core'; import {Http} from '@angular/http'; @Directive({ selector: '[templateInclude]' }) export class TemplateIncludeDirective implements OnChanges { private nativeElement: any; @Input('templateInclude') private templateUrl: string; @Output() private loaded: EventEmitter = new EventEmitter(); constructor(el: ElementRef, private http: Http) { this.nativeElement = el.nativeElement; } private _setTemplate() { this.http.get(this.templateUrl) .subscribe(res => { this.nativeElement.innerHTML = res.text(); this.loaded.next(`${this.templateUrl} loaded`); }); } ngOnChanges() { console.log(this.templateUrl); this._setTemplate(); } } ``` ## 3、总结 **1、指令的元数据有很多属性可以使用** ```typescript class DirectiveMetadata { selector : string //指令使用的标记(选择器) inputs : string[] //输入参数绑定 properties : string[] //属性绑定(过期,请使用inputs) outputs : string[] //输出参数绑定 events : string[] //事件绑定(过期,请使用outputs) host : {[key: string]: string} //宿主元素属性设置 providers : any[] //服务绑定 bindings : any[] //服务绑定(过期,请使用providers) exportAs : string //导出名称 queries : {[key: string]: any} //用于指令依赖关系 } ``` **2、在使用指令(不仅仅是指令)进行绑定的时候,[]表示输入属性,()表示输出属性和事件** **3、尽量使用统一的做法,用装饰器优于在属性上做绑定** **4、在编写指令(不仅限于指令)时,将class中内容按照特定顺序进行排列,推荐顺序如下(个人建议,仅供参考):** 1. 私有变量 2. 共有变量 3. @Input变量 4. @Output变量 5. 构造函数 6. 私有方法(建议下划线开头) 7. 公有方法 8. 生命周期钩子方法 ================================================ FILE: Angular系列/05_Angular2组件简析.md ================================================ --- title: 05_Angular2组件简析 date: 2017/02/21 14:47:10 --- ## 0、Angular2组件 **注:由于Angular2的API好不够稳定,书写该文时,采用的是Angular2 rc1(@angular rc.1)版本,其他版本请自行测试。** 在上篇中,我们已经讲到了指令,这篇呢,我们一起来看看Angular2组件是怎么一回事。 首先,组件也是指令,组件是一种有模板(内嵌视图)的特殊指令。 从元数据[指令源代码](https://github.com/angular/angular/blob/2.0.0-rc.1/modules/%40angular/core/src/metadata/directives.ts)中也可以看出组件与指令的关系: ```typescript export class ComponentMetadata extends DirectiveMetadata { } ``` 相比 ``Directive``, ``Component`` 新增了一些属性,如下: ```typescript { changeDetection : ChangeDetectionStrategy; 定义变化检测类型 viewProviders: any[]; 用于在组件中注入特定的class。一般是实体类 moduleId: string; 定义主键的ID templateUrl: string; 如ng1,外部模板地址 template: string; 如ng1,内嵌模板内容 styleUrls: string[]; 外部样式表文件 styles: string[]; 内嵌样式 directives: Array; 使用到的指令 pipes: Array; 使用到的管道 encapsulation: ViewEncapsulation 封装视图的类型 } ``` ## 1、组件生命周期 既然组件也是指令,那么指令所拥有的四大阶段组件也同样拥有。 而且,由于组件带有视图,还多了几个和视图相关的生命周期阶段。如下: 1. ngAfterContentInit 组件内容渲染到页面之后触发 2. ngAfterContentChecked 检查组件内容绑定数据后触发 3. ngAfterViewInit 创建组件视图之后触发 4. ngAfterViewChecked 检查组件视图绑定数据后触发 它们的执行顺序也和以上顺序一致。 ## 2、整一个组件试试? 接下来,我们就简单实现一个组件 ``TodoList`` 来实验一下以上的知识点。 首先,搭建好一个简单的架子,如下: ```typescript import {Component} from '@angular/core'; @Component({ selector: 'todo-list' }) export class TodoListComponent{ constructor(){ } } ``` 使用组件装饰器 ``Component`` 来定义一个组件,注意其中 ``selector`` 属性和 ``Directive`` 中的写法不一样了。 组件必须以标签的方式存在,所以 selector 属性值仅仅只需要写标签名就可以了,不再需要其他特别的符号了。 组件和指令最大的差别就在于模板,所以我们接下来添加上模板代码: ```typescript import {Component} from '@angular/core'; @Component({ selector: 'todo-list', template: `

Todo List

` }) export class TodoListComponent{ constructor(){ } } ``` 如上,一个简单的模板就搞好了。这里需要注意 ``templateUrl`` 和 ``template`` 是互斥的两个属性。一般来说只选择一个赋值,如果两者都存在,那么会采用 ``tempalte`` 的值。 模板有了,我们就来点业务逻辑: 先假设Todo有三个状态: ```typescript enum TodoStatus { Open, Processing, Closed } ``` 在来定义Todo的实体类: ```typescript class Todo { private name: string; private description: string; private status: TodoStatus; constructor(name: string, status: TodoStatus, description?: string) { this.name = name; this.status = status; this.description = description || ''; } } ``` 接着来实现一个组件: ```typescript @Component({ selector: 'todo-list', template: require('./todo-list.component.html') }) export class TodoList { private todos: Array; private todo: { name: string, desc: string } = { name: '', desc: '' }; constructor() { this.todos = []; } addTodo() { this.todos.push(new Todo(this.todo.name, TodoStatus.Open, this.todo.desc)); } } ``` 这个时候,HTML页面内容如下: ```html
No todos.
  • {{todo.name}} - {{todo.description}}

Name:
Description:

``` 功能做好了,我们得给它来点样式美化。 此时我们仅仅需要实现一点样式: ```css .todo-list{ margin: 0; padding: 0; } .todo-list li{ list-style: none; border: 1px solid red; } ``` 然后在组件装饰器中申明就可以了: ```typescript @Component({ selector: 'todo-list', template: require('./todo-list.component.html'), styles : [require('./todo-list.component.css')] }) ``` 至此,一个简单的可以添加todo的todo-list就已经完成了。 **注意:注入@Input,@Output之类的和Directive都是一样的,此处就不再演示了。** ================================================ FILE: Angular系列/06_Angular2管道(Pipe)简析.md ================================================ --- title: 06_Angular2管道(Pipe)简析 date: 2017/02/21 14:47:10 --- ## 0、Angular2 Pipe **注:由于Angular2的API好不够稳定,书写该文时,采用的是Angular2 rc1(@angular rc.1)版本,其他版本请自行测试。** 对于 ``Pipe``,其实我们并不陌生。在angular1中,它被称之为 ``filter``。 ``Pipe``用于对数据进行格式化处理,就好比管道,一个进一头出,中间过程就是管道的处理逻辑。 Angular2中的 ``Pipe`` 本质上是包含特定方法的类。 ## 1、编写一个简单Pipe ``Pipe`` 相对于指令和组件来说,非常简单,我们仅仅需要编写一个类: ```typescript class EmptyToZero{ } ``` 实现特定的方法: ```typescript exprt class EmptyToZero{ transform(v: any, args: any[]){ if(v === undefined || v === null || v === ''){ return 0; } return v; } } ``` 使用 ``Pipe`` 装饰,请设定名称: ```typescript import {Pipe} from '@angular/core'; @Pipe({ name: 'empty2zero' }) export class EmptyToZero { transform(v: any, args: any[]) { if (v === undefined || v === null || v === '') { return 0; } return v; } } ``` 如何使用? ```html {{ value | empty2zero}} ``` 当然前提是要在Component中申明要使用的Pipe: ```typescript @Component({ selector: 'todo-list', template: require('./todo-list.component.html'), styles : [require('./todo-list.component.css')], pipes:[EmptyToZero] }) ``` ## 2、有状态的Pipe 在使用 ``Pipe`` 装饰器的时候,我们可以提供两个参数: ```typescript @Pipe({ name: 'empty2zero', //string类型,必填项,指定pipe的名称 pure: true //boolean类型,可选项,默认为true,设定为true时,表示无状态管道。无论是输入或者是什么参数的改变都会触发重新计算结果。 }) ``` 有状态的 ``Pipe`` 可以收到一个Promise对象或者检测输入和自动订阅输入,最终返回一个可触发的值。 要使用有状态的管道,必须将pure属性设置为false。 比较典型的有状态异步管道如下: ```typescript import {Pipe} from 'angular2/core'; @Pipe({ name: 'fetch', pure: false }) export class FetchJsonPipe { private fetchedValue:any; private fetchPromise:Promise; transform(value:string, args:string[]):any { if (!this.fetchPromise) { this.fetchPromise = window.fetch(value) .then((result:any) => result.json()) .then((json:any) => this.fetchedValue = json); } return this.fetchedValue; } } ``` ================================================ FILE: Angular系列/07_Angular2使用路由.md ================================================ --- title: 07_Angular2使用路由 date: 2017/02/21 14:47:10 --- ## 0、关于路由 此处所说的路由是指URL路由(也许叫URL Rewrite)。其实是把网址(URL)映射到相关Controller、Component的这个功能。 Angular2的路由其实也就是URL路由,在Angular2中,有两个模块提供了路由功能,``@angular/router-deprecated`` 和 ``@angular/router``。 ``@angular/router-deprecated`` 从名称也可以看出,它是一个过时的模块(beta版本中它的名字是 ``angular2/router``,在rc版本被更名)。但疑惑的是,它一直存在于 ``@angular`` 包中。在这里,我主要使用 ``@angular/router`` 来实现路由功能。 ## 1、使用Angular2路由 建议在根组件中配置路由。要使用路由,必须先依赖 ``ROUTER_PROVIDERS``;如果要使用路由指令,必须先依赖 ``ROUTER_DIRECTIVES``。 大概结构如下: ```typescript //bootstrap.ts import {provide} from '@angular/core'; import {bootstrap} from '@angular/platform-browser-dynamic'; import {LocationStrategy, HashLocationStrategy} from '@angular/common'; import {AppComponent} from './app/app.component'; bootstrap(AppComponent, [ provide(LocationStrategy, { useClass: HashLocationStrategy }) ]); ``` ```typescript //app.component.ts import {Component, provide} from '@angular/core'; import {Routes, ROUTER_DIRECTIVES, ROUTER_PROVIDERS} from '@angular/router'; import {HomeComponent} from './../home/home.component'; import {AboutComponent} from './../about/about.component'; @Component({ selector: 'demo-app', template: `

Angular2 Router Test

Home About `, directives: [ROUTER_DIRECTIVES], providers: [ROUTER_PROVIDERS] }) @Routes([ { path: '/home', component: HomeComponent}, { path: '/about', component: AboutComponent } ]) export class AppComponent { constructor() { console.log('app init'); } } ``` 通过 ``@Routes``,我们可以配置路由对应的组件。当然,要求这些组件必须已经存在。在配置路由节点的时候,我们仅仅需要提供 ``path`` 和 ``component`` 参数。 在模板中使用 ``[routerLink]`` 可以配置连接,它的值是一个数组,第一个元素是要导向的url,第二个参数是路由参数。 除了使用 ``[routerLink]`` 实现路由跳转,还可以使用特定服务来跳转,代码如下: ```typescript this.router.navigate(['/about']); this.router.navigateByUrl('/about'); ``` 当URL比较复杂,如/home/test/index时,使用navigate方式如下: ```typescript this.router.navigate(['home', 'test' ,'index']); ``` ## 2、路由参数 有时候,我们需要给路由传递一些参数,这个时候就需要在配置路由的时候,指定参数。使用 ``[routerLink]`` 的方式如下: ```html About ``` 那么如何获取这个参数呢?就需要在 ``AboutComponent`` 组件中通过 ``RouteSegment`` 来获取,代码如下: ```typescript export class AboutComponent { constructor(private routeSegment: RouteSegment) { console.log('about init', 'params:', routeSegment.parameters); } } ``` ## 3、嵌套路由(子路由) 一般来说,我们写一个中大型的应用程序,一个一级路由根本就不够使用。这个时候,就可以使用嵌套路由来把应用程序拆分成很多小的模块。 在这种情况下,就需要我们的子组件也需要使用 ``@Routes`` 来申明自己的路由体系。 ```typescript //about.component.ts import {Component} from '@angular/core'; import {ROUTER_DIRECTIVES, Routes, RouteSegment} from '@angular/router'; import {AboutUserComponent} from './about-user.component'; import {AboutMeComponent} from './about-me.component'; @Component({ selector: 'demo-about', template: `

About

Go to Home Go to About User Go to About Me `, directives: [ROUTER_DIRECTIVES] }) @Routes([ { path: '/', component: AboutUserComponent }, { path: '/user/:id', component: AboutUserComponent }, { path: '/me', component: AboutMeComponent } ]) export class AboutComponent { private id: number = 1; constructor(private routeSegment: RouteSegment) { console.log('about init', 'params:', routeSegment.parameters); } } ``` 在这段代码里,我们需要注意,``[routerLink]``的值,有些是 ``['/home']``, 也有 ``['./me]`` 这种,它们有什么区别呢? 其实直接 '/home' 是指从根路径开始计算,也就是跳转到父路由,真实路径就是 /home。如果是使用的 './me' 这种形式,那么是相对路径,所以点击这个链接,跳转到的实际上是 /about/me 这个地址。 ## 4、生命周期钩子(拦截器) 在路由中,我们可以通过实现 ``CanDeactivate`` 来控制路由是否可以被解除;还可以通过实现 ``OnActivate`` 来控制路由激活后的操作。 当我们在 ``HomeComponent`` 中编写如下语句时: ```typescript routerCanDeactivate(curTree: RouteTree, futureTree: RouteTree) { console.log('abc'); return new Promise((resolve, reject) => { resolve(false); }); } ``` 当进入Home页之后,我们就已经无法跳出了。 当我们在 ``HomeComponent`` 中编写如下语句时: ```typescript routerOnActivate(currSegment: RouteSegment, prev: RouteSegment, currTree: RouteTree, prevTree: RouteTree){ console.log('succeed'); } ``` 每一次跳转到Home组件,我们都能在控制台看到输出 succeed 。 **很遗憾的是,暂时没有发现有全局的路由钩子,这也就意味着,我们没法在一个地方控制所有的路由是否允许被解除。** ## 5、后记 以上就是Angular2路由的简单使用了。相关Demo,请点击 [这里](https://github.com/hstarorg/HstarDemoProject/tree/master/angular2_demo) 在Angular2中,还有一个路由是 ``@angular/router-deprecated``,它是之前的路由方式,由于已经被标注为过期,这里就不在说明,如果想了解一下,可以查看 [Demo](https://github.com/hstarorg/HstarDemoProject/tree/master/angular2_demo/angular2-router-deprecated-test) 至于更复杂的动态路由,动态加载组件等等,未完待续... ================================================ FILE: Angular系列/08_Angular2动态加载组件.md ================================================ --- title: 08_Angular2动态加载组件 date: 2017/02/21 14:47:10 --- ## 0、为什么需要动态加载 在一个比较大的应用程序中,我们不可能将所有的业务逻辑一次性加载出来,比较浪费资源,因为单个用户一般用不到所有的功能,这个时候,就需要部分组件动态加载了。 ## 1、Angular2如何动态加载组件 在Angular2,有一个服务是 ``DynamicComponentLoader``,我们就可以通过它来进行动态加载组件。 首先要使用它的话,我们必须要在providers中指定它。 另外,它还有一些必须的依赖,是 ``injector``,当引入了这些元素之后,我们就可以实现一个加载组件的方法: ```javascript class AppComponent { constructor(dynamicComponentLoader, viewContainerRef, injector) { this.dynamicComponentLoader = dynamicComponentLoader; this.viewContainerRef = viewContainerRef; this.injector = injector; } //动态加载组件的方法,需要传入一个组件 loadComponent(component) { this.viewContainerRef.clear(); this.dynamicComponentLoader.loadAsRoot(component, '#component-container', this.injector) .then((componentRef) => { //必须要这样来编写,否则会导致双向绑定表达式获取不到值。 componentRef.changeDetectorRef.detectChanges(); componentRef.onDestroy(() => { componentRef.changeDetectorRef.detach(); }); return componentRef; }); } } ``` 在以上代码中,核心就是 ``dynamicComponentLoader`` 的 ``loadAsRoot`` 方法,这个方法里面有个参数是 ``'#component-container'``,实际上指定将动态加载的组件放置的容器, ```html
``` 通过这样的方式,我们就能够动态的把组件加载到页面上了。 **注:``dynamicComponentLoader`` 还有一个加载方法是 ``loadNextToLocation(component, viewContainerRef)``,只需要提供要动态加载的组件和一个容器引用,就可以将组件加载到容器中了。** ================================================ FILE: Angular系列/09_Angular2使用ui-router-ng2.md ================================================ --- title: 09_Angular2使用ui-router-ng2 date: 2017/02/21 14:47:10 --- ## 0、导言 Angular2的路由组件从beta到rc经历了多次变更,知道rc.4都没有完全稳定下来。其次它的功能也并不强大,全局钩子,动态加载,状态控制都不支持。 如果是使用Angular1,那这个时候我们一般会选择 ``ui-router`` 这一个强大的基于状态的路由。 其实,``ui-router`` 也提供了一个Angular2的版本,那就是 ``ui-router-ng2``。 我们就来简单的尝试下它的使用,和利用它来实现动态加载一批组件(结合webpack)。 ## 1、引入 ``ui-router-ng2`` 要使用 ``ui-router-ng2``,我们必须要先通过 ``npm install ui-router-ng2`` 来安装该包。 安装成功之后,我们需要实现一个 ``UIRouterConfig`` 的实例,代码如下: ```typescript import { Injectable } from '@angular/core'; import { UIRouter, UIRouterConfig } from 'ui-router-ng2'; import {AboutComponent} from './about.component'; @Injectable() export class AppRouterConfig implements UIRouterConfig { constructor() { } configure(uiRouter: UIRouter) { uiRouter.stateRegistry.register({ name: 'about', component: AboutComponent, url: '/about' }); } } ``` 一般做法,我们需要在 ``configure`` 方法中,注册路由状态对象。 当实现了 ``UIRouterConfig`` 之后,我们就可以在应用启动时来使用它了,具体代码如下: ```typescript import { enableProdMode, provide, PLATFORM_DIRECTIVES } from '@angular/core'; import { APP_BASE_HREF, LocationStrategy, HashLocationStrategy, PlatformLocation } from '@angular/common'; import { BrowserPlatformLocation } from '@angular/platform-browser'; import { bootstrap } from '@angular/platform-browser-dynamic'; import { UIROUTER_PROVIDERS, UIRouterConfig, UIROUTER_DIRECTIVES } from 'ui-router-ng2'; import { RootComponent } from './../shell'; import { AppRouterConfig } from './routes'; enableProdMode(); bootstrap(RootComponent, [ provide(APP_BASE_HREF, { useValue: '/' }), provide(LocationStrategy, { useClass: HashLocationStrategy }), provide(PlatformLocation, { useClass: BrowserPlatformLocation }), ...UIROUTER_PROVIDERS, provide(UIRouterConfig, { useClass: AppRouterConfig }), provide(PLATFORM_DIRECTIVES, { useValue: UIROUTER_DIRECTIVES, multi: true }) ]) .then(x => { console.log('app started...'); }) .catch(error => console.log(error)); ``` 其中最关键是和路由相关的代码是: ```typescript ...UIROUTER_PROVIDERS, //依赖路由提供者 provide(UIRouterConfig, { useClass: AppRouterConfig }), //指定RouterConfig provide(PLATFORM_DIRECTIVES, { useValue: UIROUTER_DIRECTIVES, multi: true }) //依赖路由指令 ``` 在经过这些步骤之后,我们的项目就已经可以使用 ``ui-router-ng2`` 来进行路由管理了。 ## 2、路由钩子 ``ui-router-ng2`` 提供了很强大的路由钩子函数,可以让我们很方便的对路由的各个阶段进行控制。 使用方式如下: ```typescript import {Injectable, Inject} from '@angular/core'; import {UIRouter, UIRouterConfig} from 'ui-router-ng2'; import {AboutComponent} from './about.component'; @Injectable() export class AppRouterConfig implements UIRouterConfig { constructor() { } configure(uiRouter: UIRouter) { uiRouter.stateRegistry.register({ name: 'about', component: AboutComponent, url: '/about' }); // 以下t均为Transition实例 uiRouter.transitionService.onBefore({}, t => { console.log('onBefore', t); //路由跳转之前 }); uiRouter.transitionService.onStart({}, t => { console.log('onStart', t); //路由跳转开始 }); uiRouter.transitionService.onExit({}, t => { console.log('onExit', t); //路由跳出时 }); uiRouter.transitionService.onRetain({}, t => { console.log('onRetain', t); //... }); uiRouter.transitionService.onEnter({}, t => { console.log('onEnter', t); //路由进入时 }); uiRouter.transitionService.onFinish({}, t => { console.log('onFinish', t); //路由跳转完成 }); uiRouter.transitionService.onSuccess({}, t => { console.log('onSuccess', t); //路由跳转成功 }); uiRouter.transitionService.onError({}, t => { console.log('onError', t); //路由跳转出错 }); } } ``` 通过以上的各个阶段,我们可以灵活控制跳转是否继续,每个钩子函数都接受 ``boolean`` 和 ``Promise``来让我们确定是否跳转。 ## 3、自定义回调处理invalidState 通过以上的方式,我们实现了状态路由,也实现了路由跳转的控制。接下来,我们另辟蹊径来实现动态加载。 由于当我们请求不合法的state时,uiRouter都会执行到 ``invalidCallbacks`` 这个函数,我这里就通过它加载动态模块。 实现代码如下: ```typescript configure(uiRouter: UIRouter) { ... //省略不相关代码 uiRouter.stateProvider.invalidCallbacks = [($from$, $to$) => { return new Promise((resolve, reject) => { let toStateName = $to$.name(); let moduleName = this._getModuleName(toStateName); if (!moduleName) { return; } this.moduleLoader.load(moduleName).then(_ => { let state = uiRouter.stateService.target(toStateName); resolve(state); }); }); }]; } ``` 接着,再来看看moduleLoader的代码: ```typescript import { Injectable, Inject } from '@angular/core'; import {Http} from '@angular/http'; import {UIRouter} from 'ui-router-ng2'; @Injectable() export class ModuleLoader { private uiRouter: UIRouter; private loadedModules: Set; constructor(private http: Http) { this.loadedModules = new Set(); } setRouter(uiRouter) { this.uiRouter = uiRouter; } load(moduleName): Promise { if (this.loadedModules.has(moduleName)) { return Promise.resolve(); } return new Promise((resolve, reject) => { this.http.get(`../dist/assets/js/${moduleName}.js`) .toPromise() .then(res => { let mod = eval(res.text()); mod.MODULE_STATES.forEach(state => { this.uiRouter.stateRegistry.register(state); }); this.loadedModules.add(moduleName); resolve(); }).catch(err => reject(err)); }); } } ``` 通过请求指定的文件,然后利用eval执行出具体的state数组,通过register方法,动态注入到我们的ui-router中。 **注意invalidCallbacks回调中的参数($from$, $to$),必须使用这两个名字,通过分析源代码发现它是用参数名做了匹配的,如果换成其他名称,会提示注入错误。** **invalidCallbacks回调中的逻辑非常关键,演示了如何获取原始要跳转的state,也演示了如何恢复继续跳转。** ## 4、更多待探索 当前对 ``ui-router-ng2`` 的探索还不够多,另外它本身也还在beta版本,该处理方式应该还有优化空间。 ================================================ FILE: Angular系列/Angular2踩坑大全.md ================================================ --- title: Angular2踩坑大全 date: 2017/02/21 14:47:10 --- ## Angular2的那些坑 1、同样的代码,引用Rxjs库的版本不对,就会导致在IE11下无法运行。(特定版本下重现) 正确的版本:[https://code.angularjs.org/2.0.0-beta.12/Rx.js](https://code.angularjs.org/2.0.0-beta.12/Rx.js) 2、在使用TypeScript编写Angular2代码时,一定要将注意 ``tsconfig.json``,其中 ``experimentalDecorators`` 和 ``emitDecoratorMetadata`` 必须要设置为true,否则无法使用依赖注入。 3、<router-outlet> 不能放在带有 *ngIf的容器内,否则会出现初始化时无法找到。 ================================================ FILE: Angular系列/利用Angular实现多团队模块化SPA开发框架.md ================================================ --- title: 利用Angular实现多团队模块化SPA开发框架 date: 2017-11-23 11:11:11 --- # 0、前言 当一个公司有多个开发团队时,我们可能会遇到这样一些问题: 1. 技术选项杂乱,大家各玩各 2. 业务重复度高,各种通用api,登录注销,权限管理都需要重复实现(甚至一个团队都需要重复实现) 3. 业务壁垒,业务之间的互通变得比较麻烦 4. 部署方式复杂,多个域名(或IP地址)访问,给用户造成较大的记忆难度 5. 多套系统,风格难以统一 6. 等等... 当然,解决方式有不少。以下就来讲解下我们这边的一种解决方案。 # 1、思路 **Angualr** `Angular`(注:非AngularJS) 是流行的前端 `MVVM` 框架之一,配合 `TypeScript`,非常适合用来做后台管理系统。由于我们曾今的一套 `Angularjs` 开发框架,我们继续选择 `Angular` 来进行实现,并尽可能的兼容 `AngularJS` 的模块。 **SPA** 选 `SPA` 还是多页?多余 `Mvvm` 来说,多页并不是标配。而且多页开发中,我们势必会关注更多的内容,包括通用header,footer,而不仅仅是页面的核心内容。 **模块化** 为什么要模块化呢?当有多个团队开发时(或者项目较大时),我们希望各个团队开发出来的东西都是 `模块`(不仅限于JS模块),这样可以让我们独立发布、更新、删除模块,也能让我们的关注点集中在特定模块下,提高开发效率和可维护性。 **平台化** 我们需要有一个运行平台(Website站点),允许在里面运行指定的模块。这样就可以实现单一入口,也容易实现通用逻辑,模块共享机制等等。 **兼容 AngularJS 模块** 在考虑将框架切换到 `Angular` 时,我们无可避免的会遇到如何兼容当前已有模块的问题。大致可选的方案如下: 1. 参考 `AngualrJS -> Angular` 官方升级指南,一步步将模块切换为 `Angular` 的实现。(工作量大,需要开发团队调整很多东西) 2. `iframe嵌入`,会有一定的体验差异,但对开发团队来说,基本无缝升级,也不需要做什么改动。(无疑,我们选择了这套方案) **模块打包** 我们需要将单个的模块打包为资源包,进行更新。这样才能做到模块独立发布,及时生效。 **CSS冲突** 在大型 `SPA` 中,CSS冲突是很大的一个问题。我们期望通过技术手段,能够根据当前使用的模块,加载和卸载CSS。 **跨页面共享数据** 由于涉及到iframe兼容旧有模块,我们无可避免,需要考虑跨窗口的页面共享。 **公共模块** 当一个团队的模块较多时,就会有一些公共的东西被抽取出来,这个过程,框架是无法知道的,所以这个时候,我们就需要考虑支持公共模块。(模块之间也有依赖关系) # 3、实现 基于以上的一些思考,我们首先需要实现一个基础的平台网站,这个没什么难度,直接用 `Angular` 实现即可。有了这一套东西,我们的登录注销,基本的菜单权限管理,也就实现了。 在这个基础之上,我们也能实现公共服务、公共组件了(封装一系列常用的玩意)。 ## 如何模块化?如何打包? **注意:此模块并非Angular本身的模块。** 我们通过约定,在 `modules/` 下的每一个目录都是一个业务模块。一个业务模块一般会包含,静态资源、CSS以及JS。根据这个思路,我们的打包策略就是:遍历 `modules/` 的所有目录,对每一个目录进行单独打包(webpack多entry打包+CSS抽取),另外使用 `gulp` 来处理相关的静态资源(在我看来,gulp才是构建工具,webpack是打包工具,所以混合使用,物尽其用)。 一般来说,`webpack` 会把所有相关依赖打包在一起,A、B 模块都依赖了 `@angular/core` 识别会重复打包,而且框架中,也已经打包了 `@angular` 相关组件。这个时候,常规的打包配置就不太合适了。那该如何做呢? 考虑到 `Angular` 也提供了 `CDN` 版本,所以我们将 `Angular` 的组件通过文件合并,作为全局全量访问,如 `ng.core`、`ng.common` 等。 既然这样,那我们打包的时候,就可以利用 `webpack` 的 `externals` 功能,把相关依赖替换为全局变量。 ```js externals: [{ 'rxjs': 'Rx', '@angular/common': 'ng.common', '@angular/compiler': 'ng.compiler', '@angular/core': 'ng.core', '@angular/http': 'ng.http', '@angular/platform-browser': 'ng.platformBrowser', '@angular/platform-browser-dynamic': 'ng.platformBrowserDynamic', '@angular/router': 'ng.router', '@angular/forms': 'ng.forms', '@angular/animations': 'ng.animations' } ``` 这样处理之后,我们打包后的文件,也就不会有 `Angular` 框架代码了。 **注:这个对引入资源的方式也有一定要求,就不能直接引入内层资源了。** ## 如何动态加载模块 打包完成之后,这个时候就要考虑平台如何加载这些模块了(发布过程就不说了,放到指定位置即可)。 什么时候决定加载模块呢?其实是访问特定路由的时候,所以我们的顶级路由,会使用Promise方法来实现,如下: ```js const loadModule = (moduleName) => { return () => { return ModuleLoaderService.load(moduleName); }; }; const dynamicRoutes = []; modules.forEach(item => { dynamicRoutes.push({ path: item.path, canActivate: [AuthGuard], canActivateChild: [AuthGuard], loadChildren: loadModule(item.module) }); }); const appRoutes: Routes = [{ path: 'login', component: LoginComponent }, { path: 'logout', component: LogoutComponent }, { path: '', component: LayoutComponent, canActivate: [AuthGuard], children: [ { path: '', component: HomeComponent }, ...dynamicRoutes, { path: '**', component: NotFoundComponent }, ] }]; ``` 我们把每个模块,按照 `umd` 的格式进行打包。然后再需要使用该模块的时候,使用动态构建 `script` 来运行脚本。 ```js load(moduleName, isDepModule = false): Promise { let module = window['xxx'][moduleName]; if (module) { return Promise.resolve(module); } return new Promise((resolve, reject) => { let path = `${root}${moduleName}/app.js?rnd=${Math.random()}`; this._loadCss(moduleName); this.http.get(path) .toPromise() .then(res => { let code = res.text(); this._DomEval(code); return window['xxx'][moduleName]; }) .then(mod => { window['xxx'][moduleName] = mod; let AppModule = mod.AppModule; // route change will call useModuleStyles function. // this.useModuleStyles(moduleName, isDepModule); resolve(AppModule); }) .catch(err => { console.error('Load module failed: ', err); resolve(EmptyModule); }); }); } // 取自jQuery _DomEval(code, doc?) { doc = doc || document; let script = doc.createElement('script'); script.text = code; doc.head.appendChild(script).parentNode.removeChild(script); } ``` CSS的动态加载相对比较简单,代码如下: ```js _loadCss(moduleName: string): void { let cssPath = `${root}${moduleName}/app.css?rnd=${Math.random()}`; let link = document.createElement('link'); link.setAttribute('rel', 'stylesheet'); link.setAttribute('href', cssPath); link.setAttribute('class', `xxx-module-style ${moduleName}`); document.querySelector('head').appendChild(link); } ``` 为了能够在模块切换时卸载,还需要提供一个方法,供路由切换时使用: ```js useModuleStyles(moduleName: string): void { let xxxModuleStyles = [].slice.apply(document.querySelectorAll('.xxx-module-style')); let moduleDeps = this._getModuleAndDeps(moduleName); moduleDeps.push(moduleName); xxxModuleStyles.forEach(link => { let disabled = true; for (let i = moduleDeps.length - 1; i >= 0; i--) { if (link.className.indexOf(moduleDeps[i]) >= 0) { disabled = false; moduleDeps.splice(i, 1); break; } } link.disabled = disabled; }); } ``` ## 公共模块依赖 为了处理模块依赖,我们可以借鉴 AMD规范 以及使用 `requirejs` 作为加载器。当前在我的实现里,是自定义了一套加载器,后期应该会切换到 AMD 规范上去。 ## 如何兼容 `AngularJS` 模块? 为了兼容 `AngularJS` 的模块,我们引入了 iframe, iframe会先加载一套曾今的 `AngularJS` 宿主,然后再这个宿主中,运行 `AngularJS` 模块。为了实现通信,我们需要两套平台程序中,都引入一个基于 `postMessage` 实现的跨窗口通信库(因为默认跨域,所以用postMessage实现),有了它之后,我们就可以很方便的两边通信了。 ## AOT编译 按照 `Angular` 官方的 `Aot` 编译流程即可。 ## 多Tab页 在后台系统中,多Tab页是比较常用了。但是多Tab页,在单页中使用,会有一定的性能风险,这个依据实际的情况,进行使用。实现多Tab页的核心就是如何动态加载组件以及如何获取到要加载的组件。 多Tab页面,实际就是一个 `Tabset` 组件,只是在 `tab-item` 的实现稍显特别一些,相关动态加载的源码: ```js @ViewChild('dynamicComponentContainer', { read: ViewContainerRef }) dynamicComponentContainer: ViewContainerRef; constructor( private elementRef: ElementRef, private renderer: Renderer2, private tabset: TabsetComponent, private resolver: ComponentFactoryResolver, private parentContexts: ChildrenOutletContexts ) { } public destroy() { let el = this.elementRef.nativeElement as HTMLElement; // tslint:disable-next-line:no-unused-expression el.parentNode && (el.parentNode.removeChild(el)); } private loadComponent(component: any) { let context = this.parentContexts.getContext(PRIMARY_OUTLET); let injector = ReflectiveInjector.fromResolvedProviders([], this.dynamicComponentContainer.injector); const resolver = context.resolver || this.resolver; let factory = resolver.resolveComponentFactory(component); // let componentIns = factory.create(injector); // this.dynamicComponentContainer.insert(componentIns.hostView); this.dynamicComponentContainer.createComponent(factory); } ``` **注意:要考虑组件卸载方法,如 destroy()** 为了获取到当前要渲染的组件,我们可以借用路由来抓取: ```js this.router.events.subscribe(evt => { if (evt instanceof NavigationEnd) { let pageComponent; let pageName; try { let nextRoute = this.route.children[0].children[0]; pageName = this.location.path(); pageComponent = nextRoute.component; } catch (e) { pageName = '$$notfound'; pageComponent = NotFoundComponent; } let idx = this.pageList.length + 1; if (!this.pageList.find(x => x.name === pageName)) { this.pageList.push({ header: `页面${idx}`, comp: pageComponent, name: pageName, closable: true }); } setTimeout(() => { this.selectedPage = pageName; }); } }); ``` # 3、总结 以上就是大概的实现思路以及部分相关的细节。其他细节就需要根据实际的情况,进行酌情处理。 该思路并不仅限于 `Angular` 框架,使用 `Vue、React` 也可以做到类似的效果。同时,这套东西也比较适合中小企业的后台平台(不一定非要多团队,一个团队按模块开发也是不错的)。 如需要了解更多细节,可以参考:[ngx-modular-platform](https://github.com/hstarorg/ngx-modular-platform),能给个 `star` 就更好了。 在此抛砖引玉,希望能集思广益,提炼出更好的方案。欢迎讨论和 `提Issue`, `发PR`。 ================================================ FILE: Angular系列/跟我学Angular2(1-初体验).md ================================================ --- title: 跟我学Angular2(1-初体验) date: 2017/02/21 14:47:10 --- ## 0、导言 Angular1作为最流行的前端MV*框架,给前端开发带来了极大的便利性。但是,仍然有许多不好的地方已经很难再改变了。Angular团队根据WEB发展的趋势和Angular1中积累的经验来开发了一个全新的Angular,也就是Angular2。 ## 1、优势 Angular2做了很激进的变化,带来的成果也是显而易见的。 1. 极大的提高了性能 2. 更强大的模块化 3. 改进的依赖注入 4. 对Web Component友好 5. 原生移动支持 - iOS 和 Android 6. 服务端渲染,搜索引擎优化 ## 2、工具链 由于Angular2面向未来,使用了太多还不被当前主流浏览器支持的技术,跑起来还真不是一个容易的事情,所以我们需要一个工具链: ![http://www.hubwiz.com/course/5599d367a164dd0d75929c76/img/toolchain.jpg](http://www.hubwiz.com/course/5599d367a164dd0d75929c76/img/toolchain.jpg) systemjs - 通用模块加载器,支持AMD、CommonJS、ES6等各种格式的JS模块加载 es6-module-loader - ES6模块加载器,systemjs会自动加载这个模块 traceur - ES6转码器,将ES6代码转换为当前浏览器支持的ES5代码。systemjs会自动加载 这个模块。 ## 3、Angular2 Hello world ### Step1、下载angular2 [https://angular.io/](https://angular.io/)是angular2的官网,我们需要通过npm进行下载angular2: npm install angular2 [https://www.npmjs.com/package/angular2](https://www.npmjs.com/package/angular2)。 ### Step2、引入angular2 ### Step3、 Hello angular ================================================ FILE: CSS3学习之路/CSS3入门之文本与字体.md ================================================ --- title: CSS3入门之文本与字体 date: 2017/02/21 14:47:10 --- ## 1、CSS3文本效果 ### 1.1、text-shadow文本阴影 语法:``text-shadow: h-shadow v-shadow blur color;``(<水平阴影>,<垂直阴影>,[模糊距离],[阴影颜色]) 示例:

我是文本阴影

我是文本阴影

我是文本阴影

我是文本阴影

我是文本阴影

我是文本阴影

**该属性兼容IE10+以及所有现代浏览器** ### 1.2、word-break文本换行 语法: ``word-break: normal|break-all|keep-all;`` normal:默认换行;break-all:允许在单词内换行;keep-all:只能在半角空格或连字符处换行 示例:
Nice to meet you. good mor-ning.
Nice to meet you. good mor-ning.
Nice to meet you. good mor-ning.
Nice to meet you. good mor-ning.
### 1.3、text-overflow修剪文本 语法:``text-overflow: clip|ellipsis|string;`` 示例:
Nice to meet you. good mor-ning.
Nice to meet you. good mor-ning.
Nice to meet you. good mor-ning.
Nice to meet you. good mor-ning.
**注意:使用text-overflow的时候,需要与overflow:hidden;white-space:nowrap;协同使用** ## 2、CSS3字体 在CSS3之前,必须使用已经在用户计算机上安装好的字体,给Web设计带来很大的局限性。现在,通过CSS3,Web设计师可以使用他们喜欢的任意字体。 ### 2.1、@font-face引入网络字体 Firefox、Chrome、Safari 以及 Opera 支持 .ttf (True Type Fonts) 和 .otf (OpenType Fonts) 类型的字体。 Internet Explorer 9+ 支持新的 @font-face 规则,但是仅支持 .eot 类型的字体 (Embedded OpenType)。 不兼容IE8,IE8-。 示例: 自定义字体演示 自定义字体演示 自定义字体演示 自定义字体演示 除此之外,在@font-face中,还可以设置多种字体描述符,如:
描述符 描述
font-family name 必需。规定字体的名称。
src URL 必需。定义字体文件的 URL。
font-stretch
  • normal
  • condensed
  • ultra-condensed
  • extra-condensed
  • semi-condensed
  • expanded
  • semi-expanded
  • extra-expanded
  • ultra-expanded
可选。定义如何拉伸字体。默认是 "normal"。
font-style
  • ormal
  • italic
  • oblique
可选。定义字体的样式。默认是 "normal"。
font-weight
  • normal
  • bold
  • 100
  • 200
  • 300
  • 400
  • 500
  • 600
  • 700
  • 800
  • 900
可选。定义字体的粗细。默认是 "normal"。
unicode-range unicode-range 可选。定义字体支持的 UNICODE 字符范围。默认是 "U+0-10FFFF"。
================================================ FILE: CSS3学习之路/CSS3入门之转换.md ================================================ --- title: CSS3入门之转换 date: 2017/02/21 14:47:10 --- ## 1、CSS3 转换 ### 1.1、转换是什么,能实现哪些效果? 转换是使元素改变形状、尺寸和位置的一种效果,主要能实现的效果如下: 1. 移动 2. 缩放 3. 转动 4. 拉长 5. 拉伸 ### 1.2、浏览器兼容 CSS3的转换属性为 ``transform`` ,IE10+,Firefox,Chrome,Opera,Safari等现代浏览器支持transform属性,IE9需要-ms-前缀。 ## 2、 2D 转换 准备工作: ### 2.1、translate() -- 移动 translate(/\*x坐标移动位移\*/ left, /\*y坐标移动位移\*/ top)

右移20px

下移20px

左移20px,下移20px

右移20px

下移20px

左移20px,下移20px

### 2.2、rotate() -- 旋转 rotate(/\*旋转角度\*/ deg)

旋转135度

旋转135度

### 2.3、scale() -- 缩放 scale(/\*宽度缩放比例\*/ widthScale, /\*高度缩放比例\*/ heightScale)

缩放到0.5倍

宽度缩放到1.5倍,高度缩放到0.25倍

缩放到0.5倍

宽度缩放到1.5倍,高度缩放到0.25倍

### 2.4、skew() -- 倾斜 skew(/\*X轴倾斜角度\*/ xDeg, /\*Y轴倾斜角度\*/ yDeg)

X轴翻转30度

X轴翻转30度,Y轴翻转10度

X轴翻转30度

X轴翻转30度,Y轴翻转10度

### 2.5、matrix() --矩阵

旋转30度

旋转30度

### 2.6 Transform方法
函数 描述
matrix(n,n,n,n,n,n) 定义 2D 转换,使用六个值的矩阵。
translate(x,y) 定义 2D 转换,沿着 X 和 Y 轴移动元素。
translateX(n) 定义 2D 转换,沿着 X 轴移动元素。
translateY(n) 定义 2D 转换,沿着 Y 轴移动元素。
scale(x,y) 定义 2D 缩放转换,改变元素的宽度和高度。
scaleX(n) 定义 2D 缩放转换,改变元素的宽度。
scaleY(n) 定义 2D 缩放转换,改变元素的高度。
rotate(angle) 定义 2D 旋转,在参数中规定角度。
skew(x-angle,y-angle) 定义 2D 倾斜转换,沿着 X 和 Y 轴。
skewX(angle) 定义 2D 倾斜转换,沿着 X 轴。
skewY(angle) 定义 2D 倾斜转换,沿着 Y 轴。
## 3、3D 转换 ### 3.1、rotateX、rotateY
### 3.2、Transform方法
函数 描述
matrix3d(n,n,n,n,n,n,
n,n,n,n,n,n,n,n,n,n)
定义 3D 转换,使用 16 个值的 4x4 矩阵。
translate3d(x,y,z) 定义 3D 转化。
translateX(x) 定义 3D 转化,仅使用用于 X 轴的值。
translateY(y) 定义 3D 转化,仅使用用于 Y 轴的值。
translateZ(z) 定义 3D 转化,仅使用用于 Z 轴的值。
scale3d(x,y,z) 定义 3D 缩放转换。
scaleX(x) 定义 3D 缩放转换,通过给定一个 X 轴的值。
scaleY(y) 定义 3D 缩放转换,通过给定一个 Y 轴的值。
scaleZ(z) 定义 3D 缩放转换,通过给定一个 Z 轴的值。
rotate3d(x,y,z,angle) 定义 3D 旋转。
rotateX(angle) 定义沿 X 轴的 3D 旋转。
rotateY(angle) 定义沿 Y 轴的 3D 旋转。
rotateZ(angle) 定义沿 Z 轴的 3D 旋转。
perspective(n) 定义 3D 转换元素的透视视图。
================================================ FILE: CSS3学习之路/CSS3入门之边框与背景.md ================================================ --- title: CSS3入门之边框与背景 date: 2017/02/21 14:47:10 --- ## 1、前言 CSS3作为CSS的最新版本,在展示效果上有非常大的提升,接下来,我们就一起领略一下CSS3的风采吧。 ## 2、CSS3边框 ``` ``` ### 2.1、border-radius(用于设置圆角边框) 在CSS2时代,要想实现圆角边框,是一件非常麻烦的事情。一种实现方式是使用一个背景图片,为了实现伸缩效果,还需要至少3张图片拼凑,相当麻烦。另外一种实现方式是使用多个div重叠来实现圆角。 在CSS3中,有一个非常简单的属性,那就是border-radius。 语法: ``border-radius: 1-4 length|% / 1-4 length|%;`` ```css border-radius: 10px; //等价于 border-top-left-radius:10px; border-top-right-radius:10px; border-bottom-right-radius:10px; border-bottom-left-radius:10px; ```
演示圆角边框
演示圆角边框
**兼容性说明:** IE9+,Chrome,FF,Safari,Oprea ``` div { border:2px solid; border-radius:25px; -moz-border-radius:25px; /* Old Firefox */ } ``` ### 2.2、box-shadow(用于添加边框阴影) 语法: ``box-shadow: h-shadow v-shadow blur spread color inset;``,其中h-shadow和v-shadow是必须设置,允许负值。【参数说明:水平阴影的位置,垂直阴影的位置,模糊距离,阴影的尺寸,阴影的颜色,外部引用(outset)改为内部阴影】 ```html
演示圆角边框
简单阴影
简单阴影
简单阴影
带模糊效果的阴影
带模糊效果的阴影
带模糊效果指定尺寸的阴影
带模糊效果指定尺寸的阴影
内部阴影
内部阴影
``` **兼容性说明:** IE9+,Chrome,FF,Safari,Oprea ### 2.3、border-image(CSS3边框图片) border-image是简写属性,全部是: ```css border-image-source //背景图片源 border-image-slice //图片边框内偏移 border-image-width //图片边框的宽度 border-image-outset //边框图像区域超出边框的量 border-image-repeat //边框是否适应平铺(repeated)、铺满(rounded)、拉伸(stretched) ``` ```html
简单图片边框
简单图片边框
完全设置的图片边框
完全设置的图片边框
``` **兼容性说明:** Chrome,FF,Safari,Oprea ``` div { border-image:url(border.png) 30 30 round; -moz-border-image:url(border.png) 30 30 round; /* 老的 Firefox */ -webkit-border-image:url(border.png) 30 30 round; /* Safari 和 Chrome */ -o-border-image:url(border.png) 30 30 round; /* Opera */ } ``` ## 3、CSS3背景 ### 整体兼容性 以下CSS背景的特性,全部支持IE9+,FF,Chrome,Safari,Oprea ### 3.1、background-size(用于规定背景图片的尺寸) 在以前的CSS中,背景图片的大小,是由图片本身的大小决定的。在CSS3中,有一个简单的CSS样式可以设置背景图片的大小,允许我们在不同的环境中重复使用背景图片。可以以像素或百分比规定尺寸。 ```html
简单设置背景图大小
``` ### 3.2、background-origin(规定背景图片的定位区域) 盒子模型示意图: box background-origin属性则可以设置背景图片放置于哪个区域上(content-box,padding-box,border-box) ```html
``` ### 3.3、多重背景 可以针对标签设置多个背景,用法如下: ```css body { background-image:url(bg_flower.gif),url(bg_flower_2.gif); } ``` ================================================ FILE: Canvas学习札记/01_初识Canvas,绘制简单图形.md ================================================ --- title: 01_初识Canvas,绘制简单图形 date: 2017/02/21 14:47:10 --- ## 0、关于Canvas ```` 是HTML5新增的一个标签,用于定义图形,比如图表和其他图像。 ```` 标签只是图形容器,必须要使用脚本来绘制图形。 一句话概括就是:```` 是浏览器上的画图,允许你通过js自由作画。 ### Canvas和SVG与VML的不同 ```` 有一个基于JS的绘图API,它本身并不会绘制图形。SVG和VML都是用一个XML文档来描述图形。 虽然它们在功能上基本相同,但是从表面上来看,它们非常不同。SVG和VML绘图易于编辑,只需要从描述中修改元素属性。而Canvas想移除元素,往往需要擦掉绘图重新绘制它。 ### Canvas兼容HTML5标准属性和事件 ```` 作为一个HTML的新标签,标准的HTML属性和事件它都支持。比如可以设置 ``title、style、class`` 等属性,也可以使用诸如 ``onclick`` 等事件。 ```html Canvas Test ``` ## 1、使用Canvas 要使用``canvas``,首先,我们先得html中加入canvas标签。最好,再加上一个id属性(也可以不加,只是查找该元素要稍微麻烦点)。 ```html ... ... ``` 在获取到 ``canvas`` 元素之后,我们需要通过 ``getContext(contextID)`` 方法获取到画布。 当前 ``contextID``的值仅仅可以用'2d',在未来,可能会允许传递'3d',来进行三维绘图。 ```html ... ... ``` ``CanvasRenderingContext2D`` 对象实现了一个画布所使用的大多数方法。现在我们就需要对它来进行使用,将图像绘制到浏览器上。 ### 1.1、绘制矩形 关于矩形的绘制,主要有三个方法: * fillRect(x, y, width, height) 用于填充矩形 * strokeRect(x, y, width, height) 用于绘制矩形边框 * clearRect(x, y, width, height) 用于清空矩形区域(设置矩形区域为空白) 其中 ``x,y``表示从那个点开始绘制。``width,height`` 表示矩形的宽度和高度。 要设置矩形的填充颜色,需要通过 ``fillStyle`` 来控制,支持 ``'red', '#fff', 'rgb(10,10,10)', 'rgba(10,10,10,10,0.5)'``等多种颜色属性。 要设置矩形的边框颜色,需要通过 ``strokeStyle`` 来控制,属性值和 ``fillStyle`` 一致。 ```javascript ... //绘制红色矩形 context.fillStyle = 'red'; context.fillRect(10,10,100,100); //绘制蓝色矩形框 context.strokeStyle = 'blue'; context.strokeRect(150,150,100,100); //清空矩形区域(设置矩形区域为空白) context.clearRect(100,100,100,100); ... ``` ### 1.2、绘制线条 我们可以通过 ``lineTo(x, y)`` 绘制直线。两点成直线,绘制直线需要两个点,所以我们需要先设置一个起点,一般来说,我们使用 ``moveTo(x, y)`` 设置笔触的位置。当然,你也可以用 ``lineTo(x, y)`` 来设置一个笔触点。 在没有设置笔触的场景下,以下两段代码的效果完全一致: ```javascript //画线,设置起点。 context.moveTo(200, 200); //设置轨迹 context.lineTo(500,500); //画线 context.stroke(); ``` ```javascript //画线,设置起点。 context.lineTo(200, 200); //设置轨迹 context.lineTo(500,500); //画线 context.stroke(); ``` **一般来说,我们会在 ``canvas`` 初始化或者 ``beginPath()`` 调用后,通过 ``moveTo(x, y)`` 来设置一个初始笔触点。** 要同时绘制多个线条,我们应该通过 ``beginPath()`` 来建立路径。 ```javascript //用线条绘制了一个矩形 context.beginPath(); context.moveTo(400, 400); context.lineTo(450, 400); context.lineTo(450, 450); context.lineTo(400, 450); context.closePath(); //真实的绘图 context.stroke(); ``` 看了以上的代码,可能会有一个疑惑,为什么仅仅三个线条就构成了一个矩形呢? 原因在于当调用 ``closePath()`` 的时候,会把最后的笔触点和最开始的笔触点连接在一起,这个时候也就构成了第四条直线。 **注意:当前路径为空,即调用beginPath()之后,或者canvas刚建的时候,第一条路径构造命令通常被视为是moveTo(),无论最后的是什么。出于这个原因,你几乎总是要在设置路径之后专门指定你的起始位置。** **闭合路径 ``closePath()``,不是必需的。这个方法会通过绘制一条从当前点到开始点的直线来闭合图形。如果图形是已经闭合了的,即当前点为开始点,该函数什么也不做。** **当你调用 ``fill()`` 函数时,所有没有闭合的形状都会自动闭合,所以你不需要调用 ``closePath()`` 函数。但是调用stroke()时不会自动闭合。** 再来填充一个梯形玩玩: ```javascript context.beginPath(); context.moveTo(100, 400); context.lineTo(200, 400); context.lineTo(250, 500); context.lineTo(50, 500); context.fill(); ``` ### 1.3、绘制矩形线条 矩形线条是一个比较常用的图形,所以提供了一个简单的方法来直接绘制: ```javascript //绘制矩形线条 context.beginPath(); context.rect(700, 10, 50, 50); context.stroke(); context.fillStyle = 'red'; context.fill(); ``` ### 1.4、绘制圆弧 绘制圆弧或者圆的时候,我们可以使用如下方法: * arc(x, y, radius, startAngle, endAngle, anticlockwise) 画一个以(x,y)为圆心的以radius为半径的圆弧(圆),从startAngle开始到endAngle结束,按照anticlockwise给定的方向(默认为顺时针)来生成。 * arcTo(x1, y1, x2, y2, radius) 根据给定的控制点和半径画一段圆弧,再以直线连接两个控制点。 anticlockwise为true则表示逆时针绘制。 ```javascript //绘制圆弧 context.beginPath(); context.arc(550, 150, 100, getRadian(90) , getRadian(360), false); context.stroke(); context.beginPath(); context.arc(550, 150, 100, 0, getRadian(90), false); context.fill(); ``` 以上代码,绘制了两个弧形,一个空心,一个实心。一般再绘制圆弧的时候就不要执行 ``moveTo(x, y)``,否则绘制终点会被连接到这个触点上。 **注意:在arc函数中,``startAngle`` 和 ``endAngle`` 属性值都是弧度,而不是我们所熟知的角度。所以我们一个一个角度转换为弧度的函数,如下:** ```javascript function getRadian(degrees/*角度值*/){ return (Math.PI / 180) * degrees; } ``` **arcTo没吃透,暂时描述不出来,先简单看看示例:** ```javascript //绘制圆弧(必须要设定起始点) context.beginPath(); context.fillRect(600, 400, 10, 10); context.fillRect(700, 500, 10, 10); context.fillStyle = 'blue'; context.fillRect(700, 400, 10, 10); context.beginPath(); context.moveTo(700, 400); context.arcTo(600, 600, 700, 700, 500); context.stroke(); ``` ## 2、其他 ### 2.1、canvas检查支持性 如果仅仅需要在UI上体现,那么我们可以在 ```` 标签内部放置元素,如果浏览器不支持 ```` 标签,那么内部的元素就会被浏览器解析,而显示出来。 ```html

Canvas not be support.

``` 除此之外,我们也可以用js的方式来检查。 ```javascript var c1 = document.getElementById('c1'); //如果canvas元素没有getContext方法,那么就证明浏览器不支持canvas。 if(!c1.getContext){ console.log('Canvas not be support.') } ``` ### 2.2、canvas的width和height属性 ```` 对象有两个比较特别的属性,``width、height``,这两者用于控制画布的大小,width的默认值是300,height的默认值为150。**当这两个属性值有变化时,在该画布上已经完成的任何绘图都会擦除掉。** ```javascript var c1 = document.getElementById('c1'); console.log('default width:', c1.width, '; default height:', c1.height); c1.width = 500; c1.height = 600; ``` ```` 的的height和width属性如果和用css设置的height和width样式不一致,那么就可能会产生扭曲。 ### 2.3、来个好玩的,画个桃心 ```javascript function drawHeart() { context.fillStyle = 'purple'; //三次曲线 context.beginPath(); context.moveTo(75, 40); context.bezierCurveTo(75, 37, 70, 25, 50, 25); context.bezierCurveTo(20, 25, 20, 62.5, 20, 62.5); context.bezierCurveTo(20, 80, 40, 102, 75, 120); context.bezierCurveTo(110, 102, 130, 80, 130, 62.5); context.bezierCurveTo(130, 62.5, 130, 25, 100, 25); context.bezierCurveTo(85, 25, 75, 37, 75, 40); context.fill(); } drawHeart(); ``` ### 2.4 附上测试代码 ```html Canvas Test ``` ```javascript var c1 = document.getElementById('c1'); // 如果不用id属性,我们可以用如下方式来获取canvas对象 //var c1 = document.getElementsByTagName('canvas')[0]; c1.width = 800; c1.height = 600; var context = c1.getContext('2d'); console.log(context); //可以看到context是一个CanvasRenderingContext2D对象 function getRadian(degrees/*角度值*/) { return (Math.PI / 180) * degrees; } //绘制红色矩形 context.fillStyle = 'red'; context.fillRect(10, 10, 100, 100); //绘制蓝色矩形框 context.strokeStyle = 'blue'; context.strokeRect(150, 150, 100, 100); //清空矩形区域(设置矩形区域为空白) context.clearRect(100, 100, 100, 100); //画线,设置起点。 context.moveTo(200, 200); //设置轨迹 context.lineTo(500, 500); //画线 context.stroke(); //绘制空心矩形 context.beginPath(); context.moveTo(400, 400); context.lineTo(450, 400); context.lineTo(450, 450); context.lineTo(400, 450); context.closePath(); context.stroke(); //绘制实心梯形 context.beginPath(); context.moveTo(100, 400); context.lineTo(200, 400); context.lineTo(250, 500); context.lineTo(50, 500); context.fill(); //绘制圆弧 context.beginPath(); context.arc(550, 150, 100, getRadian(90), getRadian(360), true); context.stroke(); context.beginPath(); context.arc(550, 150, 100, 0, getRadian(90), true); context.stroke(); //绘制圆弧(必须要设定起始点) context.beginPath(); context.fillRect(600, 400, 10, 10); context.fillRect(700, 500, 10, 10); context.fillStyle = 'blue'; context.fillRect(700, 400, 10, 10); context.beginPath(); context.moveTo(700, 400); context.arcTo(600, 600, 700, 700, 500); context.stroke(); //绘制矩形线条 context.beginPath(); context.rect(700, 10, 50, 50); context.stroke(); context.fillStyle = 'red'; context.fill(); function drawHeart() { context.fillStyle = 'purple'; //三次曲线 context.beginPath(); context.moveTo(75, 40); context.bezierCurveTo(75, 37, 70, 25, 50, 25); context.bezierCurveTo(20, 25, 20, 62.5, 20, 62.5); context.bezierCurveTo(20, 80, 40, 102, 75, 120); context.bezierCurveTo(110, 102, 130, 80, 130, 62.5); context.bezierCurveTo(130, 62.5, 130, 25, 100, 25); context.bezierCurveTo(85, 25, 75, 37, 75, 40); context.fill(); } drawHeart(); ``` ================================================ FILE: ES6入门/ES6入门系列一(基础).md ================================================ --- title: ES6入门系列一(基础) date: 2017/02/21 14:47:10 --- ##1、let命令 **Tips:** 1. 块级作用域(只在当前块中有效) 2. 不会变量提升(必须先申明在使用) 3. 让变量独占该块,不再受外部影响 4. 不允许重复声明 **总之:let更像我们熟知的静态语言的的变量声明指令** ES6新增了let命令,用来声明变量。用法类似于var,但所声明的变量,只能在let命令所在的代码块内有效。 let声明的变量只有块级作用域 'use strict' { let a = 1; } console.log(a); //结果是什么? 看一段熟悉的代码: var a = []; for (var i = 0; i < 10; i++) { a[i] = function () { console.log(i); }; } console.log(a[6]()); //结果是什么? 如果改用let的话,那么看以下代码输出什么? 'use strict' var a = []; for (let i = 0; i < 10; i++) { a[i] = function () { console.log(i); }; } console.log(a[6]()); // ? 同时,在使用let的时候,必须先申明再使用,不像var会变量提升: 'use strict' console.log(a); let a = 1; ES6中明确规定,如果区块存在let和const,那么该区块就形成封闭作用域,凡是在声明致歉就使用这些变量,就会报错。简称“暂时性死区”(temporal dead zone,简称TDZ)。 看一个不太容易发现的死区:(注:该代码未测试) function bar(x=y, y=2) { return [x, y]; } bar(); // 报错 调用bar之所以报错,是因为参数x默认值等于另一个参数y,而此时y还没有声明,属于“死区”。 需要注意,函数参数作用域和函数体的作用域是分离的: let foo = 'outer'; function bar(func) { let foo = 'inner'; console.log(func()); // outer } bar(function(){ console.log(foo); }); 同时,let还不允许重复声明 { let a = 1; var a = 1; } { let a = 1; let a = 2; } ##2、const命令 **Tips:** 1. const用于声明常量,一旦声明,值就不能改变 2. const具有块级作用域 3. const不能变量提升(先声明后使用) 4. 不可重复声明 **const看起来很像我们熟知的静态语言的只读对象** const声明常量,一旦声明,值将是不可变的。 'use strict' const PI = 3.1415; PI // 3.1415 PI = 3; //Error const指令指向变量所在的地址,所以对该变量进行属性设置是可行的(未改变变量地址),如果想完全不可变化(包括属性),那么可以使用冻结。 'use strict' const C1 = {}; C1.a = 1; console.log(C1.a); // 1 //冻结对象,此时前面用不用const都是一个效果 const C2 = Object.freeze({}); C2.a = 1; //Error,对象不可扩展 console.log(C2.a); ##3、全局对象属性 JavaScript中,全局对象是最顶层的对象,浏览器中是window对象,Node中是global对象,ES5规定,所有全局变量都是全局对象的属性。 在ES6中,var和function申明的变量,属于全局对象的属性,let和const则不是全局对象的属性。 'use strict' let b = 2; console.log(global.b); // undefined ================================================ FILE: ES6入门/ES6入门系列三(特性总览下).md ================================================ --- title: ES6入门系列三(特性总览下) date: 2017/02/21 14:47:10 --- # 0、导言 最近从coffee切换到js,代码量一下子变大了不少,也多了些许陌生感。 为了在JS代码中,更合理的使用ES6的新特性,特在此对ES6的特性做一个简单的总览。 # 1、模块(Module - Chrome测试不可用) >在ES6中,有class的概念,不过这只是语法糖,并没有解决模块化问题。Module功能则是为了解决模块化问题而提出的。 我们可以使用如下方式定义模块: 11_lib.js文件内容 // 导出属性和方法 export var PI = 3.1415926; export function calcCircularArea(r){ return PI * r * r; } app.js文件内容 //导出所有,使用别名调用 import * as lib from '11_lib'; console.log(lib.calcCircularArea(2)); console.log(lib.PI); //导出属性和方法 import {calcCircularArea, PI} from '11_lib'; console.log(calcCircularArea(2)); console.log(PI); # 2、模块加载器(Module Loaders - Chrome测试不可用) 既然用了定义module的规范,那么也就需要一个模块加载器,需要支持如下内容: 1. 动态加载 2. 状态隔离 3. 全局命名空间隔离 4. 编译钩子 5. 嵌套虚拟化 ```javascript System.import('11_lib').then(function(m) { console.log(m.calcCircularArea(2)); console.log(m.PI); }); ``` # 3、图 + 集合 + 弱引用图 + 若引用集合(Map + Set + WeakMap + WeakSet) 在ES6中,新增了几种数据结构。 **Map**是一种类似于Object的结构,Object本质上是键值对的集合,但是只能是字符串当做键,所以有一定的使用限制。Map的话,则可以拿任意类型来作为key。具体使用如下: var map = new Map(); var key = {key: 'hah'}; map.set(key, '1'); //设置key-value map.set(key, 2); //对已有key进行设置,表示覆盖 console.log(map.get(key)); //获取key的值 console.log(map.size);//获取map的元素个数 map.has(key); //判断map中有指定的key map.delete(key); //删除map中指定的key map.clear(); //清空map **WeakMap**和Map是比较类似的,唯一的区别是只接受对象作为键名(null除外),而且键名所指向的对象,不计入垃圾回收机制。 **Set**是一种类似于数组,但成员的值都是唯一(引用唯一)的一种数据结构。具体使用如下: var set = new Set();//定义set set.add(1).add(2).add(1).add('2'); //添加数据 console.log(set.size);//查看set中元素的数量,结果应该是3,因为重复添加不计算,2和'2'不等。 set.delete(1); //删除set的值(通过value删除)。 set.has(1); //set是否包含某个value set.keys(); //返回set的所有key set.values(); //返回set的所有value set.clear();//清空set **WeakSet**和Set也是比较类型的,和Set有两个区别,一个是成员只能是对象;二个是WeakSet是不可遍历的。 # 4、代理(Proxies) (Chrome测试不可用) 代理允许用宿主的行为来创建对象,能够实现拦截,对象的虚拟化,日志和分析等功能。 # 5、数据类型Symbols 在ES5中,JS只有6中原始类型,在ES6中,新增了Symbols类型,成为了JS中的第7种原始类型。 该类型表示独一无二的值。使用如下: var key = Symbol(); //定义Symbol对象 console.log(typeof key); //symbol ,表示为类型,而且不是string类型的。 key = Symbol('这是一个说明'); //可以在定义Symbol的时候,添加一个说明 Symbol不能与其他类型值进行运算,但是可以显式转换为字符串,和转换为布尔值 console.log(key.toString()); console.log(String(key)); if(key){ console.log('key is true'); } 在对象的内部,要使用Symbol值定义属性时,必须放在方括号中。 var obj = { [key]: 'abc' }; # 6、可以子类化的内置对象 在ES6中,我们可以自定义类型来继承内置对象,这个时候,如果要自定义构造函数,必须要在构造函数中调用super(),来呼叫父类的构造。 'use strict'; class MyArray extends Array { // 如果要定义constuctor,那么就必须要使用super来执行父类的构造 constructor(){ super(); } } var arr = new MyArray(); arr[1] = 12; console.log(arr.length === 2); # 7、新增的API(Math + Number + String + Array + Object APIs) 如下代码,一目了然: //数字类api Number.EPSILON; //增加常量e Number.isInteger(Infinity) // false Number.isNaN("NaN") // false //数学类api Math.acosh(3) // 1.762747174039086 Math.hypot(3, 4) // 5 Math.imul(Math.pow(2, 32) - 1, Math.pow(2, 32) - 2) // 2 //字符串类api "abcde".includes("cd") // true "abc".repeat(3) // "abcabcabc" //数组api Array.from(document.querySelectorAll('*')) // Returns a real Array Array.of(1, 2, 3) // Similar to new Array(...), but without special one-arg behavior [0, 0, 0].fill(7, 1) // [0,7,7] [1, 2, 3].find(x => x == 3) // 3 [1, 2, 3].findIndex(x => x == 2) // 1 [1, 2, 3, 4, 5].copyWithin(3, 0) // [1, 2, 3, 1, 2] ["a", "b", "c"].entries() // iterator [0, "a"], [1,"b"], [2,"c"] ["a", "b", "c"].keys() // iterator 0, 1, 2 ["a", "b", "c"].values() // iterator "a", "b", "c" //对象api Object.assign(Point, { origin: new Point(0,0) }) # 8、二进制和八进制字面量(Binary and Octal Literals) 直接上示例: var n1 = 0b111110101; //0b前缀,表示二进制字面量 console.log(n1); //输出的时候,直接用10进制展示 var n2 = 0o12345; //0o前缀,表示八进制字面量 console.log(n2); # 9、承诺(Promises) **Promise**是ES6中新增的异步编程库。 //使用承诺定义一个异步任务 var p = new Promise((resolve, reject)=>{ return setTimeout(function(){ reject('ok'); }, 2000); }); p.then((data)=>{ console.log(data); }, (data)=>{ console.log('error' + data); }).then(()=>{ console.log('throw err'); throw 'Error'; }).catch(err => { console.log(err); }); # 10、参考资料 1、ECMAScript 6 features [https://github.com/lukehoban/es6features](https://github.com/lukehoban/es6features) 2、ECMAScript 6 入门 [http://es6.ruanyifeng.com/](http://es6.ruanyifeng.com/) ================================================ FILE: ES6入门/ES6入门系列二(特性总览上).md ================================================ --- title: ES6入门系列二(特性总览上) date: 2017/02/21 14:47:10 --- ## 0、导言 最近从coffee切换到js,代码量一下子变大了不少,也多了些许陌生感。为了在JS代码中,更合理的使用ES6的新特性,特在此对ES6的特性做一个简单的总览。 ## 1、箭头函数(Arrows) 使用 => 简写的函数称之为箭头函数,和C#的lambda,CoffeeScript的语法比较类似。 var arr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; //简单使用 var arr2 = arr.map(x => x + 1); console.log(arr2); //等价于 arr2 = arr.map(function (x) { return x + 1; }); console.log(arr2); //此处必须用()包裹对象,否则语法错误 var arr3 = arr.map((x, i) => ({ idx: i, value: x })); console.log(arr3); //等价于 arr3 = arr.map(function (x, i) { return { idx: i, value: x }; }); console.log(arr3); //如果函数是一个语句块 var arr4 = []; arr.forEach(x => { if (x % 3 === 0) { arr4.push(x); } }); console.log(arr4); //等价于 arr4.length = 0; arr.forEach(function (x) { if (x % 3 === 0) { arr4.push(x); } }); console.log(arr4); //此处this一直指向obj对象。和一般function不同,箭头函数共享词法作用域。 var obj = { name: 'test', foods: ['fish', 'milk'], eat() { this.foods.forEach(x => console.log(this.name + 'like eat ' + x) //注意。此地不能有分号,因为属于表达式,不是语句块。 ); } }; obj.eat(); ## 2、类定义(classes) 在ES6中,可以直接使用class关键字定义类,并可以定义构造函数,静态方法,get/set 方法,实例方法等。 'use strict'; class Animal { constructor(name) { this.name = name; } eat() { console.log(this.name + ' should eat food.'); } }; class Dog extends Animal { //构造函数 constructor(age) { super('Dog'); this.age = age; //实例方法 this.instanceFun = function () { console.log('Instance Function.'); } } //静态方法 static go() { console.log('Dog will go.'); } //原型方法 prototypeFunc() { console.log('Prototype Function.'); } //get、set get dogName() { return 'Name: ' + this.name; } set dogName(value) { this.name = value; } //get get dogAge() { return 'Age: ' + this.age; } } var dog = new Dog(4); Dog.go(); dog.eat(); dog.instanceFun(); dog.prototypeFunc(); console.log(dog.dogName); dog.dogName = 'x'; console.log(dog.dogName); console.log(dog.dogAge); dog.dogAge = 5; //会失败,属性没有getter。 ## 3、增强的对象常量(Enhanced Object Literals) var obj = { name: 'obj', __proto__: { name: 'parent' }, toString() { // 可以通过super直接取到原型对象的属性 return super.name + ':' + this.name; }, ['prop_' + (() => 1)()]: 1 //动态属性 }; console.log(obj.toString()); console.log(obj.prop_1); ## 4、模板字符串(Template Strings) 简化了字符串的构造,拼接等。 //基本字符串,\n有效。 var str = `Basic string '\n' in Javascript.`; console.log(str); // Basic string ' // ' in Javascript. //多行字符串 str = `Multiline strings`; console.log(str); // Multiline // strings //字符串插值 var name = 'Jay'; str = `Hello, ${name}`; console.log(str); // 'Hello, Jay.' ## 5、解构(Destructuring)-- Node和Chrome中执行不成功,忽略 允许使用模式匹配,来匹配数组和对象。 ## 6、Default + Rest + Spread -- Node和Chrome中执行不成功,忽略 ## 7、局部变量+常量(Let + Const) 'use strict'; //必须启用严格模式 { let x = 1; } console.log(x); //Error:x is not defined. const PI = 3.14; PI = 3.15; //Error: 无法对常量赋值 console.log(PI); //3.14 ## 8、迭代器 + For..Of(Iterators + For..Of) 比较类似于C#中的IEnumerable,使用for..of来访问迭代器。它不要求实现一个数组,而是使用和LINQ类似的懒加载。 (function () { 'use strict'; let test = { [Symbol.iterator]() { let pre = 0, cur = 1; return { next() {//此处方法名不能变 pre = cur; cur = pre + cur; console.log('pre = ' + pre); console.log('cur = ' + cur); //返回值的属性名也不能改变 return { done: false, value: cur }; } } } }; for (var n of test) { if (n > 1000) { break; } console.log(n); } })(); // 用于获取数组的键值 for (var item of[1, 3, 5, 7, 9]) { console.log(item); } ## 9、生成器(Generators) 允许在function*()函数中使用yield关键字。 function* foo(x) { var y = 2 * (yield(x + 1)); var z = yield(y / 3); return (x + y + z); } var it = foo(5); console.log(it.next()); // { value:6, done:false } console.log(it.next(12)); // { value:8, done:false } console.log(it.next(13)); // { value:42, done:true } ## 10、unicode 增加了对unicode字符的支持。比如“𠮷”(这个和吉不一样哦!) console.log('𠮷'.length); //2 // 正则表达式增加了u这个参数,匹配unicode字符。 console.log("𠮷".match(/./u)[0].length) // 2 // new form "\u{20BB7}"=="𠮷"=="\uD842\uDFB7" // new String ops "𠮷".codePointAt(0) == 0x20BB7 // for-of iterates code points for(var c of "𠮷") { console.log(c); } ## 11、参考资料 1、ECMAScript 6 features [https://github.com/lukehoban/es6features](https://github.com/lukehoban/es6features) 2、ECMAScript 6 入门 [http://es6.ruanyifeng.com/](http://es6.ruanyifeng.com/) ================================================ FILE: ES6入门/ES6入门系列四(测试题分析).md ================================================ --- title: ES6入门系列四(测试题分析) date: 2017/02/21 14:47:10 --- ## 0、导言 ES6中新增了不少的新特性,来点测试题热热身。具体题目来源请看:[http://perfectionkills.com/javascript-quiz-es6/](http://perfectionkills.com/javascript-quiz-es6/)。 以下将一题一题来解析what和why。 ## 1、题目一 (function(x, f = () => x) { var x; var y = x; x = 2; return [x, y, f()]; })(1) A、 [2, 1, 1] B、 [2, undefined, 1] C、 [2, 1, 2] D、 [2, undefined, 2] **解析:**本题主要考察的知识点是1、参数值与函数体内定义的重名变量的优先级;2、ES6的默认参数;3、箭头函数。 在本题中,先执行x的定义,然后函数参数x=1,接着是y = x = 1,接着再x = 2,第三个是执行f函数,箭头函数如果只是表达式,那么等价于return 表达式,由于箭头函数的作用域等于定义时的作用域,那么函数定义时x=1,所以最后的return x 等价于 return 1 ## 2、题目二 (function() { return [ (() => this.x).bind({ x: 'inner' })(), (() => this.x)() ] }).call({ x: 'outer' }); A、 ['inner', 'outer'] B、 ['outer', 'outer'] C、 [undefined, undefined] D、 Error **解析:**本题主要考察的是箭头函数的作用域问题,箭头函数的作用域等于定义时的作用域,所以通过bind设置的this是无效的。那么结果就显而易见了。 ## 3、题目三 let x, { x: y = 1 } = { x }; y; A、 undefined B、 1 C、 { x: 1 } D、 Error **解析:**本题主要考察的是对象赋值,先定义x,然后在赋值的时候会执行一次y=1,最后返回y的值。 ## 4、题目四 (function() { let f = this ? class g { } : class h { }; return [ typeof f, typeof h ]; })(); A、 ["function", "undefined"] B、 ["function", "function"] C、 ["undefined", "undefined"] D、 Error **解析:**本题主要考察定义函数变量时,命名函数的名称作用域问题。在定义函数变量时,函数名称只能在函数体中生效。 ## 5、题目五 (typeof (new (class { class () {} }))) A、 "function" B、 "object" C、 "undefined" D、 Error **解析:**本题主要考察对象的类型,和原型方法。该提可以分解如下: // 定义包含class原型方法的类。 var Test = class{ class(){} }; var test = new Test(); //定义类的实例 typeof test; //出结果 ## 6、题目六 typeof (new (class F extends (String, Array) { })).substring A、 "function" B、 "object" C、 "undefined" D、 Error **解析:**本题主要考察ES6中class的继承,以及表达式的返回值和undefined的类型。题目其实可以按照如下方式分解: //由于JS的class没有多继承的概念,所以括号被当做表达式来看 (String, Array) //Array,返回最后一个值 (class F extends Array); //class F继承成Array (new (class F extends Array)); //创建一个F的实例 (new (class F extends (String, Array) { })).substring; //取实例的substring方法,由于没有继承String,Array没有substring方法,那么返回值为undefined typeof (new (class F extends (String, Array) { })).substring; //对undefined取typeof ## 7、题目七 [...[...'...']].length A、 1 B、 3 C、 6 D、 Error **解析:**本题主要考察的是扩展运算符...的作用。扩展运算符是将后面的对象转换为数组,具体用法是: [...<数据>] 比如 [...'abc']等价于["a", "b", "c"] ## 8、题目八 typeof (function* f() { yield f })().next().next() A、 "function" B、 "generator" C、 "object" D、 Error **解析:**本题主要考察ES6的生成器。题目可以如下分解: function* f() { yield f }; //定义一个生成器 var g = f(); //执行生成器 var temp = g.next(); //返回第一次yield的值 console.log(temp); //测试,查看temp,其实是一个object temp.next();//对对象调用next方法,无效 ## 9、题目九 typeof (new class f() { [f]() { }, f: { } })[`${f}`] A、 "function" B、 "undefined" C、 "object" D、 Error **解析:**本题主要考察ES6的class,以及动态属性和模板字符串等。 实际上这个题动态属性和模板字符串都是烟雾弹,在执行new class f()的时候,就已经有语法错误了。 ## 10、题目十 typeof `${{Object}}`.prototype A、 "function" B、 "undefined" C、 "object" D、 Error **解析:**本题考察的知识点相对单一,就是模板字符串。分解如下: var o = {Object}, str = `${o}`; typeof str.prototype; ## 11、题目十一 ((...x, xs)=>x)(1,2,3) A、 1 B、 3 C、 [1,2,3] D、 Error **解析:**本题主要考察的是Rest参数的用法,在ES6中,Rest参数只能放在末尾,所以该用法的错误的。 ## 12、题目十二 let arr = [ ]; for (let { x = 2, y } of [{ x: 1 }, 2, { y }]) { arr.push(x, y); } arr; A、 [2, { x: 1 }, 2, 2, 2, { y }] B、 [{ x: 1 }, 2, { y }] C、 [1, undefined, 2, undefined, 2, undefined] D、 Error **解析:**本题看起来是考察let的作用域和of迭代的用法。实则是考察let的语法,let之后是一个参数名称。所以,语法错误 ## 13、题目十三 (function() { if (false) { let f = { g() => 1 }; } return typeof f; })() A、 "function" B、 "undefined" C、 "object" D、 Error **解析:**本题非常有迷惑性,看似考察的let的作用域问题,实则考察了箭头函数的语法问题。 ## 14、题目答案 相信大家看过题目的解析,对题目答案已经了然。为了完善本文,还是在最后贴出所有题目的答案: ABBAB CBDDB DDD ================================================ FILE: GoLang学习笔记/01_开始GO.md ================================================ --- title: 01_开始GO date: 2017/02/21 14:47:10 --- # 0、前言 Go是啥?看[这里](https://golang.org)。简而言之,就是一门系统级的编程语言。 为嘛要学/用它? 1. 追个潮流,体验下不同风格的语言设计思想 2. 跨平台部署,单文件部署 3. 强大的功能(系统级) 4. 对分布式的友好支持 上手有啥问题? 1. 不友好的语法(也有一部分人觉得语法很优雅,对我来说,这语法可算不上友好) 2. 和大多数语言不一样的设计风格(需要慢慢消化) 3. 没有趁手的IDE(LiteIDE, Sublime Text, VsCode这几个可以自己优化得比较好用) # 1、开干 虽然比较恶心语法,但是其他优秀的点,也是比较吸引人的,再加上项目需要,那就开干。 因为有其他前/后端语言的经验,就跳过常规学习过程,直接上手做项目。 **注意:虽然跳过了不少步骤,但是语法还是要学习的,书还是要看的,先来一本《Go语言编程》** ### 1.1、创建项目基架 ``Go`` 是一个有很多规范的语法,就连创建目录结构也有一定的约束。之前的版本略过,我本地安装的是 ``go1.7.3`` 版本,通过 ``go version`` 可查看。 接下来就为该版本,创建一个相对较为标准的项目结构。 ================================================ FILE: JS札记/ES6 Class如何管理私有数据.md ================================================ --- title: ES6 Class如何管理私有数据 date: 2017/02/21 14:47:10 --- ## 0、前言 在ES5时代,要模拟对象的私有变量,是比较容易的,代码如下: ```javascript function Person(){ var _age = 20; //定义一个私有变量,外部无法访问。 this.setAge = function(value){ _age = value; } this.getAge = function(){ return _age; } } ``` 在ES6中,虽然可以在 ``Class`` 的 ``constructor`` 中实现类似function的私有方法,但是实际上,ES6中并不推荐这种做法。这样极大的加重了对象的实例。 那我们就来看看在ES6中有多少方法可以实现私有数据管理。 ## 1、在构造函数中存储私有数据 该方式,和在ES5中,没有什么区别。 ```javascript class Person{ constructor(){ var _age = 20; this.setAge = value => _age = value; this.getAge = _ => _age; } } ``` 该方式有一个变种,就是利用构造参数来存储,减少重新定义变量。 ```javascript class Person{ constructor(age){ this.setAge = value => age = value; this.getAge = _ => age; } } ``` **优点:** 1. 数据绝对安全,外部无法直接通过属性访问到。 2. 不会与其他私有属性有任何冲突。如 ```javascript let p = new Person(); p.age = 10; p.getAge(); //20 ``` **缺点:** 1. 代码不怎么优雅,需要把方法设置为实例方法,才能访问到私有数据。 2. 实例方法,比较浪费内存(每个实例都会拷贝一份)。 ## 2、通过命名约定来使用私有数据 该方式是在ES6 Class 中,我个人比较推荐的一个方式,实现代码如下: ```javascript class Person{ constructor(){ this._age = 0; } setAge(value){ this._age = value; } getAge(){ return this._age; } } ``` **优点:** 1. 代码看起来非常不错,简单易懂。 2. 能否在原型方法中访问。 **缺点:** 1. 并不安全,如果不遵守约定,直接操作_age,也是可行的,如: ```javascript let p = new Person(); p._age = 555; p.getAge(); // 555 ``` 2. 如果在对象上设置同名属性,会覆盖掉原本是私有属性。 ## 3、利用WeakMap来存储私有数据 该方式是利用WeakMap可以用Object来做key的特点,把this当做key来存储具体的私有属性。具体实现如下: ```javascript let dataStore = new WeakMap(); class Person{ constructor(){ dataStore.set(this, {age: 20}); } setAge(value){ let oldObj = dataStore.get(this); let newObj = Object.assign(oldObj, {age: value}); dataStore.set(this, newObj); } getAge(){ return dataStore.get(this).age; } } ``` 如何使用? ```javascript let p1 = new Person(); p1.getAge(); // 20 p1.setAge(25); p1.getAge(); // 25 ``` **优点:** 1. 能够使用原型方法,内存占用小; 2. 比命名约定属性名称安全性更高; 2. 不会有命名冲突(允许同名实例属性); **缺点:** 1. 代码没有命名约定方式(方式2)优雅; 2. 依赖外部对象; ## 4、利用Symbol来生成私有属性key。 该方式和命名约定方式没有本质区别,只是用 ``Symbol`` 来生成key,提高了key的安全性。具体实现代码如下: ```javascript const keyForAge = Symbol('age'); class Person{ constructor(){ this[keyForAge] = 20; } setAge(value){ this[keyForAge] = value; } getAge(){ return this[keyForAge]; } } ``` **优点:** 1. 能够使用原型方法,内存占用小; 2. 比命名约定属性名称安全性更高,但也并不安全; ```javascript let p1 = new Person(); Object.keys(p1); // [],无法直接访问到属性名 p1[keyForAge] = 30; p1.getAge(); // 30 Reflect.ownKeys(p1); // [Symbol(age)],通过能方式能遍历Key ``` 2. 不会有命名冲突(允许同名实例属性); **缺点:** 1. 代码没有命名约定方式(方式2)优雅; 2. 依赖外部对象; 3. 不是绝对安全; ## 5、Other 能够达到的目的的方式有很多,也没有那个有绝对优势,根据实际的需求,来选择合适的方式才是最佳的方式。 **参考资料** 1. [http://www.2ality.com/2016/01/private-data-classes.html](http://www.2ality.com/2016/01/private-data-classes.html) ================================================ FILE: JS札记/JS实现继承的几种方式.md ================================================ --- title: JS实现继承的几种方式 date: 2017/02/21 14:47:10 --- ## 前言 JS作为面向对象的弱类型语言,继承也是其非常强大的特性之一。那么如何在JS中实现继承呢?让我们拭目以待。 ## JS继承的实现方式 既然要实现继承,那么首先我们得有一个父类,代码如下: // 定义一个动物类 function Animal (name) { // 属性 this.name = name || 'Animal'; // 实例方法 this.sleep = function(){ console.log(this.name + '正在睡觉!'); } } // 原型方法 Animal.prototype.eat = function(food) { console.log(this.name + '正在吃:' + food); }; ### 1、原型链继承 **核心:** 将父类的实例作为子类的原型 function Cat(){ } Cat.prototype = new Animal(); Cat.prototype.name = 'cat'; // Test Code var cat = new Cat(); console.log(cat.name); console.log(cat.eat('fish')); console.log(cat.sleep()); console.log(cat instanceof Animal); //true console.log(cat instanceof Cat); //true 特点: 1. 非常纯粹的继承关系,实例是子类的实例,也是父类的实例 2. 父类新增原型方法/原型属性,子类都能访问到 3. 简单,易于实现 缺点: 1. 要想为子类新增属性和方法,必须要在``new Animal()``这样的语句之后执行,不能放到构造器中 2. 无法实现多继承 3. 来自原型对象的引用属性是所有实例共享的(详细请看附录代码: [示例1](javascript:void(0);)) 4. 创建子类实例时,无法向父类构造函数传参 推荐指数:★★(3、4两大致命缺陷) ### 2、构造继承 **核心:**使用父类的构造函数来增强子类实例,等于是复制父类的实例属性给子类(没用到原型) function Cat(name){ Animal.call(this); this.name = name || 'Tom'; } // Test Code var cat = new Cat(); console.log(cat.name); console.log(cat.sleep()); console.log(cat instanceof Animal); // false console.log(cat instanceof Cat); // true 特点: 1. 解决了1中,子类实例共享父类引用属性的问题 2. 创建子类实例时,可以向父类传递参数 3. 可以实现多继承(call多个父类对象) 缺点: 1. 实例并不是父类的实例,只是子类的实例 2. 只能继承父类的实例属性和方法,不能继承原型属性/方法 3. 无法实现函数复用,每个子类都有父类实例函数的副本,影响性能 推荐指数:★★(缺点3) ### 3、实例继承 **核心:**为父类实例添加新特性,作为子类实例返回 function Cat(name){ var instance = new Animal(); instance.name = name || 'Tom'; return instance; } // Test Code var cat = new Cat(); console.log(cat.name); console.log(cat.sleep()); console.log(cat instanceof Animal); // true console.log(cat instanceof Cat); // false 特点: 1. 不限制调用方式,不管是``new 子类()``还是``子类()``,返回的对象具有相同的效果 缺点: 1. 实例是父类的实例,不是子类的实例 2. 不支持多继承 推荐指数:★★ ### 4、拷贝继承 function Cat(name){ var animal = new Animal(); for(var p in animal){ Cat.prototype[p] = animal[p]; } Cat.prototype.name = name || 'Tom'; } // Test Code var cat = new Cat(); console.log(cat.name); console.log(cat.sleep()); console.log(cat instanceof Animal); // false console.log(cat instanceof Cat); // true 特点: 1. 支持多继承 缺点: 1. 效率较低,内存占用高(因为要拷贝父类的属性) 2. 无法获取父类不可枚举的方法(不可枚举方法,不能使用for in 访问到) 推荐指数:★(缺点1) ### 5、组合继承 **核心:**通过调用父类构造,继承父类的属性并保留传参的优点,然后通过将父类实例作为子类原型,实现函数复用 function Cat(name){ Animal.call(this); this.name = name || 'Tom'; } Cat.prototype = new Animal(); // Test Code var cat = new Cat(); console.log(cat.name); console.log(cat.sleep()); console.log(cat instanceof Animal); // true console.log(cat instanceof Cat); // true 特点: 1. 弥补了方式2的缺陷,可以继承实例属性/方法,也可以继承原型属性/方法 2. 既是子类的实例,也是父类的实例 3. 不存在引用属性共享问题 4. 可传参 5. 函数可复用 缺点: 1. 调用了两次父类构造函数,生成了两份实例(子类实例将子类原型上的那份屏蔽了) 推荐指数:★★★★(仅仅多消耗了一点内存) ### 6、寄生组合继承 **核心:**通过寄生方式,砍掉父类的实例属性,这样,在调用两次父类的构造的时候,就不会初始化两次实例方法/属性,避免的组合继承的缺点 function Cat(name){ Animal.call(this); this.name = name || 'Tom'; } (function(){ // 创建一个没有实例方法的类 var Super = function(){}; Super.prototype = Animal.prototype; //将实例作为子类的原型 Cat.prototype = new Super(); })(); // Test Code var cat = new Cat(); console.log(cat.name); console.log(cat.sleep()); console.log(cat instanceof Animal); // true console.log(cat instanceof Cat); //true 特点: 1. 堪称完美 缺点: 1. 实现较为复杂 推荐指数:★★★★(实现复杂,扣掉一颗星) ## 附录代码: 示例一: function Animal (name) { // 属性 this.name = name || 'Animal'; // 实例方法 this.sleep = function(){ console.log(this.name + '正在睡觉!'); } //实例引用属性 this.features = []; } function Cat(name){ } Cat.prototype = new Animal(); var tom = new Cat('Tom'); var kissy = new Cat('Kissy'); console.log(tom.name); // "Animal" console.log(kissy.name); // "Animal" console.log(tom.features); // [] console.log(kissy.features); // [] tom.name = 'Tom-New Name'; tom.features.push('eat'); //针对父类实例值类型成员的更改,不影响 console.log(tom.name); // "Tom-New Name" console.log(kissy.name); // "Animal" //针对父类实例引用类型成员的更改,会通过影响其他子类实例 console.log(tom.features); // ['eat'] console.log(kissy.features); // ['eat'] 原因分析: 关键点:属性查找过程 执行tom.features.push,首先找tom对象的实例属性(找不到), 那么去原型对象中找,也就是Animal的实例。发现有,那么就直接在这个对象的 features属性中插入值。 在console.log(kissy.features); 的时候。同上,kissy实例上没有,那么去原型上找。 刚好原型上有,就直接返回,但是注意,这个原型对象中features属性值已经变化了。 ================================================ FILE: JS札记/JavaScript之毒瘤.md ================================================ --- title: JavaScript之毒瘤 date: 2017/02/21 14:47:10 --- ## 0、导言 JavaScript中有许多难以避免的问题特性。接下来就一一揭示。 ## 1、全局变量 在所有JavaScript的糟糕特性中,最为糟糕的就是全局变量的依赖。全局变量使得在同一个程序中运行独立的子程序变得更难。 ## 2、作用域 JavaScript是类C的语法,但是却没有提供类C的块级作用域。 ## 3、自动插入分号 JavaScript有一个自动修复机制,会试图通过自动插入分号来修正有缺损的程序,但是它有可能掩盖更为严重的错误。 ## 4、保留字 有太多的单词在JavaScript中被保留,它们不能用来命名变量或者函数(在大部分执行环境下,部分关键字是可用的)。 ## 5、Unicode字符 JavaScript设计之初,Unicode预计只会有65536个字符。实际上,到现在Unicode有多大百万个字符。这也就导致了JavaScript会认为一对字符是两个不同的字符(Unicode把一对字符视为一个单一的字符) ## 6、typeof 不要指望typeof返回的类型。比如null或者是检测对象,另外检测正则可能会返回function或者是object。 ## 7、parseInt parseInt把一个字符串转换为整数,会在遇到非数字时停止解析。另外如果第一个字符是0,还会按照8进制来取值。 ## 8、运算符(+) +运算符可以用于加法运算或者是字符串连接,究竟如何执行会取决于其参数类型。 ## 9、 浮点数 二进制浮点数不能正确的处理十进制小数,因此0.1+0.2 不等于0.3。 ## 10、NaN NaN是一个特殊的数量值,它表示不是一个数字。也是唯一一个不等于自身的JavaScript数值。 ## 11、伪数组 JavaScript没有真正的数组,就连Array也是通过object来模拟的,如果完全达不到真正的数组的地步。同时typeof运算符也不能辨别数组和对象。 ## 12、假值 JavaScript中包含诸多的假值,如: 0, NaN, '', false, null, undefined ## 13、hasOwnProperty hasOwnProperty方法被用做一个过滤器来避开for..in语句的隐患,但hasOwnProperty是一个普通的方法,所以是可以被重写的。 ## 14、对象 JavaScript的对象,永远不会是真的空对象,因为可以从原形链取得成员属性。 ================================================ FILE: JS札记/JavaScript之糟粕.md ================================================ --- title: JavaScript之糟粕 date: 2017/02/21 14:47:10 --- ## 0、导言 在上篇《JavaScript之毒瘤》中,列举了一些在JavaScript中难以避免的问题特性。本篇将会展示JavaScript中有问题的特性,但我们很容易就能便面它们。通过这些简单的做法,让JavaScript称为一门更好的语言。 ## 1、== JavaScript有两组相等运算符。 === 和 !==,以及 == 和 !==。 === 和 !== 不会进行类型转换,一般会按照你期望的方式工作。由于JavaScript的类型转换系统相当复杂,如果要确保==和 != 不出错,就必须要牢记转换规则。另外==运算符缺乏传递性。 //关于传递性 if a === b, b === c then a === c; if a == b, b == c then a 不一定等于 c,破坏了传递性 **小测验**: '' == '0' 0 == '' 0 == '0' false == 'false' false == '0' false == undefined false == null null == undefined '\t\t\n' == 0 **总结**:推荐使用===和!==,尽量避免使用==和!=。 ## 2、with语句 JavaScript提供了一个with语句,本意是用它来快速访问对象,不幸的是,它的结果可能有时不可预料。 **小测验**: window.a = 1; var obj={}; with(obj){ console.log(a); } obj.a = 2; with(obj){ console.log(a); } **结论**:避免使用with。 ## 3、eval eval函数传递一个字符串给JavaScript*编译器*,并且执行结果。有问题问题呢?首先是代码难以阅读,另外需要运行编译器,导致性能低下;同时,还减弱了程序的安全性。**注:**Function构造函数,setTimeout、setInterval的字符串参数形式和eval是执行方式一致。 **结论**:避免使用eval,setTimeout、setInterval的字符串参数和Function构造函数。 ## 4、continue continue语句跳到循环顶部,性能比较低下。 **小测验**: var counter = 10; console.time('t1'); var sum = 0; for(var i = 0; i < counter; i++){ if(i % 3 !== 0){ continue; } sum = sum + i; } console.log(sum); console.timeEnd('t1'); console.time('t2'); var sum = 0; for(var i = 0; i < counter; i++){ if(i % 3 === 0){ sum = sum + i; } } console.log(sum); console.timeEnd('t2'); **结论**:尽量优化代码,减少continue的使用。 ## 5、switch switch语句中,除非明确的中断流程,否则每次条件判断后,都可以穿越到下一个case条件。这很容易造成bug。 **小测验**: var a = 15; switch(a){ case a * 1 : console.log('a*1'); case a / 1: console.log('a/1'); default: console.log('a'); } **结论**:不要刻意的使用case条件穿越。 ## 6、缺少块的语句 if、while、do或for可以接受代码块,也可以接受单行语句。单行语句的形式是一种带刺的玫瑰。虽然它可以节约2个字节,但它模糊了程序的结构。 **小测验**: if(1 == '0') console.log('1 == 0'); console.log('my god'); // VS if(1 == '0'){ console.log('1 == 0'); } console.log('my god'); **结论**:避免使用模糊程序结构的单行语句。 ## 7、++ -- 递增和递减使得可以用非常简洁的风格去编码。但是它可能造成缓冲区溢出、同时往往让代码变得拥挤也不易于理解。 **小测验**: var a = 1; a = a++ + ++a; console.log(a); a = 1; a = a++ + a++; console.log(a); **结论**:避免使用++ --。 ## 8、位运算符 JavaScript有着和Java相同的一套位运算符。Java中位运算符处理整数,非常快。在JavaScript中,只有双精度浮点数,所以位运算非常慢。另外,&非常容易误写为&&,使得bug容易被隐藏起来。 **小测验**: var counter = 10000; var a = 5; var sum = 0; console.time('t1'); for(var i = 0; i < counter; i++){ sum += a << 1; } console.log(sum); console.timeEnd('t1'); sum = 0; console.time('t2'); for(var i = 0; i < counter; i++){ sum += a * 2; } console.log(sum); console.timeEnd('t2'); **结论**:避免使用位运算符。 ## 9、function语句 与 function表达式 JavaScript中既有function语句,也有function表达式,这令人困惑,似乎看起来也差不多。function语句在解析时会产生变量提升,放宽了函数必须先申明后使用的的要求。同时,JS在if语句中使用function语句也是被禁止的,但实际上大多数浏览器允许在if中使用function语句,这有可能会导致兼容性问题。 由于一个语句不能以一个函数表达式开头,如下如下写法,可以改写为另外一种形式。 function (){}(); //Error (function(){}()); // Right **小测验**: // function语句 function fun(){} // function表达式 var fun = function(){}; **结论**:合理使用function语句和function表达式。 ## 10、类型的包装对象 JavaScript有一种类型的包装对象,如 new Number(1);这很容易令人困惑。 **小测验**: var num1 = new Number(1); var num2 = 1; console.log(typeof num1); console.log(num1 === num2); **结论**:避免使用包装对象,如new Boolean(),new String(),new Number()等 ## 11、new new运算符创建一个继承于其原型的新对象,并将新创建的对象绑定给this。但是,如果忘记使用new,那么就得到一个普通的函数调用,对象属性也会被绑定到全局对象上。这不会导致什么编译警告,也没有运行警告。 根据惯例,需要用new的函数,以首字母大写命名。这能部分程度上便于我们发现错误。 **小测验**: function Person(){ this.name = 'Default'; this.sex = undefiend; } Person(); new Person(); //更好的实现 function Person(){ if(this === window){ return new Person(); } this.name = 'Default'; this.sex = undefiend; } **结论**:合理的避免使用new。另外可以先判断this,再做对应处理。 ## 12、void 大部分语言中,void是一种类型,在Js中,void是一种运算符,接收一个运算数,并返回undefined **小测验**: void 0 void true **结论**:有限的使用void ================================================ FILE: JS札记/JavaScript的深拷贝的实现.md ================================================ --- title: JavaScript的深拷贝的实现 date: 2017/02/21 14:47:10 --- ## JavaScript的数据类型 ### 简单数据类型 1. string 2. number 3. boolean 4. function 5. null 6. undefined ### 复杂数据类型 1. String 2. Number 3. Boolean 3. Function 4. Date 5. Array 6. RegExp 7. Object ## 各种类型的深复制方式: 先来看看简单类型的复制方式: //string var s1 = 'abc'; var s2 = s1; s2 = 'ccc'; console.log(s1); //number var n1 = 12.1; var n2 = n1; n2 = 7410; console.log(n1); //boolean var b1 = true; var b2 = b1; b2 = false; console.log(b1); //null var nu1 = null; var nu2 = nu1; nu2 = 'abc'; console.log(nu1); //undefined var u1 = undefined; var u2 = u1; u2 = 'abc'; console.log(u1); 从以上的代码可以看出,简单类型,只需要直接赋值就是深复制了。但是也有一个例外,那就是function。 接着来看看String、Number、Boolean、Date的深复制: //String var s1 = new String('s1'); var s2 = new String(s1); console.log(s2); //Number var n1 = new Number('1'); var n2 = new Number(n1); console.log(n2); //Boolean var b1 = new Boolean(1); var b2 = new Boolean(b1); console.log(b2); //Date var d1 = new Date(); var d2 = new Date(d1); console.log(d2); 除以上的做法之外,还需要对实例属性进行拷贝。那么剩下的Function、function、RegExp和Array还有Object又该怎么拷贝呢?这几个比较特殊,我们一个一个来: 对于Function和function的深拷贝,我们可以按照如下的方式来做: var f1 = new Function('a', 'console.log("f1" + a);'); var f2 = function(b){console.log('f2' + b);}; //通过toString获取源代码(有浏览器兼容问题) var code = f1.toString(); //利用eval进行复制 var f1_copy = (function(functionCode){ eval('var f = ' + functionCode); return f; })(code); f1_copy('abc'); //当然f2也可以用同样的方式来复制。 接着,我们来看下RegExp,可以同样同时eval来执行拷贝,也可以使用如下方式: var reg1 = /abc/g; var reg2 = new RegExp('abc', 'gmi'); var reg1_copy = (function(reg){ var pattern = reg.valueOf(); var flags = (pattern.global ? 'g' : '') + (pattern.ignorecase ? 'i' : '') + (pattern.multiline ? 'm' : ''); return new RegExp(pattern.source, flags); })(reg1); 最后,我们来说一说Array的复制,有的人可以说,直接用slice复制一份出来就是了,那我们来看看,是否真的达到效果的呢? var o = {name: 'Jay'}; var arr1 = [o, '22', 1]; var arr2 = arr1.slice(0); arr2[0].name = 'Arr2'; console.log(arr1[0].name); 很简短的代码,直接就把slice抛弃了,slice只能保证Array是新的,并不意味着内部的元素是深拷贝的,那么如何做呢?就是遍历元素,对每个元素进行深拷贝了。代码如下: var o = {name: 'Jay'}; var arr1 = [o, '22', 1]; var arr2 = []; for(var i = 0, len = arr1.length; i < len; i++){ //注意,deepClone还未实现 arr2.push(deepClone(arr1[i])); } 以上对针对不同的类型,特殊的代码,那么如何来拷贝实例属性呢?代码如下: var o = {p1: '1', p2: 2, p3: function(){}}; var copy = {}; for(var p in o){ //注意deepClone还未实现 copy[p] = deepClone(o[p]); } **注意:针对复杂类型,还需要同时copy.constructor = source.constructor来保证构造函数一致。** ## 最终的深复制代码 通过以上的分析与代码示例,那么我们最终的代码又是怎样的呢?详细代码如下: //自调用函数,防御性编程 ; (function (window) { 'use strict'; function getCustomType(obj) { var type = typeof obj, resultType = 'object'; //简单类型 if (type !== 'object' || obj === null) { resultType = 'simple'; } else if (obj instanceof String || obj instanceof Number || obj instanceof Boolean || obj instanceof Date) { resultType = 'complex'; } else if (obj instanceof Function) { resultType = 'function'; } else if (obj instanceof RegExp) { resultType = 'regexp'; } else if (obj instanceof Array) { resultType = 'array'; } return resultType; } function cloneProperties(dest, source) { dest.constructor = source.constructor; for (var p in source) { dest[p] = deepClone(source[p]); } return dest; } function cloneSimple(obj) { return obj; } function cloneComplex(obj) { var result = new obj.constructor(obj); return cloneProperties(result); } function cloneFunction(obj) { var funCopy = (function (f) { eval('var abcdefg_$$$$ = ' + obj.toString()); return abcdefg_$$$$; })(obj); return cloneProperties(funCopy); } function cloneRegExp(obj) { var pattern = obj.valueOf(); var flags = (pattern.global ? 'g' : '') + (pattern.ignorecase ? 'i' : '') + (pattern.multiline ? 'm' : ''); var reg = new RegExp(pattern.source, flags); return cloneProperties(reg); } function cloneArray(obj) { var resultArr = []; for (var i = 0, len = obj.length; i < len; i++) { resultArr.push(deepClone(obj[i])); } for (var p in obj) { if (typeof p === 'number' && p < len) { continue; } resultArr[p] = deepClone(obj[p]); } return resultArr; } function cloneObject(obj) { var result = {}; result.constructor = obj.constructor; for (var p in obj) { result[p] = deepClone(obj[p]); } return result; } function deepClone(obj) { var f = undefined; switch (getCustomType(obj)) { case 'simple': f = cloneSimple; break; case 'complex': f = cloneComplex; break; case 'function': f = cloneFunction; break; case 'regexp': f = cloneRegExp; break; case 'array': f = cloneArray; break; case 'object': f = cloneObject; break; } return f.call(undefined, obj); } //挂载到window对象上 window.deepClone = deepClone; })(window); ================================================ FILE: JS札记/[20141121]JavaScript之Array常用功能汇总.md ================================================ --- title: JavaScript之Array常用功能汇总 date: 2014/11/21 00:00:00 --- **导语:**在JavaScript中,Array是一个使用比较频繁的对象,那么它到底有哪些常用的方法呢? 首先,我们先看一下Array对象的类型: typeof Array // 'function' Array instanceof Object // true 从上可以看出,Array本质是一个function,同样派生自Object,定义如下: function Array(args) {} ###接下来,我们来看Array自身的方法: #### #1、concat() 定义:原型方法,连接两个或更多的数组,并返回结果(新数组)。 Array.prototype.concat = function(items) {}; 示例: var arr1 = [1, 2]; var arr2 = arr1.concat([3, 4]); var arr3 = arr2.concat([5, 6], [7, 8] ,10, {}); console.log(arr1); // [1, 2] console.log(arr2); // [1, 2, 3, 4] console.log(arr3); // [1, 2, 3, 4, 5, 6, 7, 8, 10, Object] **注意:**concat不仅可以连接单个对象,也可以连接多个对象,同时如果是参数为数组,那么会将数组元素拆分并连接,如果是对象,则直接将对象连接。该方法不会改变原始数组 #### #2、join() 定义:原型方法,把数组的所有元素放入一个字符串。元素通过指定的分隔符进行分隔。 Array.prototype.join = function(separator) {}; 示例: var arr = [1, 2, 3]; console.log(arr.join('|')); // '1|2|3' console.log(arr.join('')); // '123' console.log(arr.join('---')); // '1---2---3' **注意:**太常用了,没什么可注意的~ #### #3、pop() 定义:原型方法,删除并返回数组的最后一个元素。 Array.prototype.pop = function() {}; 示例: var arr1 = [1, 2, 3, 4]; var lastOne = arr1.pop(); console.log(lastOne); // 4 console.log(arr1); // [1, 2, 3] **注意:**该方法无参数,有返回值,返回数组最后一个元素。该方法会改变原始数组 #### #4、push() 定义:原型方法,向数组的末尾添加一个或更多元素,并返回新的长度。 Array.prototype.push = function(items) {}; 示例: var arr1 = [1, 2]; var len = arr1.push(3); var arr2 = arr1.push(4, 5); console.log(len); console.log(arr1); console.log(arr2); **注意:**该方法的返回值会返回数组的新长度。该方法会改变原始数组 #### #5、reverse() 定义:原型方法,颠倒数组中元素的顺序。 Array.prototype.reverse = function() {}; 示例: var arr1 = [1, 2, 3, 4, 5]; var res = arr1.reverse(); console.log(res); console.log(arr1); **注意:**该方法的返回值为自身(翻转后的值),该方法会改变原始数组 #### 6、shift() 定义:原型方法,删除并返回数组的第一个元素。 Array.prototype.shift = function() {}; 示例: var arr1 = [1, 2, 3]; var res = arr1.shift(); console.log(res); console.log(arr1); **注意:**该方法返回数组第一个元素,和pop()方法对应(返回并删除最后一个元素)。该方法会改变原始数组 #### #7、slice() 定义:原型方法,从某个已有的数组返回选定的元素。 Array.prototype.slice = function(start,end) {}; 示例: var arr = [1, 2, 3, 4, 5, 6, 7, 8, 9]; var res1 = arr.slice(0, 3); var res2 = arr.slice(0, 100); var res3 = arr.slice(-1,-6); var res4 = arr.slice(-6, -1); console.log(res1); console.log(res2); console.log(res3); console.log(res4); console.log(arr) **注意:**该方法支持逆向索引,同时索引采取区间左闭右开的原则。该方法不会改变原始数组 #### #8、sort() 定义:原型方法,对数组的元素进行排序。 Array.prototype.sort = function(compareFn) {}; 示例: var arr = [1, 5, 2, 3, 4, 7, 8, 6, 9]; var res1 = arr.sort(); //如果是数字,默认从小到大排序 console.log(res1); var arr2 = ['a', 'c', 'b']; var res2 = arr2.sort();//如果是字符,按照字符顺序(ASCII,字符串同)排序 console.log(res2); //遇到复杂数据,经过测试是按照数组<正则<数字<对象<字符串<函数 这个顺序 var arr3 = [{name:'name'}, 134, 'aaa', function(){}, [], /a/]; var res3 = arr3.sort(); console.log(arr3); //可以通过自定义规则实现复杂的排序 var res4 = arr.sort(function(a1, a2){ if(a1 === a2){ // 两者相等,那么就算想等 return 0; } if(a1%3 === 0){ //如果a1被3整除,那么a1小 return -1; } if(a2%3 === 0){ //如果a2被3整除,那么a2小 return 1; } return a2%3-a2%3; //不满足以上条件,那么根据余数比大小,余数小的元素小 }) console.log(res4); **注意:**该方法返回自身(排序后数组)。可通过function(a1, a2){}实现非常复杂的排序规则。该方法会改变原始数组 #### #9、splice() 定义:原型方法,删除元素,并向数组添加新元素。(该方法相等较复杂,悠着点用) Array.prototype.splice = function(start,deleteCount,items) {}; 示例: var arr1 = [1, 2, 3, 4, 5, 6, 7, 8, 9]; var res1 = arr1.splice(0, 3, 'new1', 'new2'); console.log(res1); // [1, 2, 3] console.log(arr1); // ['new1', 'new2', 4, 5, 6, 7, 8, 9] arr1 = [1, 2, 3, 4, 5, 6, 7, 8, 9]; res1 = arr1.splice(-6, 3, 'new1', 'new2'); console.log(res1); // [4, 5, 6] console.log(arr1); // [1, 2, 3, 'new1', 'new2', 7, 8, 9] **注意:**splice()函数支持倒叙索引,同时第二个参数是长度(不是下标),新插入的数据会插入在start下标位置。返回值为删除的元素数组。该方法会改变原始数组 #### #10、unshift() 定义:原型方法,向数组的开头添加一个或更多元素,并返回新的长度。 Array.prototype.unshift = function(items) {}; 示例: var arr1= [1, 2, 3]; var res1 = arr1.unshift('new1', 'new2'); console.log(res1); // 5 console.log(arr1); // ["new1", "new2", 1, 2, 3] **注意:**该方法和push相对(在末尾添加元素,返回新长度),该方法的返回值是新数组长度。该方法会改变原始数组 ### 我们还可以为Array添加更多的常用功能,比如: Array.prototype.where = function(predicateFn){ var parameterIsFn = typeof predicateFn === 'function' var result = []; for(var i = 0, len = this.length; i < len; i++){ if(!parameterIsFn || predicateFn(this[i])){ result.push(this[i]); } } return result; }; var arr = ['new1', 'new2', 1, 2, 3]; var res = arr.where(function(item){ return typeof item === 'number'; }); console.log(res); ================================================ FILE: JS札记/那些不常见的JavaScript题目(上).md ================================================ --- title: 那些不常见的JavaScript题目(上) date: 2017/02/21 14:47:10 --- ## 0、导言 JavaScript超乎寻常的灵活性,让JavaScript可以有很多特殊的用法,让我们来领略一下它们的风采吧。 此为上篇,下篇请查阅:[下篇](那些不常见的JavaScript题目(下).md) ## 1、那些不常见(好玩)的题目 --- ### 1.1、 ```javascript ["1", "2", "3"].map(parseInt) ``` **解析:** 该题考察数组的 ``Array.map()`` 函数和 ``parseInt()``的用法。 ``Array.map()``,接受两个两个,第一个是回调函数 ``function(currentValue, index, array)``,第二个是可选参数 ``thisArg``,用来指定回调函数的 ``this`` 对象,默认是 ``window``。 ```javascript ['1', '2', '3'].map(function(currentValue, index, array){ console.log(currentValue, index, array, this); return '1'; }, {}); ``` 通过上面的代码可以验证 ``Array.map()`` 的用法,想更详细了解,请参考 [MDN Array.map](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map)。 接着看 ``parseInt``,``parseInt`` 默认接受两个函数,第一个要是要转化的元素,第二个是进制,默认2~36,0的话,就当成10进制处理。如果超过则会返回 ``NaN``。把它作为map的回调函数时,相当于给parseInt传递了三个参数。多余的参数对结果不影响,所以问题可以变形为:``[parseInt('1', 0), parseInt('2', 2), parseInt('3',2)]``,所以就能得出答案了。 **答案:** ``[1, NaN, NaN]`` --- ### 1.2、 ```javascript [typeof null, null instanceof Object] ``` **解析:** 该题考察了对``null``的认识。 ``null`` 本身是一个类型(并不是Object的实例),但对它进行 ``typeof`` 会返回 ``object``。 **答案:** ``['object', false]`` --- ### 1.3、 ```javascript [ [3,2,1].reduce(Math.pow), [].reduce(Math.pow)] ] ``` **解析:**此题和1.1比较类似,考察 ``Array.reduce()`` 和 ``Math.pow()`` 方法的使用。 ``Array.reduce()`` 用于将数组的多个值根据指定的处理函数,合并为一个值。它的回调函数定义为 ``function(previousValue, currentValue, currentIndex, array){}``,其中: 1. previousValue // 上一次的运算结果(注意:该变量的初始值为数组的第一个元素) 2. currentValue // 当前数组元素 3. currentIndex // 当前数组索引 4. array //数组本身 另外,需要注意,由于默认 ``previousValue`` 的初始值是第一个数组元素,那么实际的回调函数调用次数为:``arr.length - 1``。如果数组元素小于1,就会因为无法提供初始值,而导致方法异常(报错)。想了解更多,参考:[MDN Array.reduce()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/reduce)。 ``Math.pow()`` 用于计算指定值的次方,接受两个参数,前者为基数,后者为几次方,如:``Math.pow(2, 3) // 8``。 **答案:** ``[9, error]`` --- ### 1.4、 ```javascript var val = 'smtg'; console.log('Value is ' + (val === 'smtg') ? 'Something' : 'Nothing'); ``` **解析:** 此题比较简单,考察+号运算符和?号运算符的优先级问题,+优先级大于?。 **答案:** ``'Something'`` --- ### 1.5、 ```javascript var name = 'World!'; (function () { if (typeof name === 'undefined') { var name = 'Jack'; console.log('Goodbye ' + name); } else { console.log('Hello ' + name); } })(); ``` **解析:** 此题考察函数作用域、变量提前、全局变量和局部变量相关的知识点。JS默认是函数作用域,并且函数申明,变量申明会提前到函数体开头。所以题目代码等价于: ```javascript var name = 'World!'; (function () { var name; if (typeof name === 'undefined') { name = 'Jack'; console.log('Goodbye ' + name); } else { console.log('Hello ' + name); } })(); ``` 再结合局部变量,会隐藏全局变量,所以答案就出来。 **答案:** ``Goodbye Jack`` --- ### 1.6、 ```javascript var END = Math.pow(2, 53); var START = END - 100; var count = 0; for (var i = START; i <= END; i++) { count++; } console.log(count); ``` **解析:** 此题考察的知识比较偏门,考察了JS中**能正确计算且不失精度的最大整数**,当达到这个数之后,++(自加)操作将不会产生变化了。所以<=将永远满足条件。 **答案:** ``死循环`` --- ### 1.7、 ```javascript var ary = [0,1,2]; ary[10] = 10; ary.filter(function(x) { return x === undefined;}); ``` **解析:** 此题考察了数组自动补齐元素和 ``Array.filter()`` 的用法。 数组自动补齐的元素为 ``undefined``,示例如下: ```javascript var arr = []; arr[9] = 1; console.log(arr); // [undefined * 9, 1] ``` ``Array.filter()`` 用于筛选满足条件的数组元素,接受回调函数 ``function(currentValue, currentIndex, array){}``。但是一定要注意:**它会忽略自动填充的undefined的元素** **再次警告:是仅仅忽略自动填充的undefined元素,如果不是自动填充的undefined,是不会被忽略的。** ```javascript var arr = [1, 'str', undefined, null]; arr.filter(function(item){console.log(item)}); //执行四次,没有忽略 var arr = []; arr[9] = 1; arr.filter(function(item){console.log(item)}); //执行1次,忽略了自动填充的9个元素 ``` **为什么有这种现象呢?** 因为filter在判断的时候,是通过key来进行的,对比一下一下两种方式的key: ```javascript var arr = [1, 'str', undefined, null]; Object.keys(arr); //一共有四个。 var arr = []; arr[9] = 1; Object.keys(arr); //一共只有一个。 ``` **引申:该判断逻辑也适合大多数数组方法,如reduce,map等等。** **答案:** ``[]`` --- ### 1.8、 ```javascript var two = 0.2 var one = 0.1 var eight = 0.8 var six = 0.6 [two - one == one, eight - six == two] ``` **解析:** 该题考察了JS中的浮点数运算有误差的知识点。这个到底规则是什么,I don't know. **答案:** ``[true, false]`` --- ### 1.9、 ```javascript function showCase(value) { switch(value) { case 'A': console.log('Case A'); break; case 'B': console.log('Case B'); break; case undefined: console.log('undefined'); break; default: console.log('Do not know!'); } } showCase(new String('A')); ``` **解析:** 此题考察 ``new String('A')`` 产生的结果为 ``object``,另外switch判断分支是使用 ``===``,所以只能到default分支。 **答案:** ``'Do not know!'`` --- ### 1.10、 ```javascript function showCase2(value) { switch(value) { case 'A': console.log('Case A'); break; case 'B': console.log('Case B'); break; case undefined: console.log('undefined'); break; default: console.log('Do not know!'); } } showCase(String('A')); ``` **解析:** 此题和1.9考察点类似,不过不适用new的 ``String('A')``,返回的是字符串的 ``'A'``。 **注意,以此类推,Number(1), 也返回数字 1 。** **答案:** ``'Case A'`` --- ### 1.11、 ```javascript function isOdd(num) { return num % 2 == 1; } function isEven(num) { return num % 2 == 0; } function isSane(num) { return isEven(num) || isOdd(num); } var values = [7, 4, '13', -9, Infinity]; values.map(isSane); ``` **解析:** 此题没有什么难点,只需要记得 ``Infinity % 2 //NaN``就可以了。其他直接根据传入的值,运算即可。 **答案:** ``[true, true, true, false, false]`` --- ### 1.12、 ```javascript parseInt(3, 8) parseInt(3, 2) parseInt(3, 0) ``` **解析:** 此题的考察点,在1.1就考察过了。 **答案:** ``3, NaN, 3`` --- ### 1.13、 ```javascript Array.isArray(Array.prototype) ``` **解析:** 此题考察Array的原型。 ```javascript Array.prototype // [] ``` **答案:** ``true`` --- ### 1.14、 ```javascript var a = [0]; if ([0]) { console.log(a == true); } else { console.log("wut"); } ``` **解析:** 死记硬背吧。[参考图](https://dorey.github.io/JavaScript-Equality-Table/) **答案:** ``false`` --- ### 1.15、 ```javascript []==[] ``` **解析:** 此题如上,也可以理解没有类型转换,引用不同。 **答案:** ``false`` --- ### 1.16、 ```javascript '5' + 3 '5' - 3 ``` **解析:** 此题考察类型转换,和 + 号的作用。当有字符串操作数时,+ 表示字符串连接。 **答案:** ``'53', 2`` --- ### 1.17、 ```javascript 1 + - + + + - + 1 ``` **解析:** 此题,个人根据结果归纳了一套规则: 1. 首先,忽略所有的+号, 得到: ``1 - - 1`` 2. 然后 - - 得正,成为 + 号,得到:``1 1`` 3. 把最后的操作数相加,即为结果 测试代码: ```javascript 5 + 1 - 1 + 2 + 3 + 1 // 11 5 - 1 - 1 - 1 - 1 - 10 - 5 - 20 // -34 1 - - - - - - 1 // 2 ``` **答案:** ``2`` --- ### 1.18、 ```javascript var ary = Array(3); ary[0]=2 ary.map(function(elem) { return '1'; }); ``` **解析:** 此题的考察点在1.7就考察过了。但是需要注意,map函数,并不改变结果数组的长度。 **答案:** ``['1', undefined*2]`` --- ### 1.19、 ```javascript function sidEffecting(ary) { ary[0] = ary[2]; } function bar(a,b,c) { c = 10 sidEffecting(arguments); return a + b + c; } bar(1,1,1) ``` **解析:** 此题考察改变 ``arguments``的属性值,会不会影响该对象。由于 ``arguments`` 是个对象,实际上是会影响的。**注意:使用 ``use strict`` 可以避免此种情况。** **答案:** ``21`` **注意:当使用ES6语法,参数有rest parameters的时候,结果就不在一样了。** ```javascript function sidEffecting(ary) { ary[0] = ary[2]; } function bar(a, b, c=3) { c = 10; sidEffecting(arguments); return a + b + c; } bar(1,1,1); // 12 ``` --- ### 1.20、 ```javascript var a = 111111111111111110000, b = 1111; a + b; ``` **解析:** 当数值超过**JS能正确计算且不失精度的最大整数**时,会产生缺少精度问题。导致结果不太可预料。 基本上,超过16位数的整数都有这个问题了。很多时候,超过16位之后的数字会被补0。如下: ```javascript // 一共20个2,超过16位之后的,将会为0。 22222222222222222222 // 22222222222222220000 98765432109876543210 // 98765432109876540000 //当从1开始时,超过17位才为0。 12345678901234567890 // 12345678901234567000 ``` **答案:** ``111111111111111110000`` ================================================ FILE: JS札记/那些不常见的JavaScript题目(下).md ================================================ --- title: 那些不常见的JavaScript题目(下) date: 2017/02/21 14:47:10 --- ## 0、导言 JavaScript超乎寻常的灵活性,让JavaScript可以有很多特殊的用法,让我们来领略一下它们的风采吧。 此为下篇,上篇请查阅:[上篇](那些不常见的JavaScript题目(上).md) ## 1、那些不常见(好玩)的题目(下) --- ### 1.21、 ```javascript Number.MIN_VALUE > 0 ``` **解析:** 此题考察数字的最小值,它的最小值是大于0的浮点数。 **答案:** ``true`` --- ### 1.22、 ```javascript [1 < 2 < 3, 3 < 2 < 1] ``` **解析:** 此题考察运算顺序。直接从左往右计算,即可。等价于: ```javascript [true < 3, false < 1] => [1 < 3, 0 < 1] ``` **答案:** ``[true, true]`` --- ### 1.23、 ```javascript 2 == [[[2]]] ``` **解析:** 此题考察类型转化,全部调用toString(),转换为了``'2'`` **答案:** ``true`` --- ### 1.24、 ```javascript 3.toString(); 3..toString(); 3...toString(); ``` **解析:** 此题考察.的结合性问题,对于到底属于数字还是函数调用呢,其实只能是数字。三个点是什么语法? **注意:这也导致了整数字面量无法直接调用toString()** **答案:** ``error, '3', error`` --- ### 1.25、 ```javascript (function(){ var x = y = 1; })(); console.log(y); console.log(x); ``` **解析:** 此题考察作用域问题和var定义变量问题。当没有用var定义变量时,变量会成为全局变量。 **答案:** ``1, error`` --- ### 1.26、 ```javascript var a = /123/, b = /123/; a == b a === b ``` **解析:** 此题考察正则表达式比较,即使字面量相等,它们也不相等。 **答案:** ``false, false`` --- ### 1.27、 ```javascript var a = [1, 2, 3], b = [1, 2, 3], c = [1, 2, 4]; a == b; a === b; a > c; a < c; ``` **解析:** 此题同样考察对象比较。当比较相等时,引用不同,所以皆不等。当比较大小时,会按照数组元素,依次比较字典序。 **答案:** ``false, false, false, true`` --- ### 1.28、 ```javascript var a = {}, b = Object.prototype; [a.prototype === b, Object.getPrototypeOf(a) === b] ``` **解析:** 此题比较有误导性,对于一个对象实例来说,获取原型的方法是:``a.constructor.prototype`` 或者是 ``Object.getPrototypeOf(a)``,对于 ``a.prototype``,直接当成一个属性访问,由于未定义,所以会产生 ``undefined``。 **答案:** ``false, true`` --- ### 1.29、 ```javascript function f() {} var a = f.prototype, b = Object.getPrototypeOf(f); a === b ``` **解析:** 此题同样考察原型相关知识,前者是f的原型,后者是f的构造函数(Function)的原型。 **答案:** ``false`` --- ### 1.30、 ```javascript function foo() { } var oldName = foo.name; foo.name = "bar"; [oldName, foo.name] ``` **解析:** 函数的名称为常量,但需要注意,赋值不会报错。 **答案:** ``['foo', 'foo']`` --- ### 1.31、 ```javascript "1 2 3".replace(/\d/g, parseInt) ``` **解析:** 此题考察 ``String.replace()`` 的回调函数,它的回调函数定义是 ``funcation(matchValue, group, valueIndex, sourceStr){}``,依次为匹配到的值、正则分组,该值在字符串中的index,字符串本身。由于在该题中,正则没有分组,所以,调用了三次 ``parseInt`` 如下: ```javascript parseInt('1', 0, '1 2 3'); parseInt('2', 2, '1 2 3'); parseInt('3', 4, '1 2 3'); ``` **答案:** ``'1 NaN 3'`` --- ### 1.32、 ```javascript function f() {} var parent = Object.getPrototypeOf(f); f.name // ? parent.name // ? typeof eval(f.name) // ? typeof eval(parent.name) // ? ``` **解析:** 通过 ``Object.getPrototypeOf()`` 获取原型,参数是实例,当f为实例的时候,获取实例f的原型就等于 ``Function.prototype``,由于Function的原型还是一个function。所以 ``parent = Function.prototype 是一个没有名字的function``。 因此: ```javascript f.name // f parent.name // '' ``` 使用 ``eval``,执行f,会返回f这个函数,执行'',会返回 ``undefined``。 **答案:** ``'f', '', 'function', 'undefined'`` --- ### 1.33、 ```javascript var lowerCaseOnly = /^[a-z]+$/; [lowerCaseOnly.test(null), lowerCaseOnly.test()] ``` **解析:** 正则 ``test()`` 方法,会把参数当成字符串来使用,但要注意,``null`` 会被当成 ``'null'``来使用,无参则为 ``undefined``。 **答案:** ``[true, true]`` --- ### 1.34、 ```javascript [,,,].join(", ") ``` **解析:** 由于JS的Array本质也是对象,所以具有对象的一个特点,会忽略最后一个单引号,``[,,,].length // 3``。 另外,这种方式定义,实际上是没有产生真正的key的。``[,,,] // [undefined * 3]`` **答案:** ``[, , ]`` --- ### 1.35、 ```javascript var a = {class: "Animal", name: 'Fido'}; a.class ``` **解析:** 此题理应考察 ``class`` 为JS中的保留字,由于JS版本升级,此种写法已经可以正常返回属性值了。 **注意:在IE8-的浏览器中,会有语法错误,使用了保留字** **答案:** ``'Animal'`` --- ### 1.36、 ```javascript var a = new Date("epoch") ``` **解析:** 日期通过new返回的一定是一个Date对象,如果参数格式不合理,则会返回 ``Invalid Date`` 的日期对象。 **答案:** ``Invalid Date`` --- ### 1.37、 ```javascript var a = Function.length, b = new Function().length a === b ``` **解析:** ``Function.length`` 默认为1,``Function`` 实例的 ``length`` 属性等于 ``function``的参数个数。 **答案:** ``false`` --- ### 1.38、 ```javascript var a = Date(0); var b = new Date(0); var c = new Date(); [a === b, b === c, a === c] ``` **解析:** 直接函数调用,不关心参数是啥,都会返回当前日期字符串。 通过 ``new Date()``,如果无参数,返回当前日期对象。 通过 ``new Date(0)``,就有一个参数,并且是数字,则参数含义为long类型的UTC时间。 **答案:** ``[false, false, false]`` --- ### 1.39、 ```javascript var min = Math.min(), max = Math.max() min < max ``` **解析:** 此题考察 ``Math.min()`` 和 ``Math.max()`` 的用法。 当 ``Math.min()`` 无参数时,返回 ``Infinity``,参考:[MDN Math.min](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/min) 当 ``Math.max()`` 无参数时,返回 ``-Infinity``,参考:[MDN Math.min](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/max) **答案:** ``false`` --- ### 1.40、 ```javascript var a = new Date("2014-03-19"), b = new Date(2014, 03, 19); [a.getDay() === b.getDay(), a.getMonth() === b.getMonth()] ``` **解析:** 此题考察使用年月日构造函数时,月份是从0开始计算的。 **答案:** ``[false, false]`` ## 2、参考资料 [MDN](https://developer.mozilla.org/en-US) -- Mozilla 开发者网络 [MDN Javascrpt](https://developer.mozilla.org/en-US/docs/Web/JavaScript) --MDN JavaScript目录 ================================================ FILE: LICENSE ================================================ The MIT License (MIT) Copyright (c) 2015 hstar Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: MongoDB入门基础/01_记一次MongoDB裸奔.md ================================================ --- title: 01_记一次MongoDB裸奔 date: 2017/02/21 14:47:10 --- # 导言 大意失荆州,裸奔的 `MongoDB` 被黑了。虽然并不是什么非常重要的数据,但也给自己敲响的一个警钟。虽然我们平时不容易接触到数据安全,但我们在开发,部署项目的时候,一定要养成良好的安全意识。 根据木桶原理,整个系统的安全性,取决于整个系统最薄弱的环节。所以,我们要尽可能多的考虑更多组成部分的安全性。 # 事件发现 本月初,发生了大家所熟知的 `MondoDB赎金事件`。当时本人也保持了一定的关注,并去 [https://www.shodan.io/](https://www.shodan.io/) 溜达了一圈,顺便连了几个裸奔的MongoDB(当然,绝未做任何更改)。 直到昨天下午,发现我应用的管理员账户登录不上了。多次检查密码,发现还是无法解决,此时有点怀疑被黑了。由于应用有新建用户功能,新建一个和管理员账户同名的账户,居然成功了。这个时候,我想多半是遭了,只等晚上回去确认了。 回到家,远程到服务器,一连接,果然遭了(可怜我那几十个代码片段 + 几个Gist),需要赎金0.1BTC。 # 原因分析 此时可能就要问了,都知道了裸奔不安全,为嘛还不修复? 我能说我懒么?心大么? 因为当时我部署的版本的3.2,据说3.2默认没有开启外网访问。我心大到直接未经尝试就认为这是对的。 实际这句话也没错,Linux版本的 `3.x` 确实是默认绑定到 `127.0.0.1` 上的。可TM我是运行在 `Windows` 上的,由于安装的时候,默认没有创建配置文件,导致一运行就绑定到所有host上了。 **当上,以上都是外因!根本原因还是自己疏忽大意,安全意识薄弱。** # 解决(重头再来) 没有备份,直接无法恢复。 另外,0.1个BTC我是拿不出来的(我也不相信他会好心给你恢复),再加上数据也不是太重要,就直接把安全设置配置上,重头开始。 那现在是如何配置安全性的呢? 以下操作,均在未开启授权访问时执行 首先是添加用户并设置角色: ```bash # 切换到admin库 use admin # 创建User db.createUser({user: '', pwd: '', roles: [ {role: 'readWrite', db: ''}, {role: 'dbAdmin', db: ''} ]}) ``` 接下来就是创建一个配置文件(2.6之后,配置文件是yaml格式),内容如下: ``` systemLog: destination: file path: c:\data\log\mongod.log storage: dbPath: c:\data\db net: bindIp: 127.0.0.1 port: 27017 ``` **注意:配置文件中一定要设定 `log path` 和 `db path`** **注意2:如果要限制外网访问,就可以配置 net -> bindIp,另外也可以调整端口** 此时如何运行呢? ```bash mongod --config "D:\MongoDB\mongod.conf" --auth ``` 带上 `--auth` 就是开启授权访问。 最后客户端访问也需要稍微修改下,只需要修改 `mongoAddress` 配置即可: ```bash # MongoDB Connection String Format mongodb://[username:password@]host1[:port1][,host2[:port2],...[,hostN[:portN]]][/[database][?options]] # 实例地址 const mongoAddress = 'mongodb://admin:Pwd0603@127.0.0.1:27017/dojo' ``` ### 就这么一点简单的步骤,就能实现 `MongoDB` 较高的安全性,可这却是非常容易忽略的点。希望大家引以为戒。 # 总结 1. 一定不能疏忽大意,安全意识要加强。 2. 一定要结果实际验证,才能下结论。不能不清楚外置条件,人云亦云。 3. 不要有我的应用小,就不会被黑这种想法。批量攻击,才不会放过你。 4. 定期做好数据备份,被攻击是一回事,能否恢复又是另外一回事了。 ================================================ FILE: MongoDB入门基础/02_Mongo权限探索.md ================================================ --- title: 02_Mongo权限探索 date: 2017/02/21 14:47:10 --- # 0x0、导言 经过一次 `MongoDB` 被黑之后,就想把 `MongoDB` 的权限仔细的了解一遍。安全第一!安全第一! 为了避免版本不一致导致的差异,特此说明:以下命令均在 `Mongo3.4` 中测试,理论上支持所有 `3.x` 版本 # 0x1、MongoDB权限系统 `MongoDB` 的权限管理也是符合 `RBAC` 的权限系统。 既然是 `RBAC` 的权限管理,那么就一定会有 `actions` 和 `resources` 的概念。 在 `MongoDB` 中,每一种操作都对应一种 `action`,在 [Action List](https://docs.mongodb.com/manual/reference/privilege-actions/) 可以查看所有的 `Action` 列表 同理,`Collection/Database/Cluster` 都是 `Resrouce`, `resource` 列表也可以在 [Resource Document](https://docs.mongodb.com/manual/reference/resource-document/) 找到。 ## 1.1、如何授权启动 在 `MongoDB` 中,默认是无授权启动的。如果我们要开启授权,那么需要在启动 `MongoDB` 的时候,加上 `--auth` 参数: ```bash mongod --config "" --auth ``` 如果使用配置文件,那么也可以直接配置 ``` security authorization: enabled ``` **注意:如果没有添加账户,就算设置 `--auth` 参数也无效,需要先添加用户,在添加用户的时候,必须要指定用户角色。** ## 1.2、MongoDB角色管理 在 `MongoDB` 中,具有两种类型的角色,一类是系统角色,一类是自定义角色。 一般来说,我们只需要关注系统角色。自定义角色需要通过角色相关的Shell来进行CRUD。 ### 1.2.1、系统角色 `MongoDB` 按照分类,具有较多的角色,列举如下,也可在 [security-built-in-roles](https://docs.mongodb.com/manual/core/security-built-in-roles/) 查看所有内建角色明细。 * 数据库用户相关角色 1. read --只能查看单个数据库 2. readWrite -- 可读写单个数据库 * 数据库管理员相关角色 1. dbAdmin -- 数据库管理员,能进行差不多各种操作 2. dbOwner -- 等于dbAdmin、readWrite、userAdmin的并集,数据库拥有者 3. userAdmin -- 能够管理各种用户,角色等(如果是admin集合的,则能管理所有db) * 集群管理员相关角色 1. clusterAdmin -- 是clusterManager,clusterMonitor,clusterMonitor权限的集合,还多了个删除数据库操作。 2. clusterManager -- 主要是配置集群 3. clusterMonitor -- 主要是监控集群 4. hostManager -- 主要是配置主机 * 备份还原相关角色 1. backup -- 备份操作 2. restore -- 还原操作 * 针对所有DB的角色 1. readAnyDatabase -- 可读取所有的数据库 2. readWriteAnyDatabase -- 可读写所有的数据库 3. userAdminAnyDatabase -- 所有db的用户管理员 4. dbAdminAnyDatabase -- 所有db的DB管理员 * 超级管理员角色 1. root -- 超级超级管理员,最大权限了 * 内部角色 1. __system -- 既然是内部角色,我们就不要去用了 ### 1.2.2、自定义角色 除了系统角色之外,还可以自定义角色,能够更灵活的控制权限。 详细,请参考:[role-management](https://docs.mongodb.com/manual/reference/method/js-role-management/) ## 1.2、MongoDB用户管理 有了Role的知识,我们来看User,就很easy了。 和一般的系统权限类似,MongoDB 也提供了诸多和用户相关的操作 1. db.auth() -- 用于登录 ``` db.auth( { user: , -- 用户 pwd: , -- 密码 mechanism: , -- 可选,认证机制 digestPassword: } ) ``` 2. db.createUser() -- 创建用户 ``` { user: "", -- 用户名 pwd: "", -- 密码 customData: { }, -- 自定义的数据 roles: [ -- 配置角色,db角色,设置数据库, 否则直接写角色名 { role: "", db: "" } | "", ... ] } ``` 3. db.updateUser() -- 更新用户 ``` db.updateUser( "", -- 要更新的用户名 { -- 要更新的用户对象 customData : { }, roles : [ { role: "", db: "" } | "", ... ], pwd: "" }, writeConcern: { } ) ``` **注:仅仅只需要更新role的话,考虑使用grantRolesToUser,revokeRolesFromUser** 5. db.removeUser() 6. db.dropAllUsers() 7. db.dropUser() 8. db.grantRolesToUser() 9. db.revokeRolesFromUser() 10. db.getUser() 11. db.getUsers() 其他用户方法都比较类似,查看详细: [user-management](https://docs.mongodb.com/manual/reference/method/js-user-management/) # 0x2、使用授权的MongoDB 如果在 `MongoDB` 中配置了授权,那么连接到带授权的 `MongoDB` 也会有些许区别。 ```bash # 简单的MongoDB Connection String mongodb://127.0.0.1/testdb # 带端口的MongoDB mongodb://127.0.0.1:27018/testdb # 带授权的MongoDB mongodb://user1:password1@127.0.0.1:27017/testdb # 带授权,且User是admin的场景 mongodb://user1:password1@127.0.0.1:27017/testdb/?authSource=admin ``` 如果是 `shell`,则需要先执行 `db.auth('name', 'pwd')`,之后才能执行其他命令。 # 0x3、总结 加强安全意识,人为预防常规安全风险。 ================================================ FILE: Other/Go Go.md ================================================ --- title: Go Go date: 2017/02/21 14:47:10 --- # 题记 > 学习是进步的源泉 在这个云计算、多核盛行的时代,学习一门与之相配合的语言也就无可厚非了。那么对多核与并行计算原生支持的Go就是我的选择了... 关于GO的好坏,我不会去深究,在每个人眼中,这都是主观的。喜欢就会觉得好,不喜欢好也是坏。当然,它本身的优势与劣势是值得我们关注了,这决定了它的适用性。 工欲善其事必先利其器,本人学习一门语言之前,喜欢先寻找趁手的兵器。搜索之,继而找到了LiteIDE。由于偏爱集成开发环境,那么Sublime也就不是我的首选了。 以上全是废话,下面开始搭建开发环境。 # 1、准备工作 1. PC + Windows操作系统(我承认我只会玩Windows...) 2. 你得下载Go语言:[https://golang.org/](https://golang.org/) BTW:我下载的文件名是:go1.4.1.windows-amd64.msi 3. LiteIDE:[https://github.com/visualfc/liteide](https://github.com/visualfc/liteide) # 2、搭建环境 先,你需要安装Go语言。什么?你不会?双击msi(或者exe),Next到手软就可以了。当然,最后应该是Finish。经过这个步骤,go就安装上了,什么环境变量啥的都给你配置好了。当然,安装成功不成功不是你说了算,那么打开cmd,输入一个go试试?如果报错,那么狠抱歉,请重试该步骤,或者检查环境变量。如果提示Go is a tool for managing Go source code ,那么恭喜你,安装已成功! 下面,安装LiteIDE,这货的下载地址在[http://sourceforge.net/projects/liteide/files](http://sourceforge.net/projects/liteide/files),如果你爱折腾,那么直接下载源码编译也是OK的,请参考[https://github.com/visualfc/liteide/blob/master/liteidex/deploy/welcome/en/install.md](https://github.com/visualfc/liteide/blob/master/liteidex/deploy/welcome/en/install.md)。此处熊出没!liteide下载下来可不是exe或者msi文件哦。我下载的文件名是:liteidex27-1.windows.7z。既然这样,那么解压缩,找个地儿一扔就搞定,是不是更简单? # 3、永远的经典:Hello World 既然到了这里,想必以上两个小玩意已经安装好了。那么就应该开始写代码。据说程序员爱代码不爱妹子,这是真的么? 找到liteIDE的安装目录,进行/bin目录,双击“liteide.exe”(PS:一看到这个丑丑的太极图标,我就在想,这货应该是一个国人开发的吧,呵呵,果真是!)。和一般IDE无异,File->New->Go1 Command Project(Not Use GOPATH),当然其他也是可以选的,惊喜在等着你。输入必要的信息,那么一个项目就创建好了。 打开main.go文件,输入代码(这段代码我是借鉴的,肯定不是copy的): // demo1 project main.go package main import ( "fmt" ) func main() { fmt.Println("Hello World!") } 然后点击工具栏上的FR(File Run),这坑爹的按钮,简直反人类。。我点了N次Go按钮之后,才发现这货才是运行。。在右下方区域就能看到输出了。。Hello World~ **后记:** 至此,一个简单的Go开发环境已经搭建好了。 ## 或许你还有疑问 1. 如果生成可执行文件呢? **可以利用LiteIDE的Build按钮,或者是控制台命令 ``go build``** 2. 如果我是64位系统,如何生成32位可执行程序呢? **我也不知道,只能给你关键字“交叉编译”** 3. 更多,就留给你慢慢发掘吧。Over! ================================================ FILE: Other/NPM使用详解(上).md ================================================ --- title: NPM使用详解(上) date: 2017/02/21 14:47:10 --- ## 1、NPM是什么? NPM是JavaScript的包管理工具,在安装NodeJS(什么?你不知道node?来,我们合计合计:[https://nodejs.org/](https://nodejs.org/))的时候,会自动安装上npm。 要查看安装的npm版本,只需要打开cmd控制台,输入``npm -v`` NPM使得JavaScript开发者分享和重用代码非常容易,同时也让你能否非常方便的更新你分享的代码。 NPM能够自己升级自己,使用命令如下: ``npm install npm -g`` ## 2、NPM的使用 以下代码示例中:<>表示必选参数,[]表示可选参数 ### #最常用命令 #### 2.1、 init:用于初始化项目 /* * npm init [-f|--force|-y|--yes] */ //在文件夹中打开cmd,然后输入npm init,打开项目初始化向导 npm init //如果文件夹名称满足npm的module name, //那么使用如下方式,可以直接生成一个默认的package.json //如果文件夹名称不满足要求,那么会出错 npm init -f npm init --force npm init --force=true npm init -y npm init --yes npm init --yes=true #### 2.2、install:用于安装模块 /* * npm install (with no args in a package dir) * npm install * npm install * npm install * npm install [@/] [--save|--save-dev|--save-optional] [--save-exact] * npm install [@/]@ * npm install [@/]@ * npm install [@/]@ * npm i (with any of the previous argument usage) */ //直接使用npm install 或者是npm i,表示根据package.json,安装所有依赖 npm install npm i //如果加上--production参数,那么只会安装dependencies的模块, //而不会安装devDependencies的内模块 npm install --production npm i --production //使用全局上下文来初始化 npm install -g npm i -g //安装指定模块 npm install npm install -g //全局安装 npm install @ //指定要安装的模块版本 npm install @ //指定要安装的模块版本 npm install --registry= //指定零食的仓库地址 npm install --msvs_version= //指定编译使用的VS版本 npm install --save // 安装模块并修改package.json的dependencies npm install --save-dev //安装模块并修改package.json的devDependencies npm install //从指定的压缩包地址安装,示例如下: npm install https://github.com/indexzero/forever/tarball/v0.5.6 npm install //从指定的压缩包安装,如下(注意压缩包格式): npm install del-1.2.0.tar.gz //使用.tgz和.tar.gz格式 npm install @/ //安装私有包 #### 2.3、uninstall:用于卸载模块 /* * npm uninstall [@/] [--save|--save-dev|--save-optional] */ //直接卸载模块,加上-g参数,表示卸载全局的模块 npm uninstall npm uninstall -g //卸载时同时修改package.json文件 npm uninstall --save-dev npm uninstall --save #### 2.4、update:用于更新模块 /* * npm update [-g] [ [ ...]] */ //更新一个或多个模块,加上-g参数,表示更新全局的模块 npm update [packageName2...] npm update [packageName2...] -g //更新时同时修改package.json文件 npm update [packageName2...] --save-dev npm update [packageName2...] --save #### 2.5、config:用于设置npm参数 //设置指定参数 npm config set [--global] npm set [--global] //可以省略config //获取现有参数值 npm config get npm get //可以省略config //删除指定参数,此时参数值会变为默认值 npm config delete //查看npm信息;注意:此命令不是查看所有参数配置 npm config list //编辑全量的npm配置文件(.npmrc) npm config edit //可以将config使用c代替,执行以上所有命令 npm c [set|get|delete|list] #### 2.6、cache:管理包缓存 //将指定的包加入npm缓存 npm cache add npm cache add npm cache add npm cache add @ //查看现有的npm包缓存,如果加上path参数,则查看该路径下的文件 npm cache ls [] eg: npm cache ls gulp //清空缓存。如果加上path,则清理指定路径下的包缓存 npm cache clean [] eg: npm cache clean gulp ================================================ FILE: Other/NPM使用详解(下).md ================================================ --- title: NPM使用详解(下) date: 2017/02/21 14:47:10 --- 在浏览本文之前,建议您先浏览《NPM使用详解(上)》 在上一文中,罗列出了最常用的NPM命令,那么本文将继续分解剩下的NPM命令 --- #### 1、access #### 2、adduser //用于启动在指定的git仓库添加用户的向导 npm adduser [--registry=url] [--scope=@orgname] [--always-auth] //eg: npm adduser --registry=http://registry.npmjs.org #### 3、bin //打印出npm执行安装的文件夹 npm bin #### 4、bugs //查看某个包的issue列表 npm bugs //eg:(将会用浏览器打开https://github.com/sindresorhus/del/issues) npm bugs del // 可以直接在一个包的文件夹中执行无参数的命令,将自动打开该包的issue列表 //eg:(在del文件夹下执行cmd) npm bugs #### 5、build #### 6、bundle(已过期) #### 7、completion #### 8、dedupe // npm dedupe [package names...] //可简化为如下调用 npm ddp [package names...] #### 9、deprecate //为指定版本的包添加过期警告 npm deprecate [@] // eg: npm deprecate my-thing@"< 0.2.3" "critical bug fixed in v0.2.3" #### 10、dist-tag npm dist-tag add @ [] npm dist-tag rm npm dist-tag ls [] #### 11、docs //打开包的文档页面 npm docs [ [ ...]] npm docs (with no args in a package dir) // 打开包的首页readme npm home [ [ ...]] npm home (with no args in a package dir) #### 12、edit npm edit [@] #### 13、explore npm explore [ -- ] #### 14、help //打开本地npm的帮助文件 npm help npm help some search terms //eg:(打开config的本地帮助) npm help config #### 15、help-search //从npm的markdown文档中查询所有的term,并展示 npm help-search some search terms #### 16、link npm link (in package folder) npm link [@/] npm ln (with any of the previous argument usage) #### 17、logout //从指定的仓库登出 npm logout [--registry=url] [--scope=@orgname] #### 18、ls //列举当前文件夹下的所有包 npm list [[@/] ...] npm ls [[@/] ...] npm la [[@/] ...] npm ll [[@/] ...] #### 19、npm npm [args] #### 20、outdated(☆☆☆☆☆) //检查当前文件夹中的包版本(当前,需要,最新) npm outdated [ [ ...]] #### 21、owner //管理包的拥有者 npm owner ls npm owner add npm owner rm #### 22、pack(☆☆☆☆☆) //压缩包文件夹 npm pack [ [ ...]] //eg:在del目录中直接执行 npm pack //或者在项目目录中,执行 npm pack del #### 23、prefix //打印本地前缀到控制台,如果-g,则打印全局的前缀 #### 24、prune(☆☆☆☆☆) //删除多余的包(如果指定包名,则删除指定的包) npm prune [ [ [ [--tag ] [--access ] npm publish [--tag ] [--access ] #### 26、rebuild //重新编译包 npm rebuild [ [ ...]] npm rb [ [ ...]] #### 27、repo //在浏览器中打开包的仓库地址 npm repo npm repo (with no args in a package dir) #### 28、restart //重新启动包 npm restart [-- ] #### 29、rm //移除包 npm rm npm r npm uninstall npm un #### 30、root //打印node_modules文件夹到控制台 npm root #### 31、run-script //运行任意的包脚本 npm run-script [command] [-- ] npm run [command] [-- ] #### 32、search #### 33、shrinkwrap #### 34、star //给指定的包加star npm star [, ...] npm unstar [, ...] #### 35、stars //查看指定用户的stars npm stars npm stars [username] #### 36、start #### 37、stop #### 38、tag #### 39、test #### 40、unpublish #### 41、version npm version [ | major | minor | patch | premajor | preminor | prepatch | prerelease] //查看项目相关信息 npm version npm version major #### 42、view #### 43、whoami ================================================ FILE: Other/Thrift简单实践.md ================================================ --- title: Thrift简单实践 date: 2017/02/21 14:47:10 --- ## 0、什么是RPC **RPC**(Remote Procedure Call - 远程过程调用),是通过网络从远程计算机上请求服务,而不需要了解底层网路技术的细节。简单点说,就是**像调用本地服务(方法)一样调用远端的服务(方法)。** ### RPC与REST的区别 RPC是一种协议,REST是一种架构风格。 RPC以行为为中心,REST以资源为中心。当加入新功能时,RPC需要增加更多的行为,并进行调用。REST的话,调用方法基本不变。 RPC可以不基于HTTP协议,因此在后端语言调用中,可以采用RPC获得更好的性能。REST一般是基于HTTP协议。 ## 1、RPC框架Thrift(0.9.3) Thrift是一种开源的高效的、支持多种编程语言的远程服务调用框架。支持C++, Java, Python, PHP, Ruby, Erlang, Perl, Haskell, C#, Cocoa, JavaScript, Node.js, Smalltalk, OCaml 和 Delphi等诸多语言,能够很好的进行跨语言调用。 Thrift官网: [https://thrift.apache.org/](https://thrift.apache.org/) ## 2、Thrift的简单实践(Windows) ### 2.1 安装Thrift 在[http://www.apache.org/dyn/closer.cgi?path=/thrift/0.9.3/thrift-0.9.3.exe](http://www.apache.org/dyn/closer.cgi?path=/thrift/0.9.3/thrift-0.9.3.exe)这里可以下载Thrift的windows系统编译版本。 该文件是一个绿色文件,可以放置在目录中,进入该目录的cmd,就可以直接使用thrift。输入``thrift -version``可以查看当前Thrift的版本。 ![img1](http://7xit2j.com1.z0.glb.clouddn.com/abc1.png) 至此,Thrift已完成安装 ### 2.2 编写接口定义文件 在安装好Thrift之后,需要我们编写接口定义文件,用来约定服务和thrift类型的接口定义。 Thrift主要有一下这些类型: 1. bool --简单类型,true or false 2. byte --简单类型,单字符 3. i16 --简单类型,16位整数 4. i32 --简单类型,32位整数 5. i64 --简单类型,64位整数 6. double --简单类型,双精度浮点型 7. string --简单类型,utf-8编码字符串 8. binary --二进制,未编码的字符序列 9. struct --结构体,对应结构体、类等对象类型 10. list --list容器 11. set --set容器 12. map --map容器 13. enum --枚举类型 接下来,利用这些类型,编写一个简单的.thrift接口定义文件。 /* 1.thrift file content */ namespace js ThriftTest namespace csharp ThriftTest service ThriftTest{ double plus(1:double num1, 2:double num2) } 更复杂的案例: [https://git-wip-us.apache.org/repos/asf?p=thrift.git;a=blob_plain;f=test/ThriftTest.thrift;hb=HEAD](https://git-wip-us.apache.org/repos/asf?p=thrift.git;a=blob_plain;f=test/ThriftTest.thrift;hb=HEAD) 在利用``thrift --gen js:node --gen js 1.thrift``来生成好客户端代码和服务端代码。可以跟多个--gen 参数,来实现一次性生成多个语言的代码。 ### 2.3 利用Thrift实现nodeJS服务端 var thrift = require('thrift'); var ThriftTest = require("./gen-nodejs/ThriftTest"); var ttypes = require("./gen-nodejs/1_types"); var nodeServer = thrift.createServer(ThriftTest, { //完成具体的事情 plus: function(n1, n2, callback){ console.log(`server request, n1 = ${n1}, n2 = ${n2}.`); callback(null, n1 + n2); } }); //处理错误,假设不处理,如果客户端强制断开连接,会导致后端程序挂掉 nodeServer.on('error', function(err){ console.log(err); }); nodeServer.listen(7410); console.log('node server started... port: 7410'); //如果client的浏览器,通信采用http的时候,需要创建http server var httpServer = thrift.createWebServer({ cors: {'*': true}, //配置跨域访问 services: { '/thrift': { //配置路径映射 transport: thrift.TBufferedTransport, protocol: thrift.TJSONProtocol, processor: ThriftTest, handler: { //具体的处理对象 plus: function(n1, n2, callback) { console.log(`http request, n1 = ${n1}, n2 = ${n2}.`); callback(null, n1 + n2); } } } } }); httpServer.on('error', function(err) { console.log(err); }); httpServer.listen(7411); console.log('http server started... port: 7411'); ### 2.4 Node Client 调用 var thrift = require('thrift'); var ThriftTest = require('./gen-nodejs/ThriftTest'); var ttypes = require('./gen-nodejs/1_types'); transport = thrift.TBufferedTransport() protocol = thrift.TBinaryProtocol() var connection = thrift.createConnection("localhost", 7410, { transport : transport, protocol : protocol }); connection.on('error', function(err) { console.log(false, err); }); var client = thrift.createClient(ThriftTest, connection); var sum = client.plus(1, 1, function(err, result){ //connection.end(); //如果不关闭连接,那么强制断开连接,将会导致后端出现error if(err){ console.log(err); return; } console.log(result); }); ### 2.5、Http Client 调用 Thrift Test Client + ? **注意:如果在thrift生成代码时,使用了--gen js:jquery参数,那么在浏览器调用的时候,就必须依赖jquery。** ## 3、demo地址 [https://github.com/hstarorg/HstarDemoProject/tree/master/thrift_demo](https://github.com/hstarorg/HstarDemoProject/tree/master/thrift_demo) ================================================ FILE: Other/TypeScript札记:初体验.md ================================================ --- title: TypeScript札记:初体验 date: 2017/02/21 14:47:10 --- ## 1、简介 TypeScript 是一种由微软开发的自由和开源的编程语言。它是JavaScript的一个超集,而且本质上向这个语言添加了可选的静态类型和基于类的面向对象编程。 TypeScript是一种Compile-to-JavaScript的语言 TypeScript扩展了JavaScript的句法,所以现有的JavaScript程序可以不加修改,直接在TypeScript下工作。同时,TypeScript编译产生JavaScript以确保兼容性。 ## 2、特点&优势 2.1、兼容现有JS代码 2.2、类型系统,面向对象设计,保证程序的健壮性(编译检查) 2.3、良好的语法,良好的工具支持 2.4、良好的社区支持 ## 3、快速开始 3.1、 **工具** 如果是VS开发,安装 [TypeScript 1.4 for Visual Studio 2013](https://portal.qiniu.com/signup?code=3lo24xqrim8gi),版本随时变化,建议下载最新版本。 如果是NPM用户,那么直接 ``npm install -g typescript`` 3.2、 **Hello Jay** 使用VS的用户,直接新建项(TypeScript File即可);使用其他IDE的用户,如果IDE支持TypeScript,那么直接新建TypeScript;其他则新建文本文件,后缀名为ts。如果是不能在IDE中编译,那么可以直接通过npm安装typescript之后,使用tsc fileName.ts,进行编译。 打开1.ts文件,输入: function hello(name: string){ return 'Hello,' + name; } var res = hello('Jay'); console.log(res); 执行``tsc 1.ts``之后,生成一个1.js文件(具有可读性的标准js文件): function hello(name) { return 'Hello,' + name; } var res = hello('Jay'); console.log(res); ## 4、参考资料 1、 [官网:http://www.typescriptlang.org/](http://www.typescriptlang.org/) 2、 [入门指南: https://github.com/vilic/typescript-guide](https://github.com/vilic/typescript-guide) ================================================ FILE: Other/Windows下把Nginx,PM2包装为服务.md ================================================ --- title: Windows下把Nginx,PM2包装为服务 date: 2017-2-25 09:55:26 --- # 0x0、前言 在 `Windows` 上部署 `Node` 或者前后端分离的静态Web程序时,我们一般会使用到 `PM2` 和 `Nginx`。 `PM2` 用于管理 `Node` 程序。 `Nginx` 用于托管静态文件或者反向代理。 # 0x1、遇到的问题 在使用 `PM2` 和 `Nginx` 的时候,我们不能包装服务器一直不关机。只要一关机,我们就需要手动去重新启动我们的服务,这种方式非常不友好。 所以,我们需要它们能开机自动启动。虽然有多种方式,但最佳的无疑是包装为 `Windows Service` 。 # 0x2、winsw 在寻找方案的过程中,我们发现了 [winsw](https://github.com/kohsuke/winsw),它是一个可以将可执行程序包装为 `Windows Service` 的包装程序。 接下来就用它来演示如何将 `Nginx` 和 `PM2` 包装为 `Windows Service` ## 将 `Nginx` 包装为 `Windows Service` 首先,我们要先准备好 `Windows` 版本的 `Nginx`,[Nginx Donwload Page](http://nginx.org/en/download.html),选择合适的版本后,下载解压到目录中。 接着我们从 [https://github.com/kohsuke/winsw](https://github.com/kohsuke/winsw) 上找到 `winsw` 的可执行文件,实际是:[http://repo.jenkins-ci.org/releases/com/sun/winsw/winsw/](http://repo.jenkins-ci.org/releases/com/sun/winsw/winsw/)。 选择合适的版本后(建议用2.x版本),进行下载。如 [winsw-2.0.2-bin.exe](http://repo.jenkins-ci.org/releases/com/sun/winsw/winsw/2.0.2/winsw-2.0.2-bin.exe) 。 将下载好的文件放到 `nginx` 的目录下(和nginx.exe同级),并改名为 `nginx-service.exe`(名字可以自定义)。 之后,我们需要创建对应的服务配置文件 `nginx-service.xml` (该文件对应上一步骤的名称),并输入以下内容: ```xml nginx Nginx High Performance Nginx Service. D:\GreenSoft\nginx-1.11.5\logs\ rotate D:\GreenSoft\nginx-1.11.5\nginx.exe D:\GreenSoft\nginx-1.11.5\nginx.exe -s stop ``` 最后我们就可以使用 `nginx-service install` 来安装服务,并通过 `net start nginx` 来启动了。 如果要重启服务呢?使用 `net stop nginx & net start nginx` ## 将 `PM2` 包装为 `Windows Service` (公司权限限制,暂未测试通过) 和 `Nginx` 类似,我们只需要将wrapper程序改名,然后创建匹配的`xml`配置文件即可。 ```xml pm2 PM2 Node applications management tools. rotate %AppData%\npm\pm2 resurrect %AppData%\npm\pm2 save & %AppData%\npm\pm2 delete all ``` ================================================ FILE: Other/开发者讨厌你API的十个原因.md ================================================ --- title: 开发者讨厌你API的十个原因 date: 2017/02/21 14:47:10 --- ##1、文档的吸引力太弱## ###解决之道### 1. 采用大图片:[示例站点](https://www.twilio.com/docs) 2. 文档清晰度:[示例站点](https://stripe.com/docs/api) 3. 文档易于查找:[示例站点](https://stripe.com/docs/) 4. 生动的文档: 1. [Swagger](https://github.com/wordnik/swagger-core) 2. [I/O Docs](https://github.com/mashery/iodocs) 3. 采用RAML(RESTful API 模型语言) [RAML官网](raml.org) ##2、您的沟通技能需要工作(你不能保证开发者始终被通知到) ###解决之道### 1. 使用变更日志:[http://developer.github.com/changes/](http://developer.github.com/changes/) 2. 使用路线图:[https://developers.facebook.com/roadmap/](https://developers.facebook.com/roadmap/) 3. 采用发布日志:[http://techblog.constantcontact.com/api/release-updates](http://techblog.constantcontact.com/api/release-updates) 4. 使用博客(Blog):[http://aws.typepad.com/](http://aws.typepad.com/) 5. 使用论坛(Forum):[http://stackoverflow.com/questions/tagged/soundcloud](http://stackoverflow.com/questions/tagged/soundcloud) 6. 邮件通知 ##3、你不能使API使用简单## ###解决之道### 1. 说明你是做什么的:[https://www.twilio.com/voice/api](https://www.twilio.com/voice/api) 2. 支持快速注册:[https://manage.stripe.com/register](https://manage.stripe.com/register) 3. 使用step1-step2-step3说明使用步骤:[示例站点](http://developer.constantcontact.com/get-started.html) 4. 提供快速入门手册:[https://www.twilio.com/docs/quickstart](https://www.twilio.com/docs/quickstart) 5. 提供免费版或者免费试用版:[https://parse.com/plans](https://parse.com/plans) 6. 提供丰富的SDK(支持多种开发语言) 7. 使用GitHub :[https://github.com/OneNoteDev](https://github.com/OneNoteDev) ##4、没有提供法律申明## ###解决之道### 1. 要明确权利与义务:[http://500px.com/terms](http://500px.com/terms) 2. 编写使用协议:[https://www.etsy.com/developers/terms-of-use](https://www.etsy.com/developers/terms-of-use) 3. 申明越短越好:[http://googledevelopers.blogspot.com](http://googledevelopers.blogspot.com) 4. 申明要想长远:[https://developers.google.com/youtube/terms](https://developers.google.com/youtube/terms) 5. 分享你的财富:[http://slideshare.net/jmusser](http://slideshare.net/jmusser) ##5、你的API不可靠(慢、错误、不可靠)## API会被停运(Outage)、Bug、速率(Rate limit)、变更(包含有计划的变更和未被文档跟踪的变更)、ToS违规、Provider biz change、网络等原因影响。 不要让API返回未知的错误信息,让用户迷惑。 ###解决之道### 1. 使用状态页:[http://status.aws.amazon.com/](http://status.aws.amazon.com/) 2. 监控API:[http://www.apiscience.com](http://www.apiscience.com) 3. 不要隐藏API的变化,如停运:[http://blog.akismet.com](http://blog.akismet.com) ##6、没有提供能帮助我调用成功的工具## ###解决之道### 1. 提供开发者仪表板:[https://manage.stripe.com/test/dashboard](https://manage.stripe.com/test/dashboard) 2. 提供 Debug/Log 等日志:[示例站点](www.twilio.com/user/account/developer-tools/app-monitor) 3. 提供用于测试的沙盒环境:[https://www.twilio.com/user/account](https://www.twilio.com/user/account) 4. 提供Playground:[https://developers.google.com/oauthplayground](https://developers.google.com/oauthplayground) 5. 提供测试控制台:[https://apigee.com/providers](https://apigee.com/providers) ##7、只管销售,但不提供售后服务## ###解决之道### 1. Evangelists:[http://sendgrid.com/developers](http://sendgrid.com/developers) 2. Events:[https://www.twilio.com/conference](https://www.twilio.com/conference) 3. Hackathons 4. PS:不知道如何翻译,so总结一点,就是提供售后支持。 ##8、API太复杂了(你使用你自己定制的授权、协议、格式)## ###解决之道### 1. 使用REST(当前最流行的风格) 2. 使用JSON格式(XML也还好) 3. 保持务实:[http://apigee.com/about/content/web-api-design](http://apigee.com/about/content/web-api-design) ##9、你的TTFHW(Time to *(your)* First Hello World)太长## ###解决之道### 1. 极好的开发者体验:[http://developerexperience.org](http://developerexperience.org) 2. 在所有问题修正前,先说“Sorry” ##10、你还没有从最好的学习到的## 1. 学习榜样的做法(Twilio,Stripe,GitHub.SendGrid) 2. 保持进步 3. 记住一句话:API是旅程,不是目的地 ================================================ FILE: Other/浅析12306前端优化点.md ================================================ --- title: 浅析12306前端优化点 date: 2017/02/21 14:47:10 --- ## 关于12306 中国铁路客户服务中心([12306.cn](https://kyfw.12306.cn/)),相信大家都不陌生。作为一个超大型的类电商网站,具体业务不予置评,但从前端设计来看,却有诸多的不足。 ## 12306订票首页分析 12306首页([https://kyfw.12306.cn/otn/](https://kyfw.12306.cn/otn/))请求达到32个,累计文件大小近800k。 其中有一半是图片资源,大小达到444kb。 ![12306首页图片资源](http://images.cnblogs.com/cnblogs_com/humin/771181/o_1.jpg) 另外有6个css文件请求,特别的是有2次css请求完全指向同一个css文件。 ![12306首页CSS请求](http://images.cnblogs.com/cnblogs_com/humin/771181/o_2.jpg) 还有8个js请求 ![12306首页JS请求](http://images.cnblogs.com/cnblogs_com/humin/771181/o_3.jpg) 这就是首页初次打开所需要的内容,当然还有两个html页面,就不计算了。可以看到,打开整个页面文件大小达到800k,和taobao,jd之类的比起来,这个大小是很小的,但是为什么给用户的体验就是卡顿、加载中、加载中呢? 接下来,就从前端的角度来看下,有没有可以优化的地方呢? ## 优化1、使用浏览器缓存 在页面加载中,12306请求了如此多的资源,很多资源看起来,根本就是不太容易变化的,应在HTTP标头中设置有效期,尽可能多的使用浏览器缓存。 ## 优化2、图片优化 数据传输时间,在访问网站的过程中,是一个耗时比较大的过程,其中又以图片传输为最,如果网站上有较多的图片,那么就要想办法减少体积,延迟加载等等。在12306的页面上,logo(https://kyfw.12306.cn/otn/resources/images/logo.png ),icon(https://kyfw.12306.cn/otn/resources/images/logo.png )等等图片都是可以优化的。 ## 优化3、图片组合为CSS贴图 浏览器一般都有并发连接数限制,也就是同时请求的资源数量是有效的,前端优化点之一就是减少请求数量,那么12306中的诸多小图片完全可以合并到一个大图之中,采用贴图定位的方式,降低请求数量。 ## 优化4、暂缓JS解析 由于JS是阻塞加载的,一般来说,把js放在head中会影响页面的渲染速度,很多时候,我们都推荐把js放在body结束标记之前。但12306偏偏没有这么做,把大把的js放在head中。 ## 优化5、使用css而不是图片控制背景 这个似乎是大家都知道的常识,就算为了兼容老版本的浏览器,也可以考虑做优雅降级。但12306偏偏就大量使用背景图。 ## 优化6、CSS合并 同样为了减少请求数,应该尽量将CSS压缩合并。分析12306的站点css,发现部分合并了,部分没有,而且有些css连压缩都没做,很难想象是怎么打算的。另外,外部控件的样式(不会变的样式)完全可以打包放到cdn上。 ## 优化7、JS合并 JS同上,该打包就打包,不要搞一堆js出来,加载还慢。。 ## 总结 抢票还是每年的一个老大难的问题。12306,你可以推说你的核心逻辑复杂,这个我接受。但你完全可以把前端的一些基本优化点做到吧。就算我买不到票,至少我抢票的时候心情不至于太差。。 以上,抢票之余作为一个伪前端的发泄。 ================================================ FILE: Other/程序集强签名.md ================================================ --- title: 程序集强签名 date: 2017/02/21 14:47:10 --- ## 1、特点 1.1、强签名的程序集可以注册到GAC(全局应用程序集缓存),不同的应用程序可以共享同一个dll。 1.2、强签名的库(应用程序)只能引用强签名的库。非强签名的库(应用程序)没有限制,既可以引用强签名的库,也可以引用非强签名的库。(实际测试,强签名的应用只引用非强签名的程序集但不使用是可以的,只要使用了引入库的东西,那就会报:引用的程序集没有强签名) 1.3、强签名无法保护源代码,但能防止dll被第三方篡改。 1.4、能防止dll冲突。 ## 2、方法 **--如果有源代码** 项目 -> 右键 -> 属性 -> 签名: ![Signing panel](http://7ximjo.com1.z0.glb.clouddn.com/1.png) 勾选 Sign the assembly(签名程序集) ![Signing panel](http://7ximjo.com1.z0.glb.clouddn.com/2.png) 填写 key file name,如果勾选了密码保护key file,那么就输入密码。点击OK就创建好了签名文件。 将属性保存,然后再次编译,程序集就是强签名程序集了。 **--如果没有源代码** 1、首先创建一个签名key file,可以通过有源代码的方式,创建key file备用;也可以通过VS 控制台执行``sn.exe -k D:\sn.snk`` 生成key。 2、将无源代码的dll,通过 ``ILDASM.exe MagicOrm.dll /OUTPUT=D:\MagicOrm.il``进行反汇编 **注意:如果dll有资源文件,同时会生成MagicOrm.res文件** 3、带上签名文件重新汇编为dll,如果有资源文件生成,也需要带上,命令:``ILASM.exe MagicOrm.il /dll /output=D:\MagicOrm.dll /Key=sn.snk /Resource=MagicOrm.res`` ## Over ================================================ FILE: PHP学习之路/01_PHP简易安装环境.md ================================================ --- title: 01_PHP简易安装环境 date: 2017/02/21 14:47:10 --- ## 0、导言 **PHP** 是啥,我想应该不用解释了吧。 最近发布的最新版本 ``PHP7`` ,提供之前版本的2倍速度提升,感觉很有吸引力哈。在看到2016年编程语言趋势和想到之前也想体验一下 ``PHP`` 的情况下,就说干就干,直接来简单学习下这门语言。 ## 1、PHP简易环境搭建 ### 1.1、PHP安装 ``PHP`` 的安装相当简单,打开 ``PHP`` 的下载地址:[http://php.net/downloads.php](http://php.net/downloads.php),可以看到它的版本下载。 我这里是 Windows 环境,就点击 【Windows downloads】 进入Windows版本的PHP下载地址: [http://windows.php.net/download#php-7.0](http://windows.php.net/download#php-7.0) 。在这里找到对应的版本下载即可。 **注意:请下载对应x86,x64的zip包,不要下载Debug Pack包。至于Non Thread Safe 与 Thread Safe,由于本人刚接触,不知道有什么区别,随意下载一个就行。** 我是Win10 x64版本,所以直接下载的:【VC14 x64 Non Thread Safe (2016-May-25 23:02:13)】(有最新版本下载最新版本即可)。 下载好之后,是一个压缩包。解压到目录中,在环境变量中配置 ``Path`` 为该目录。 打开 ``cmd`` 窗口,执行 ``php -v`` ,如果输出 ``PHP`` 的版本号,则表示安装成功! ### 1.2、IDE的选择 PHP有比较多IDE,这里推荐 [PhpStorm](https://www.jetbrains.com/phpstorm/) 和 [VsCode](https://code.visualstudio.com/)。 本人使用的 ``VsCode``,足够轻量。 ### 1.3、依赖管理工具 一个成熟的语言,一定会有很多现成的包,如C#的Nuget,Node的npm。在PHP中,也有同样的工具:Composer。 *如何在Windows下使用:Composer?* 首先,进入Composer下载地址:[https://getcomposer.org/download/](https://getcomposer.org/download/),找到【Composer-Setup.exe】,然后下载安装。 安装成功之后在控制台执行:``composer`` 会输出一系列命令,则证明安装成功。 然后就可以通过 ``composer install `` 来安装依赖包了。 想了解更多 ``composer`` 命令,请查询:[https://getcomposer.org/doc/](https://getcomposer.org/doc/)。 **注意,我在Windows中使用composer安装时,先使用了 ``composer config disable-tls true`` 和 ``composer config secure-http false`` 才得以成功安装依赖。** ### 1.4、Server程序 PHP自带有一个命令行的Server,用于开发测试已经足够使用了。所以,我直接使用了该Server。 只需要在php项目的根目录,打开cmd,执行 ``php -S localhost:9999`` 就可以启动一个PHP Server了。 想了解更多关于PHP自带的Web Server,请参考 [http://php.net/features.commandline.webserver](http://php.net/features.commandline.webserver) ## 2、Hello PHP 新建一个目录,创建 ``index.php``,输入以下内容: ```php Php Info ``` 打开控制台,使用 ``php -S localhost:9999`` 启动WebServer。 用浏览器访问 [http://localhost:9999](http://localhost:9999),就可以看到当前服务器的PHP环境信息了。 ## 3、Other 3.1、推荐资料: [PHP之道](http://laravel-china.github.io/php-the-right-way/) 3.2、PHP的编码问题,一般在php的页面上,我们都需要设置: ```php ``` 要想用Server运行含有该代码的PHP页面。需要特别配置一下 ``php.ini`` 文件。 在PHP的解压目录,找到 ``php.ini-development``,复制一份为 ``php.ini``,然后找到 ``extension_dir``,设置为:``extension_dir = "你的PHP解压目录\ext"``,然后找到 ``;extension=php_mbstring.dll`` 去掉前面的注释。 3.3、PHP框架推荐 * Yaf 官方框架,超高性能 http://www.laruence.com/manual/index.html http://php.net/manual/zh/yaf.installation.php * LazyPHP 超级简单的框架,建议读源码 https://github.com/easychen/LazyPHP * Slim 据说还不错 http://www.slimframework.com/ * Laravel 高人气框架 https://laravel.com/ https://lumen.laravel.com/ 专注API开发的PHP。 * ThinkPHP 中文 http://www.thinkphp.cn/ * InitPHP (A PHP Framework) - (from github) http://www.initphp.com/ * TinyMVC (from github) https://github.com/mohrt/tinymvc-php ================================================ FILE: PHP学习之路/02-PHP基础语法(上).md ================================================ --- title: 02-PHP基础语法(上) date: 2017/02/21 14:47:10 --- # 0、导言 学习一门语言,首先要了解它能做什么?其次,就应该去学习应该如何做。那这个的前提就是语法的学习。 语法决定了代码应该如何写(仅仅是可运行),接着我们就来看看PHP它的语法吧。 *注意:本文测试代码全部运行在PHP7上。* # 1、基础中的基础 1.1、 PHP文件以 ``.php`` 结尾,对于渲染HTML的PHP文件,其本质还是一个HTML页面,只要可以嵌入PHP逻辑代码。 1.2、 在前端 ``.php`` 文件中,要嵌入PHP代码,需要使用 <?php 你的代码 ?>。 1.3、PHP的每个语句以分号结束(部分场景省略分号也不报错)。 1.4、 PHP中,有两种注释方式。 ```php // 我是单行注释 /* 我是多行注释 */ ``` 1.5、PHP的输出,也有两种方式 一是 ``echo`` ``` echo 'abc', 'aaaa'; echo('abc', 'aaaa'); ``` 二是 ``print`` ``` print 'abc'; print('abc'); $result = print('abc'); ``` **注意:``echo``、``print``即是语言结构,也算是函数,所以可以不加括号调用,也可加括号调用。** **注意2:``echo`` 输出没有返回值,``print`` 有返回值1。** **注意3:``echo`` 输出比 ``print`` 快!** 1.6、数据的格式化输出,在输入时,我们可以用更简单的方法拼接字符串 ```php $name = 'Jay'; echo 'My name is {$name}'; echo "My name is {$name}"; print 'My name is {$name}'; print "My name is {$name}"; ``` 会输出: ```html My name is {$name} My name is Jay My name is {$name} My name is Jay ``` **!!!注意:只有当使用双引号("")包裹字符串的时候,才可以使用简易字符串拼接。** # 2、数据类型 PHP有个和大多数语言雷同的类型系统,系统提供了如下类型: 1. String(字符串) 2. Integer(整型) 3. Float(浮点型) 4. Boolean(布尔型) 5. Array(数组) 6. Object(对象) 7. NULL(空值)。 PHP中的变量命名以$为标记,之后跟变量名称(变量名字只能包含数字字母和下划线),变量区分大小写。 PHP是弱类型语言,所以同一个变量,可以存储多种类型数据。 ```php $a = '我是字符串'; //定义字符串(单引号,双引号皆可) echo $a, '
'; $a = 10; // 我的整数 echo $a, '
'; $a = 0x10; // 定义16进制整数 echo $a, '
'; $a = 010; // 定义8进制整数 echo $a, '
'; $a = 0.1; // 浮点数 echo $a, '
'; $a = 8E-5; // 指数形式定义浮点数 echo $a, '
'; $a = true; // Bool类型,只有true,false,注意不区分大小写,写成True,TrUe都没问题。 echo $a, '
'; $a = NuLL; // NULL类型只有一个null值,同样不区分大小写。 ``` 输出如下: ```html 我是字符串
10
16
8
0.1
8.0E-5
1
// 注意,Bool类型,true会输出1,false会输出0
// NULL类型,无任何输出 ``` 以上演示了PHP中的简单类型,还剩下Array和Object两个复杂类型。 **Array 类型** 数组又分为以下几种: 1. 简单数组(数值数组,下标为数字) ```php $arr = ['item1', 'item2']; // 等价于 $arr = array('item1', 'item2'); // 仅能通过下标访问元素 echo $arr[0]; ``` 2. 关联数组 ```php $arr = ['key1' => 'value1', 'key2' => 'value2']; //等价于 $arr = array('key1' => 'value1', 'key2' => 'value2'); // 仅能通过key访问 echo $arr['key1']; ``` 3. 多维数组(数组包含数组) ```php $arr = ['key1' => ['a', 'b'], 'key2' => ['c', 'd']]; //等价于 $arr = array('key1' => array('a', 'b'), 'key2' => array('c', 'd')); // 根据数组类型,通过key或者是下标访问 echo $arr['key1'][0]; ``` 数组Demo合集: ```php $arr = ['item1', 'item2']; print_r($arr); echo '
'; $arr = array('item1', 'item2'); print_r($arr); echo '
'; //仅能通过下标访问 echo '$arr第一个元素是:', $arr[0], '

'; $arr = ['key1' => 'value1', 'key2' => 'value2']; print_r($arr); echo '
'; $arr = array('key1' => 'value1', 'key2' => 'value2'); print_r($arr); echo '
'; // 通过key访问 echo '$arr的key1值是:', $arr['key1'], 'key2值是:', $arr['key2'], '

'; $arr = ['key1' => ['a', 'b'], 'key2' => ['c', 'd']]; print_r($arr); echo '
'; $arr = array('key1' => array('a', 'b'), 'key2' => array('c', 'd')); print_r($arr); echo '
'; // 根据数组类型,通过key或者是下标访问 echo $arr['key1'][0], $arr['key2'][1]; ``` **Object 类型** PHP中的Object类型,和编译性语言比较类似,是通过new class得到的。 ```php class User{ var $userName; function setName($name){ $this->userName = $name; } function getName(){ return $this->userName; } } $user = new User(); $user->setName('Jay'); echo $user->getName(); ``` 以上代码会输出:``Jay`` # 4、常量与变量 ## 4.1、常量 PHP中的常量必须使用 ``define`` 函数来定义。语法如下: ```php define(常量名称:string, 常量值, 是否区分大小写:bool-默认为false); //定义一个常量 define('PI', 3.1415926, true); ``` 常量值被定义后,在脚本的其他任何地方都不能被改变,且常量是全局可用的。 ## 4.2、变量 PHP的变量有如下几类: 1. local - 局部变量 2. global - 全局变量 3. static - 静态变量 4. parameter - 参数变量 ### 4.2.1、局部变量 定义在函数中的变量,被称之为局部变量,只在当前函数有效。 ```php function fun(){ $funName = 'fun1'; echo $funName; } fun(); echo $funName; // 出现警告:Undefined variable: funName ``` ### 4.2.2、全局变量 定义在函数外部的变量则是全局变量,如果要在函数内部使用,则需要使用global关键字。示例如下: ```php $appName = 'test'; function fun1(){ // 需要指定,当访问$appName时,是访问全局的$appName,否则会出现一个警告,未定义的变量。 global $appName; echo $appName; } fun1(); ``` ### 4.2.3、静态变量 局部变量,一般是执行完函数,即被释放。如果想保留该变量,那么就可以使用静态变量。 ```php function funs(){ $id = 1; static $static_id = 1; echo '$id = ', $id, ', $static_id=', $static_id, '
'; $id++; $static_id++; } funs(); funs(); ``` 输出结果为: ```html $id = 1, $static_id=1 $id = 1, $static_id=2 ``` 我们可以看到 ``$static_id`` 并没有被释放,一直有效。 **注意:静态变量本质上还是局部变量。** ### 4.2.4 参数变量 这个没啥好说的,函数参数中的变量,类似于局部变量。 ### 4.2.5 超级全局变量 什么是超级全局变量呢? 不需要特别定义,可直接在全局任何地方使用,是PHP预定义的全局变量。它们是: 1. $GLOBALS 2. $_SERVER 3. $_REQUEST 4. $_POST 5. $_GET 6. $_FILES 7. $_ENV 8. $_COOKIE 9. $_SESSION # 5、其他 完整Demo地址:[PHP语法演示Demo01](https://github.com/hstarorg/HstarDemoProject/blob/master/php_demo/04-grammar/01.php) 更多内容,请看下回分解。 ================================================ FILE: README.md ================================================ # HstarDoc markdown docs. Save my markdown blogs. # Table of Contents * [AngularJS相关](AngularJS%E7%9B%B8%E5%85%B3) * [Angular1.x升级指南.md](AngularJS%E7%9B%B8%E5%85%B3/Angular1.x%E5%8D%87%E7%BA%A7%E6%8C%87%E5%8D%97.md) * [AngularJS官方FAQ.md](AngularJS%E7%9B%B8%E5%85%B3/AngularJS%E5%AE%98%E6%96%B9FAQ.md) * [AngularJS教程:1W字综合指南.md](AngularJS%E7%9B%B8%E5%85%B3/AngularJS%E6%95%99%E7%A8%8B%EF%BC%9A1W%E5%AD%97%E7%BB%BC%E5%90%88%E6%8C%87%E5%8D%97.md) * [AngularJS:Looking under the hood.md](AngularJS%E7%9B%B8%E5%85%B3/AngularJS%EF%BC%9ALooking%20under%20the%20hood.md) * [Angular从0到1:function(上).md](AngularJS%E7%9B%B8%E5%85%B3/Angular%E4%BB%8E0%E5%88%B01%EF%BC%9Afunction%EF%BC%88%E4%B8%8A%EF%BC%89.md) * [Angular从0到1:function(下).md](AngularJS%E7%9B%B8%E5%85%B3/Angular%E4%BB%8E0%E5%88%B01%EF%BC%9Afunction%EF%BC%88%E4%B8%8B%EF%BC%89.md) * [Angular再回首(1)-Component组件.md](AngularJS%E7%9B%B8%E5%85%B3/Angular%E5%86%8D%E5%9B%9E%E9%A6%96(1)-Component%E7%BB%84%E4%BB%B6.md) * [Angular再回首(2)-那些容易忽略的Component细节.md](AngularJS%E7%9B%B8%E5%85%B3/Angular%E5%86%8D%E5%9B%9E%E9%A6%96(2)-%E9%82%A3%E4%BA%9B%E5%AE%B9%E6%98%93%E5%BF%BD%E7%95%A5%E7%9A%84Component%E7%BB%86%E8%8A%82.md) * [Angular再回首(3)-我们来实现一个组件.md](AngularJS%E7%9B%B8%E5%85%B3/Angular%E5%86%8D%E5%9B%9E%E9%A6%96(3)-%E6%88%91%E4%BB%AC%E6%9D%A5%E5%AE%9E%E7%8E%B0%E4%B8%80%E4%B8%AA%E7%BB%84%E4%BB%B6.md) * [Angular开发Tips.md](AngularJS%E7%9B%B8%E5%85%B3/Angular%E5%BC%80%E5%8F%91Tips.md) * [Angular:指令、Controller数据共享.md](AngularJS%E7%9B%B8%E5%85%B3/Angular%EF%BC%9A%E6%8C%87%E4%BB%A4%E3%80%81Controller%E6%95%B0%E6%8D%AE%E5%85%B1%E4%BA%AB.md) * [[20140917]Angular:如何编写一个指令.md](AngularJS%E7%9B%B8%E5%85%B3/%5B20140917%5DAngular%EF%BC%9A%E5%A6%82%E4%BD%95%E7%BC%96%E5%86%99%E4%B8%80%E4%B8%AA%E6%8C%87%E4%BB%A4.md) * [用AngularJS开发Web应用程序.md](AngularJS%E7%9B%B8%E5%85%B3/%E7%94%A8AngularJS%E5%BC%80%E5%8F%91Web%E5%BA%94%E7%94%A8%E7%A8%8B%E5%BA%8F.md) * [详解angular之$q.md](AngularJS%E7%9B%B8%E5%85%B3/%E8%AF%A6%E8%A7%A3angular%E4%B9%8B$q.md) * [Angular系列](Angular%E7%B3%BB%E5%88%97) * [01_Angular2初体验.md](Angular%E7%B3%BB%E5%88%97/01_Angular2%E5%88%9D%E4%BD%93%E9%AA%8C.md) * [02_Angular2组件生命周期.md](Angular%E7%B3%BB%E5%88%97/02_Angular2%E7%BB%84%E4%BB%B6%E7%94%9F%E5%91%BD%E5%91%A8%E6%9C%9F.md) * [03_Angular2的那些Decorator.md](Angular%E7%B3%BB%E5%88%97/03_Angular2%E7%9A%84%E9%82%A3%E4%BA%9BDecorator.md) * [04_Angular2指令简析.md](Angular%E7%B3%BB%E5%88%97/04_Angular2%E6%8C%87%E4%BB%A4%E7%AE%80%E6%9E%90.md) * [05_Angular2组件简析.md](Angular%E7%B3%BB%E5%88%97/05_Angular2%E7%BB%84%E4%BB%B6%E7%AE%80%E6%9E%90.md) * [06_Angular2管道(Pipe)简析.md](Angular%E7%B3%BB%E5%88%97/06_Angular2%E7%AE%A1%E9%81%93%EF%BC%88Pipe%EF%BC%89%E7%AE%80%E6%9E%90.md) * [07_Angular2使用路由.md](Angular%E7%B3%BB%E5%88%97/07_Angular2%E4%BD%BF%E7%94%A8%E8%B7%AF%E7%94%B1.md) * [08_Angular2动态加载组件.md](Angular%E7%B3%BB%E5%88%97/08_Angular2%E5%8A%A8%E6%80%81%E5%8A%A0%E8%BD%BD%E7%BB%84%E4%BB%B6.md) * [09_Angular2使用ui-router-ng2.md](Angular%E7%B3%BB%E5%88%97/09_Angular2%E4%BD%BF%E7%94%A8ui-router-ng2.md) * [Angular2踩坑大全.md](Angular%E7%B3%BB%E5%88%97/Angular2%E8%B8%A9%E5%9D%91%E5%A4%A7%E5%85%A8.md) * [利用Angular实现多团队模块化SPA开发框架.md](Angular%E7%B3%BB%E5%88%97/%E5%88%A9%E7%94%A8Angular%E5%AE%9E%E7%8E%B0%E5%A4%9A%E5%9B%A2%E9%98%9F%E6%A8%A1%E5%9D%97%E5%8C%96SPA%E5%BC%80%E5%8F%91%E6%A1%86%E6%9E%B6.md) * [跟我学Angular2(1-初体验).md](Angular%E7%B3%BB%E5%88%97/%E8%B7%9F%E6%88%91%E5%AD%A6Angular2%EF%BC%881-%E5%88%9D%E4%BD%93%E9%AA%8C%EF%BC%89.md) * [C#](C#) * [01_Dotnet Core尝鲜.md](C#/01_Dotnet%20Core%E5%B0%9D%E9%B2%9C.md) * [02_Dotnet Core V2.md](C#/02_Dotnet%20Core%20V2.md) * [C#中处理耗时任务的几种方式.md](C#/C#%E4%B8%AD%E5%A4%84%E7%90%86%E8%80%97%E6%97%B6%E4%BB%BB%E5%8A%A1%E7%9A%84%E5%87%A0%E7%A7%8D%E6%96%B9%E5%BC%8F.md) * [[20140913]可替代反射的几种方式.md](C#/%5B20140913%5D%E5%8F%AF%E6%9B%BF%E4%BB%A3%E5%8F%8D%E5%B0%84%E7%9A%84%E5%87%A0%E7%A7%8D%E6%96%B9%E5%BC%8F.md) * [实战Asp.Net Core:DI生命周期.md](C#/%E5%AE%9E%E6%88%98Asp.Net%20Core%EF%BC%9ADI%E7%94%9F%E5%91%BD%E5%91%A8%E6%9C%9F.md) * [实战Asp.Net Core:中间件.md](C#/%E5%AE%9E%E6%88%98Asp.Net%20Core%EF%BC%9A%E4%B8%AD%E9%97%B4%E4%BB%B6.md) * [实战Asp.Net Core:部署应用.md](C#/%E5%AE%9E%E6%88%98Asp.Net%20Core%EF%BC%9A%E9%83%A8%E7%BD%B2%E5%BA%94%E7%94%A8.md) * [Canvas学习札记](Canvas%E5%AD%A6%E4%B9%A0%E6%9C%AD%E8%AE%B0) * [01_初识Canvas,绘制简单图形.md](Canvas%E5%AD%A6%E4%B9%A0%E6%9C%AD%E8%AE%B0/01_%E5%88%9D%E8%AF%86Canvas%EF%BC%8C%E7%BB%98%E5%88%B6%E7%AE%80%E5%8D%95%E5%9B%BE%E5%BD%A2.md) * [CSS3学习之路](CSS3%E5%AD%A6%E4%B9%A0%E4%B9%8B%E8%B7%AF) * [CSS3入门之文本与字体.md](CSS3%E5%AD%A6%E4%B9%A0%E4%B9%8B%E8%B7%AF/CSS3%E5%85%A5%E9%97%A8%E4%B9%8B%E6%96%87%E6%9C%AC%E4%B8%8E%E5%AD%97%E4%BD%93.md) * [CSS3入门之转换.md](CSS3%E5%AD%A6%E4%B9%A0%E4%B9%8B%E8%B7%AF/CSS3%E5%85%A5%E9%97%A8%E4%B9%8B%E8%BD%AC%E6%8D%A2.md) * [CSS3入门之边框与背景.md](CSS3%E5%AD%A6%E4%B9%A0%E4%B9%8B%E8%B7%AF/CSS3%E5%85%A5%E9%97%A8%E4%B9%8B%E8%BE%B9%E6%A1%86%E4%B8%8E%E8%83%8C%E6%99%AF.md) * [ES6入门](ES6%E5%85%A5%E9%97%A8) * [ES6入门系列一(基础).md](ES6%E5%85%A5%E9%97%A8/ES6%E5%85%A5%E9%97%A8%E7%B3%BB%E5%88%97%E4%B8%80%EF%BC%88%E5%9F%BA%E7%A1%80%EF%BC%89.md) * [ES6入门系列三(特性总览下).md](ES6%E5%85%A5%E9%97%A8/ES6%E5%85%A5%E9%97%A8%E7%B3%BB%E5%88%97%E4%B8%89%EF%BC%88%E7%89%B9%E6%80%A7%E6%80%BB%E8%A7%88%E4%B8%8B%EF%BC%89.md) * [ES6入门系列二(特性总览上).md](ES6%E5%85%A5%E9%97%A8/ES6%E5%85%A5%E9%97%A8%E7%B3%BB%E5%88%97%E4%BA%8C%EF%BC%88%E7%89%B9%E6%80%A7%E6%80%BB%E8%A7%88%E4%B8%8A%EF%BC%89.md) * [ES6入门系列四(测试题分析).md](ES6%E5%85%A5%E9%97%A8/ES6%E5%85%A5%E9%97%A8%E7%B3%BB%E5%88%97%E5%9B%9B%EF%BC%88%E6%B5%8B%E8%AF%95%E9%A2%98%E5%88%86%E6%9E%90%EF%BC%89.md) * [GoLang学习笔记](GoLang%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0) * [01_开始GO.md](GoLang%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/01_%E5%BC%80%E5%A7%8BGO.md) * [jQuery拆解](jQuery%E6%8B%86%E8%A7%A3) * [01-目录篇.md](jQuery%E6%8B%86%E8%A7%A3/01-%E7%9B%AE%E5%BD%95%E7%AF%87.md) * [02-模块化加载&防冲突处理.md](jQuery%E6%8B%86%E8%A7%A3/02-%E6%A8%A1%E5%9D%97%E5%8C%96%E5%8A%A0%E8%BD%BD&%E9%98%B2%E5%86%B2%E7%AA%81%E5%A4%84%E7%90%86.md) * [03-基础结构.md](jQuery%E6%8B%86%E8%A7%A3/03-%E5%9F%BA%E7%A1%80%E7%BB%93%E6%9E%84.md) * [jQuery中那些有趣的代码.md](jQuery%E6%8B%86%E8%A7%A3/jQuery%E4%B8%AD%E9%82%A3%E4%BA%9B%E6%9C%89%E8%B6%A3%E7%9A%84%E4%BB%A3%E7%A0%81.md) * [JS札记](JS%E6%9C%AD%E8%AE%B0) * [ES6 Class如何管理私有数据.md](JS%E6%9C%AD%E8%AE%B0/ES6%20Class%E5%A6%82%E4%BD%95%E7%AE%A1%E7%90%86%E7%A7%81%E6%9C%89%E6%95%B0%E6%8D%AE.md) * [JavaScript之毒瘤.md](JS%E6%9C%AD%E8%AE%B0/JavaScript%E4%B9%8B%E6%AF%92%E7%98%A4.md) * [JavaScript之糟粕.md](JS%E6%9C%AD%E8%AE%B0/JavaScript%E4%B9%8B%E7%B3%9F%E7%B2%95.md) * [JavaScript的深拷贝的实现.md](JS%E6%9C%AD%E8%AE%B0/JavaScript%E7%9A%84%E6%B7%B1%E6%8B%B7%E8%B4%9D%E7%9A%84%E5%AE%9E%E7%8E%B0.md) * [JS实现继承的几种方式.md](JS%E6%9C%AD%E8%AE%B0/JS%E5%AE%9E%E7%8E%B0%E7%BB%A7%E6%89%BF%E7%9A%84%E5%87%A0%E7%A7%8D%E6%96%B9%E5%BC%8F.md) * [[20141121]JavaScript之Array常用功能汇总.md](JS%E6%9C%AD%E8%AE%B0/%5B20141121%5DJavaScript%E4%B9%8BArray%E5%B8%B8%E7%94%A8%E5%8A%9F%E8%83%BD%E6%B1%87%E6%80%BB.md) * [那些不常见的JavaScript题目(上).md](JS%E6%9C%AD%E8%AE%B0/%E9%82%A3%E4%BA%9B%E4%B8%8D%E5%B8%B8%E8%A7%81%E7%9A%84JavaScript%E9%A2%98%E7%9B%AE%EF%BC%88%E4%B8%8A%EF%BC%89.md) * [那些不常见的JavaScript题目(下).md](JS%E6%9C%AD%E8%AE%B0/%E9%82%A3%E4%BA%9B%E4%B8%8D%E5%B8%B8%E8%A7%81%E7%9A%84JavaScript%E9%A2%98%E7%9B%AE%EF%BC%88%E4%B8%8B%EF%BC%89.md) * [MongoDB入门基础](MongoDB%E5%85%A5%E9%97%A8%E5%9F%BA%E7%A1%80) * [01_记一次MongoDB裸奔.md](MongoDB%E5%85%A5%E9%97%A8%E5%9F%BA%E7%A1%80/01_%E8%AE%B0%E4%B8%80%E6%AC%A1MongoDB%E8%A3%B8%E5%A5%94.md) * [02_Mongo权限探索.md](MongoDB%E5%85%A5%E9%97%A8%E5%9F%BA%E7%A1%80/02_Mongo%E6%9D%83%E9%99%90%E6%8E%A2%E7%B4%A2.md) * [Other](Other) * [Go Go.md](Other/Go%20Go.md) * [NPM使用详解(上).md](Other/NPM%E4%BD%BF%E7%94%A8%E8%AF%A6%E8%A7%A3%EF%BC%88%E4%B8%8A%EF%BC%89.md) * [NPM使用详解(下).md](Other/NPM%E4%BD%BF%E7%94%A8%E8%AF%A6%E8%A7%A3%EF%BC%88%E4%B8%8B%EF%BC%89.md) * [Thrift简单实践.md](Other/Thrift%E7%AE%80%E5%8D%95%E5%AE%9E%E8%B7%B5.md) * [TypeScript札记:初体验.md](Other/TypeScript%E6%9C%AD%E8%AE%B0%EF%BC%9A%E5%88%9D%E4%BD%93%E9%AA%8C.md) * [Windows下把Nginx,PM2包装为服务.md](Other/Windows%E4%B8%8B%E6%8A%8ANginx%EF%BC%8CPM2%E5%8C%85%E8%A3%85%E4%B8%BA%E6%9C%8D%E5%8A%A1.md) * [开发者讨厌你API的十个原因.md](Other/%E5%BC%80%E5%8F%91%E8%80%85%E8%AE%A8%E5%8E%8C%E4%BD%A0API%E7%9A%84%E5%8D%81%E4%B8%AA%E5%8E%9F%E5%9B%A0.md) * [浅析12306前端优化点.md](Other/%E6%B5%85%E6%9E%9012306%E5%89%8D%E7%AB%AF%E4%BC%98%E5%8C%96%E7%82%B9.md) * [程序集强签名.md](Other/%E7%A8%8B%E5%BA%8F%E9%9B%86%E5%BC%BA%E7%AD%BE%E5%90%8D.md) * [PHP学习之路](PHP%E5%AD%A6%E4%B9%A0%E4%B9%8B%E8%B7%AF) * [01_PHP简易安装环境.md](PHP%E5%AD%A6%E4%B9%A0%E4%B9%8B%E8%B7%AF/01_PHP%E7%AE%80%E6%98%93%E5%AE%89%E8%A3%85%E7%8E%AF%E5%A2%83.md) * [02-PHP基础语法(上).md](PHP%E5%AD%A6%E4%B9%A0%E4%B9%8B%E8%B7%AF/02-PHP%E5%9F%BA%E7%A1%80%E8%AF%AD%E6%B3%95%EF%BC%88%E4%B8%8A%EF%BC%89.md) * [React Native Cookbook](React%20Native%20Cookbook) * [章节一-new.md](React%20Native%20Cookbook/%E7%AB%A0%E8%8A%82%E4%B8%80-new.md) * [章节一.md](React%20Native%20Cookbook/%E7%AB%A0%E8%8A%82%E4%B8%80.md) * [React Native 开发笔记](React%20Native%20%E5%BC%80%E5%8F%91%E7%AC%94%E8%AE%B0) * [React Native开发之多屏适配.md](React%20Native%20%E5%BC%80%E5%8F%91%E7%AC%94%E8%AE%B0/React%20Native%E5%BC%80%E5%8F%91%E4%B9%8B%E5%A4%9A%E5%B1%8F%E9%80%82%E9%85%8D.md) * [RN Aspect-01-环境准备.md](React%20Native%20%E5%BC%80%E5%8F%91%E7%AC%94%E8%AE%B0/RN%20Aspect-01-%E7%8E%AF%E5%A2%83%E5%87%86%E5%A4%87.md) * [RN Aspect-02-Hello React Native.md](React%20Native%20%E5%BC%80%E5%8F%91%E7%AC%94%E8%AE%B0/RN%20Aspect-02-Hello%20React%20Native.md) * [RN Aspect-03-修改名称与icon.md](React%20Native%20%E5%BC%80%E5%8F%91%E7%AC%94%E8%AE%B0/RN%20Aspect-03-%E4%BF%AE%E6%94%B9%E5%90%8D%E7%A7%B0%E4%B8%8Eicon.md) * [RN Aspect-04-打包App.md](React%20Native%20%E5%BC%80%E5%8F%91%E7%AC%94%E8%AE%B0/RN%20Aspect-04-%E6%89%93%E5%8C%85App.md) * [React面面观](React%E9%9D%A2%E9%9D%A2%E8%A7%82) * [JSX中的那些小细节.md](React%E9%9D%A2%E9%9D%A2%E8%A7%82/JSX%E4%B8%AD%E7%9A%84%E9%82%A3%E4%BA%9B%E5%B0%8F%E7%BB%86%E8%8A%82.md) * [sources](React%E9%9D%A2%E9%9D%A2%E8%A7%82/sources) * [【译】参考手册-React组件.md](React%E9%9D%A2%E9%9D%A2%E8%A7%82/%E3%80%90%E8%AF%91%E3%80%91%E5%8F%82%E8%80%83%E6%89%8B%E5%86%8C-React%E7%BB%84%E4%BB%B6.md) * [【译】快速起步-JSX简介.md](React%E9%9D%A2%E9%9D%A2%E8%A7%82/%E3%80%90%E8%AF%91%E3%80%91%E5%BF%AB%E9%80%9F%E8%B5%B7%E6%AD%A5-JSX%E7%AE%80%E4%BB%8B.md) * [【译】快速起步-事件处理.md](React%E9%9D%A2%E9%9D%A2%E8%A7%82/%E3%80%90%E8%AF%91%E3%80%91%E5%BF%AB%E9%80%9F%E8%B5%B7%E6%AD%A5-%E4%BA%8B%E4%BB%B6%E5%A4%84%E7%90%86.md) * [【译】快速起步-列表与KEY.md](React%E9%9D%A2%E9%9D%A2%E8%A7%82/%E3%80%90%E8%AF%91%E3%80%91%E5%BF%AB%E9%80%9F%E8%B5%B7%E6%AD%A5-%E5%88%97%E8%A1%A8%E4%B8%8EKEY.md) * [【译】快速起步-条件渲染.md](React%E9%9D%A2%E9%9D%A2%E8%A7%82/%E3%80%90%E8%AF%91%E3%80%91%E5%BF%AB%E9%80%9F%E8%B5%B7%E6%AD%A5-%E6%9D%A1%E4%BB%B6%E6%B8%B2%E6%9F%93.md) * [【译】快速起步-渲染元素.md](React%E9%9D%A2%E9%9D%A2%E8%A7%82/%E3%80%90%E8%AF%91%E3%80%91%E5%BF%AB%E9%80%9F%E8%B5%B7%E6%AD%A5-%E6%B8%B2%E6%9F%93%E5%85%83%E7%B4%A0.md) * [【译】快速起步-状态和生命周期.md](React%E9%9D%A2%E9%9D%A2%E8%A7%82/%E3%80%90%E8%AF%91%E3%80%91%E5%BF%AB%E9%80%9F%E8%B5%B7%E6%AD%A5-%E7%8A%B6%E6%80%81%E5%92%8C%E7%94%9F%E5%91%BD%E5%91%A8%E6%9C%9F.md) * [【译】快速起步-状态提升.md](React%E9%9D%A2%E9%9D%A2%E8%A7%82/%E3%80%90%E8%AF%91%E3%80%91%E5%BF%AB%E9%80%9F%E8%B5%B7%E6%AD%A5-%E7%8A%B6%E6%80%81%E6%8F%90%E5%8D%87.md) * [【译】快速起步-组件与属性.md](React%E9%9D%A2%E9%9D%A2%E8%A7%82/%E3%80%90%E8%AF%91%E3%80%91%E5%BF%AB%E9%80%9F%E8%B5%B7%E6%AD%A5-%E7%BB%84%E4%BB%B6%E4%B8%8E%E5%B1%9E%E6%80%A7.md) * [【译】快速起步-组合与继承.md](React%E9%9D%A2%E9%9D%A2%E8%A7%82/%E3%80%90%E8%AF%91%E3%80%91%E5%BF%AB%E9%80%9F%E8%B5%B7%E6%AD%A5-%E7%BB%84%E5%90%88%E4%B8%8E%E7%BB%A7%E6%89%BF.md) * [【译】快速起步-表单.md](React%E9%9D%A2%E9%9D%A2%E8%A7%82/%E3%80%90%E8%AF%91%E3%80%91%E5%BF%AB%E9%80%9F%E8%B5%B7%E6%AD%A5-%E8%A1%A8%E5%8D%95.md) * [【译】高级指南-不受控组件.md](React%E9%9D%A2%E9%9D%A2%E8%A7%82/%E3%80%90%E8%AF%91%E3%80%91%E9%AB%98%E7%BA%A7%E6%8C%87%E5%8D%97-%E4%B8%8D%E5%8F%97%E6%8E%A7%E7%BB%84%E4%BB%B6.md) * [【译】高级指南-深入JSX.md](React%E9%9D%A2%E9%9D%A2%E8%A7%82/%E3%80%90%E8%AF%91%E3%80%91%E9%AB%98%E7%BA%A7%E6%8C%87%E5%8D%97-%E6%B7%B1%E5%85%A5JSX.md) * [【译】高级指南-高阶组件.md](React%E9%9D%A2%E9%9D%A2%E8%A7%82/%E3%80%90%E8%AF%91%E3%80%91%E9%AB%98%E7%BA%A7%E6%8C%87%E5%8D%97-%E9%AB%98%E9%98%B6%E7%BB%84%E4%BB%B6.md) * [README.md](README.md) * [RxJS小记](RxJS%E5%B0%8F%E8%AE%B0) * [02_RxJS之Observable.md](RxJS%E5%B0%8F%E8%AE%B0/02_RxJS%E4%B9%8BObservable.md) * [Sass学习之路](Sass%E5%AD%A6%E4%B9%A0%E4%B9%8B%E8%B7%AF) * [01_Sass学习之路:Sass、Compass安装与命令行.md](Sass%E5%AD%A6%E4%B9%A0%E4%B9%8B%E8%B7%AF/01_Sass%E5%AD%A6%E4%B9%A0%E4%B9%8B%E8%B7%AF%EF%BC%9ASass%E3%80%81Compass%E5%AE%89%E8%A3%85%E4%B8%8E%E5%91%BD%E4%BB%A4%E8%A1%8C.md) * [02_Sass学习之路:注释、变量以及导入.md](Sass%E5%AD%A6%E4%B9%A0%E4%B9%8B%E8%B7%AF/02_Sass%E5%AD%A6%E4%B9%A0%E4%B9%8B%E8%B7%AF%EF%BC%9A%E6%B3%A8%E9%87%8A%E3%80%81%E5%8F%98%E9%87%8F%E4%BB%A5%E5%8F%8A%E5%AF%BC%E5%85%A5.md) * [Vue实践之路](Vue%E5%AE%9E%E8%B7%B5%E4%B9%8B%E8%B7%AF) * [01_认识Vue.md](Vue%E5%AE%9E%E8%B7%B5%E4%B9%8B%E8%B7%AF/01_%E8%AE%A4%E8%AF%86Vue.md) * [02_Vue组件(上).md](Vue%E5%AE%9E%E8%B7%B5%E4%B9%8B%E8%B7%AF/02_Vue%E7%BB%84%E4%BB%B6%EF%BC%88%E4%B8%8A%EF%BC%89.md) * [_images](_images) * [从0开始Stylus](%E4%BB%8E0%E5%BC%80%E5%A7%8BStylus) * [01_Stylus简介&基本使用.md](%E4%BB%8E0%E5%BC%80%E5%A7%8BStylus/01_Stylus%E7%AE%80%E4%BB%8B&%E5%9F%BA%E6%9C%AC%E4%BD%BF%E7%94%A8.md) * [从零开始H5](%E4%BB%8E%E9%9B%B6%E5%BC%80%E5%A7%8BH5) * [HTML5探索一(那些新增的标签和属性).md](%E4%BB%8E%E9%9B%B6%E5%BC%80%E5%A7%8BH5/HTML5%E6%8E%A2%E7%B4%A2%E4%B8%80%EF%BC%88%E9%82%A3%E4%BA%9B%E6%96%B0%E5%A2%9E%E7%9A%84%E6%A0%87%E7%AD%BE%E5%92%8C%E5%B1%9E%E6%80%A7%EF%BC%89.md) * [从零开始H5(一):升级你的HTML到HTML5.md](%E4%BB%8E%E9%9B%B6%E5%BC%80%E5%A7%8BH5/%E4%BB%8E%E9%9B%B6%E5%BC%80%E5%A7%8BH5%EF%BC%88%E4%B8%80%EF%BC%89%EF%BC%9A%E5%8D%87%E7%BA%A7%E4%BD%A0%E7%9A%84HTML%E5%88%B0HTML5.md) * [从零开始H5(二):HTML5新技术点.md](%E4%BB%8E%E9%9B%B6%E5%BC%80%E5%A7%8BH5/%E4%BB%8E%E9%9B%B6%E5%BC%80%E5%A7%8BH5%EF%BC%88%E4%BA%8C%EF%BC%89%EF%BC%9AHTML5%E6%96%B0%E6%8A%80%E6%9C%AF%E7%82%B9.md) * [前端相关](%E5%89%8D%E7%AB%AF%E7%9B%B8%E5%85%B3) * [CORS详解.md](%E5%89%8D%E7%AB%AF%E7%9B%B8%E5%85%B3/CORS%E8%AF%A6%E8%A7%A3.md) * [CSS布局(上).md](%E5%89%8D%E7%AB%AF%E7%9B%B8%E5%85%B3/CSS%E5%B8%83%E5%B1%80%EF%BC%88%E4%B8%8A%EF%BC%89.md) * [CSS布局(下).md](%E5%89%8D%E7%AB%AF%E7%9B%B8%E5%85%B3/CSS%E5%B8%83%E5%B1%80%EF%BC%88%E4%B8%8B%EF%BC%89.md) * [Google JavaScript Style Guide(上).md](%E5%89%8D%E7%AB%AF%E7%9B%B8%E5%85%B3/Google%20JavaScript%20Style%20Guide%EF%BC%88%E4%B8%8A%EF%BC%89.md) * [Iframe跨域通信的几种方式.md](%E5%89%8D%E7%AB%AF%E7%9B%B8%E5%85%B3/Iframe%E8%B7%A8%E5%9F%9F%E9%80%9A%E4%BF%A1%E7%9A%84%E5%87%A0%E7%A7%8D%E6%96%B9%E5%BC%8F.md) * [JSONP详解.md](%E5%89%8D%E7%AB%AF%E7%9B%B8%E5%85%B3/JSONP%E8%AF%A6%E8%A7%A3.md) * [JWT详解.md](%E5%89%8D%E7%AB%AF%E7%9B%B8%E5%85%B3/JWT%E8%AF%A6%E8%A7%A3.md) * [Nginx常规用法解析.md](%E5%89%8D%E7%AB%AF%E7%9B%B8%E5%85%B3/Nginx%E5%B8%B8%E8%A7%84%E7%94%A8%E6%B3%95%E8%A7%A3%E6%9E%90.md) * [TypeScript配置文件tsconfig简析.md](%E5%89%8D%E7%AB%AF%E7%9B%B8%E5%85%B3/TypeScript%E9%85%8D%E7%BD%AE%E6%96%87%E4%BB%B6tsconfig%E7%AE%80%E6%9E%90.md) * [VsCode简易配置手册.md](%E5%89%8D%E7%AB%AF%E7%9B%B8%E5%85%B3/VsCode%E7%AE%80%E6%98%93%E9%85%8D%E7%BD%AE%E6%89%8B%E5%86%8C.md) * [Web API接口之FileReader.md](%E5%89%8D%E7%AB%AF%E7%9B%B8%E5%85%B3/Web%20API%E6%8E%A5%E5%8F%A3%E4%B9%8BFileReader.md) * [Web API接口之Geolocation.md](%E5%89%8D%E7%AB%AF%E7%9B%B8%E5%85%B3/Web%20API%E6%8E%A5%E5%8F%A3%E4%B9%8BGeolocation.md) * [Webpack In Angular2.md](%E5%89%8D%E7%AB%AF%E7%9B%B8%E5%85%B3/Webpack%20In%20Angular2.md) * [Webpack初体验.md](%E5%89%8D%E7%AB%AF%E7%9B%B8%E5%85%B3/Webpack%E5%88%9D%E4%BD%93%E9%AA%8C.md) * [Webpack小抄.md](%E5%89%8D%E7%AB%AF%E7%9B%B8%E5%85%B3/Webpack%E5%B0%8F%E6%8A%84.md) * [Web前端基础测试题.md](%E5%89%8D%E7%AB%AF%E7%9B%B8%E5%85%B3/Web%E5%89%8D%E7%AB%AF%E5%9F%BA%E7%A1%80%E6%B5%8B%E8%AF%95%E9%A2%98.md) * [Yarn vs. Npm.md](%E5%89%8D%E7%AB%AF%E7%9B%B8%E5%85%B3/Yarn%20vs.%20Npm.md) * [[20140311]前端构建之gulp与常用插件.md](%E5%89%8D%E7%AB%AF%E7%9B%B8%E5%85%B3/%5B20140311%5D%E5%89%8D%E7%AB%AF%E6%9E%84%E5%BB%BA%E4%B9%8Bgulp%E4%B8%8E%E5%B8%B8%E7%94%A8%E6%8F%92%E4%BB%B6.md) * [[20141025]从0开始Grunt.md](%E5%89%8D%E7%AB%AF%E7%9B%B8%E5%85%B3/%5B20141025%5D%E4%BB%8E0%E5%BC%80%E5%A7%8BGrunt.md) * [[20150107]Web离线存储的几种方式.md](%E5%89%8D%E7%AB%AF%E7%9B%B8%E5%85%B3/%5B20150107%5DWeb%E7%A6%BB%E7%BA%BF%E5%AD%98%E5%82%A8%E7%9A%84%E5%87%A0%E7%A7%8D%E6%96%B9%E5%BC%8F.md) * [一个元素实现3个回图形.md](%E5%89%8D%E7%AB%AF%E7%9B%B8%E5%85%B3/%E4%B8%80%E4%B8%AA%E5%85%83%E7%B4%A0%E5%AE%9E%E7%8E%B03%E4%B8%AA%E5%9B%9E%E5%9B%BE%E5%BD%A2.md) * [再说Promise.md](%E5%89%8D%E7%AB%AF%E7%9B%B8%E5%85%B3/%E5%86%8D%E8%AF%B4Promise.md) * [前端模块化:RequireJS.md](%E5%89%8D%E7%AB%AF%E7%9B%B8%E5%85%B3/%E5%89%8D%E7%AB%AF%E6%A8%A1%E5%9D%97%E5%8C%96%EF%BC%9ARequireJS.md) * [如何用Node编写命令行工具.md](%E5%89%8D%E7%AB%AF%E7%9B%B8%E5%85%B3/%E5%A6%82%E4%BD%95%E7%94%A8Node%E7%BC%96%E5%86%99%E5%91%BD%E4%BB%A4%E8%A1%8C%E5%B7%A5%E5%85%B7.md) * [探索Decorator.md](%E5%89%8D%E7%AB%AF%E7%9B%B8%E5%85%B3/%E6%8E%A2%E7%B4%A2Decorator.md) * [浏览器 Pointer Events.md](%E5%89%8D%E7%AB%AF%E7%9B%B8%E5%85%B3/%E6%B5%8F%E8%A7%88%E5%99%A8%20Pointer%20Events.md) * [浏览器关闭事件分析.md](%E5%89%8D%E7%AB%AF%E7%9B%B8%E5%85%B3/%E6%B5%8F%E8%A7%88%E5%99%A8%E5%85%B3%E9%97%AD%E4%BA%8B%E4%BB%B6%E5%88%86%E6%9E%90.md) * [浏览器内容安全策略解析.md](%E5%89%8D%E7%AB%AF%E7%9B%B8%E5%85%B3/%E6%B5%8F%E8%A7%88%E5%99%A8%E5%86%85%E5%AE%B9%E5%AE%89%E5%85%A8%E7%AD%96%E7%95%A5%E8%A7%A3%E6%9E%90.md) * [浏览器历史history对象.md](%E5%89%8D%E7%AB%AF%E7%9B%B8%E5%85%B3/%E6%B5%8F%E8%A7%88%E5%99%A8%E5%8E%86%E5%8F%B2history%E5%AF%B9%E8%B1%A1.md) * [简单学ES6.md](%E5%89%8D%E7%AB%AF%E7%9B%B8%E5%85%B3/%E7%AE%80%E5%8D%95%E5%AD%A6ES6.md) * [认识AMD、CMD、UMD、CommonJS.md](%E5%89%8D%E7%AB%AF%E7%9B%B8%E5%85%B3/%E8%AE%A4%E8%AF%86AMD%E3%80%81CMD%E3%80%81UMD%E3%80%81CommonJS.md) * [记一次Bug排查(Spider).md](%E5%89%8D%E7%AB%AF%E7%9B%B8%E5%85%B3/%E8%AE%B0%E4%B8%80%E6%AC%A1Bug%E6%8E%92%E6%9F%A5%EF%BC%88Spider%EF%BC%89.md) * [说说如何部署node程序.md](%E5%89%8D%E7%AB%AF%E7%9B%B8%E5%85%B3/%E8%AF%B4%E8%AF%B4%E5%A6%82%E4%BD%95%E9%83%A8%E7%BD%B2node%E7%A8%8B%E5%BA%8F.md) * [这些年我们处理过的跨域.md](%E5%89%8D%E7%AB%AF%E7%9B%B8%E5%85%B3/%E8%BF%99%E4%BA%9B%E5%B9%B4%E6%88%91%E4%BB%AC%E5%A4%84%E7%90%86%E8%BF%87%E7%9A%84%E8%B7%A8%E5%9F%9F.md) * [那些容易出错的Dom操作.md](%E5%89%8D%E7%AB%AF%E7%9B%B8%E5%85%B3/%E9%82%A3%E4%BA%9B%E5%AE%B9%E6%98%93%E5%87%BA%E9%94%99%E7%9A%84Dom%E6%93%8D%E4%BD%9C.md) * [那些年我们认识的iframe.md](%E5%89%8D%E7%AB%AF%E7%9B%B8%E5%85%B3/%E9%82%A3%E4%BA%9B%E5%B9%B4%E6%88%91%E4%BB%AC%E8%AE%A4%E8%AF%86%E7%9A%84iframe.md) * [微信小程序](%E5%BE%AE%E4%BF%A1%E5%B0%8F%E7%A8%8B%E5%BA%8F) * [数据库之路](%E6%95%B0%E6%8D%AE%E5%BA%93%E4%B9%8B%E8%B7%AF) * [[20141114]这些年你需要注意的SQL.md](%E6%95%B0%E6%8D%AE%E5%BA%93%E4%B9%8B%E8%B7%AF/%5B20141114%5D%E8%BF%99%E4%BA%9B%E5%B9%B4%E4%BD%A0%E9%9C%80%E8%A6%81%E6%B3%A8%E6%84%8F%E7%9A%84SQL.md) * [说说你所熟知的MSSQL中的substring函数.md](%E6%95%B0%E6%8D%AE%E5%BA%93%E4%B9%8B%E8%B7%AF/%E8%AF%B4%E8%AF%B4%E4%BD%A0%E6%89%80%E7%86%9F%E7%9F%A5%E7%9A%84MSSQL%E4%B8%AD%E7%9A%84substring%E5%87%BD%E6%95%B0.md) * [最佳实践系列](%E6%9C%80%E4%BD%B3%E5%AE%9E%E8%B7%B5%E7%B3%BB%E5%88%97) * [Express异步进化史.md](%E6%9C%80%E4%BD%B3%E5%AE%9E%E8%B7%B5%E7%B3%BB%E5%88%97/Express%E5%BC%82%E6%AD%A5%E8%BF%9B%E5%8C%96%E5%8F%B2.md) * [正则表达式](%E6%AD%A3%E5%88%99%E8%A1%A8%E8%BE%BE%E5%BC%8F) * [你真的理解正则修饰符吗.md](%E6%AD%A3%E5%88%99%E8%A1%A8%E8%BE%BE%E5%BC%8F/%E4%BD%A0%E7%9C%9F%E7%9A%84%E7%90%86%E8%A7%A3%E6%AD%A3%E5%88%99%E4%BF%AE%E9%A5%B0%E7%AC%A6%E5%90%97.md) * [测试相关](%E6%B5%8B%E8%AF%95%E7%9B%B8%E5%85%B3) * [MOCHA测试代码汇总.md](%E6%B5%8B%E8%AF%95%E7%9B%B8%E5%85%B3/MOCHA%E6%B5%8B%E8%AF%95%E4%BB%A3%E7%A0%81%E6%B1%87%E6%80%BB.md) * [使用chai-http实现API测试.md](%E6%B5%8B%E8%AF%95%E7%9B%B8%E5%85%B3/%E4%BD%BF%E7%94%A8chai-http%E5%AE%9E%E7%8E%B0API%E6%B5%8B%E8%AF%95.md) * [利用Karma、Mocha搭建测试环境.md](%E6%B5%8B%E8%AF%95%E7%9B%B8%E5%85%B3/%E5%88%A9%E7%94%A8Karma%E3%80%81Mocha%E6%90%AD%E5%BB%BA%E6%B5%8B%E8%AF%95%E7%8E%AF%E5%A2%83.md) * [利用Nightwatch.js实现e2e测试.md](%E6%B5%8B%E8%AF%95%E7%9B%B8%E5%85%B3/%E5%88%A9%E7%94%A8Nightwatch.js%E5%AE%9E%E7%8E%B0e2e%E6%B5%8B%E8%AF%95.md) * [编写高质量JS代码的68个有效方法-读书笔记](%E7%BC%96%E5%86%99%E9%AB%98%E8%B4%A8%E9%87%8FJS%E4%BB%A3%E7%A0%81%E7%9A%8468%E4%B8%AA%E6%9C%89%E6%95%88%E6%96%B9%E6%B3%95-%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0) * [[20140926]编写高质量JS代码的68个有效方法(一).md](%E7%BC%96%E5%86%99%E9%AB%98%E8%B4%A8%E9%87%8FJS%E4%BB%A3%E7%A0%81%E7%9A%8468%E4%B8%AA%E6%9C%89%E6%95%88%E6%96%B9%E6%B3%95-%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0/%5B20140926%5D%E7%BC%96%E5%86%99%E9%AB%98%E8%B4%A8%E9%87%8FJS%E4%BB%A3%E7%A0%81%E7%9A%8468%E4%B8%AA%E6%9C%89%E6%95%88%E6%96%B9%E6%B3%95%EF%BC%88%E4%B8%80%EF%BC%89.md) * [[20141011]编写高质量JS代码的68个有效方法(二).md](%E7%BC%96%E5%86%99%E9%AB%98%E8%B4%A8%E9%87%8FJS%E4%BB%A3%E7%A0%81%E7%9A%8468%E4%B8%AA%E6%9C%89%E6%95%88%E6%96%B9%E6%B3%95-%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0/%5B20141011%5D%E7%BC%96%E5%86%99%E9%AB%98%E8%B4%A8%E9%87%8FJS%E4%BB%A3%E7%A0%81%E7%9A%8468%E4%B8%AA%E6%9C%89%E6%95%88%E6%96%B9%E6%B3%95%EF%BC%88%E4%BA%8C%EF%BC%89.md) * [[20141030]编写高质量JS代码的68个有效方法(三).md](%E7%BC%96%E5%86%99%E9%AB%98%E8%B4%A8%E9%87%8FJS%E4%BB%A3%E7%A0%81%E7%9A%8468%E4%B8%AA%E6%9C%89%E6%95%88%E6%96%B9%E6%B3%95-%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0/%5B20141030%5D%E7%BC%96%E5%86%99%E9%AB%98%E8%B4%A8%E9%87%8FJS%E4%BB%A3%E7%A0%81%E7%9A%8468%E4%B8%AA%E6%9C%89%E6%95%88%E6%96%B9%E6%B3%95%EF%BC%88%E4%B8%89%EF%BC%89.md) * [[20141129]编写高质量JS代码的68个有效方法(四).md](%E7%BC%96%E5%86%99%E9%AB%98%E8%B4%A8%E9%87%8FJS%E4%BB%A3%E7%A0%81%E7%9A%8468%E4%B8%AA%E6%9C%89%E6%95%88%E6%96%B9%E6%B3%95-%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0/%5B20141129%5D%E7%BC%96%E5%86%99%E9%AB%98%E8%B4%A8%E9%87%8FJS%E4%BB%A3%E7%A0%81%E7%9A%8468%E4%B8%AA%E6%9C%89%E6%95%88%E6%96%B9%E6%B3%95%EF%BC%88%E5%9B%9B%EF%BC%89.md) * [[20141205]编写高质量JS代码的68个有效方法(五).md](%E7%BC%96%E5%86%99%E9%AB%98%E8%B4%A8%E9%87%8FJS%E4%BB%A3%E7%A0%81%E7%9A%8468%E4%B8%AA%E6%9C%89%E6%95%88%E6%96%B9%E6%B3%95-%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0/%5B20141205%5D%E7%BC%96%E5%86%99%E9%AB%98%E8%B4%A8%E9%87%8FJS%E4%BB%A3%E7%A0%81%E7%9A%8468%E4%B8%AA%E6%9C%89%E6%95%88%E6%96%B9%E6%B3%95%EF%BC%88%E4%BA%94%EF%BC%89.md) * [[20141213]编写高质量JS代码的68个有效方法(六).md](%E7%BC%96%E5%86%99%E9%AB%98%E8%B4%A8%E9%87%8FJS%E4%BB%A3%E7%A0%81%E7%9A%8468%E4%B8%AA%E6%9C%89%E6%95%88%E6%96%B9%E6%B3%95-%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0/%5B20141213%5D%E7%BC%96%E5%86%99%E9%AB%98%E8%B4%A8%E9%87%8FJS%E4%BB%A3%E7%A0%81%E7%9A%8468%E4%B8%AA%E6%9C%89%E6%95%88%E6%96%B9%E6%B3%95%EF%BC%88%E5%85%AD%EF%BC%89.md) * [[20141220]编写高质量JS代码的68个有效方法(七).md](%E7%BC%96%E5%86%99%E9%AB%98%E8%B4%A8%E9%87%8FJS%E4%BB%A3%E7%A0%81%E7%9A%8468%E4%B8%AA%E6%9C%89%E6%95%88%E6%96%B9%E6%B3%95-%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0/%5B20141220%5D%E7%BC%96%E5%86%99%E9%AB%98%E8%B4%A8%E9%87%8FJS%E4%BB%A3%E7%A0%81%E7%9A%8468%E4%B8%AA%E6%9C%89%E6%95%88%E6%96%B9%E6%B3%95%EF%BC%88%E4%B8%83%EF%BC%89.md) * [[20141227]编写高质量JS代码的68个有效方法(八).md](%E7%BC%96%E5%86%99%E9%AB%98%E8%B4%A8%E9%87%8FJS%E4%BB%A3%E7%A0%81%E7%9A%8468%E4%B8%AA%E6%9C%89%E6%95%88%E6%96%B9%E6%B3%95-%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0/%5B20141227%5D%E7%BC%96%E5%86%99%E9%AB%98%E8%B4%A8%E9%87%8FJS%E4%BB%A3%E7%A0%81%E7%9A%8468%E4%B8%AA%E6%9C%89%E6%95%88%E6%96%B9%E6%B3%95%EF%BC%88%E5%85%AB%EF%BC%89.md) * [[20150110]编写高质量JS代码的68个有效方法(九).md](%E7%BC%96%E5%86%99%E9%AB%98%E8%B4%A8%E9%87%8FJS%E4%BB%A3%E7%A0%81%E7%9A%8468%E4%B8%AA%E6%9C%89%E6%95%88%E6%96%B9%E6%B3%95-%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0/%5B20150110%5D%E7%BC%96%E5%86%99%E9%AB%98%E8%B4%A8%E9%87%8FJS%E4%BB%A3%E7%A0%81%E7%9A%8468%E4%B8%AA%E6%9C%89%E6%95%88%E6%96%B9%E6%B3%95%EF%BC%88%E4%B9%9D%EF%BC%89.md) * [[20150123]编写高质量JS代码的68个有效方法(十).md](%E7%BC%96%E5%86%99%E9%AB%98%E8%B4%A8%E9%87%8FJS%E4%BB%A3%E7%A0%81%E7%9A%8468%E4%B8%AA%E6%9C%89%E6%95%88%E6%96%B9%E6%B3%95-%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0/%5B20150123%5D%E7%BC%96%E5%86%99%E9%AB%98%E8%B4%A8%E9%87%8FJS%E4%BB%A3%E7%A0%81%E7%9A%8468%E4%B8%AA%E6%9C%89%E6%95%88%E6%96%B9%E6%B3%95%EF%BC%88%E5%8D%81%EF%BC%89.md) * [[20150214]编写高质量JS代码的68个有效方法(十一).md](%E7%BC%96%E5%86%99%E9%AB%98%E8%B4%A8%E9%87%8FJS%E4%BB%A3%E7%A0%81%E7%9A%8468%E4%B8%AA%E6%9C%89%E6%95%88%E6%96%B9%E6%B3%95-%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0/%5B20150214%5D%E7%BC%96%E5%86%99%E9%AB%98%E8%B4%A8%E9%87%8FJS%E4%BB%A3%E7%A0%81%E7%9A%8468%E4%B8%AA%E6%9C%89%E6%95%88%E6%96%B9%E6%B3%95%EF%BC%88%E5%8D%81%E4%B8%80%EF%BC%89.md) * [[20150304]编写高质量JS代码的68个有效方法(十二).md](%E7%BC%96%E5%86%99%E9%AB%98%E8%B4%A8%E9%87%8FJS%E4%BB%A3%E7%A0%81%E7%9A%8468%E4%B8%AA%E6%9C%89%E6%95%88%E6%96%B9%E6%B3%95-%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0/%5B20150304%5D%E7%BC%96%E5%86%99%E9%AB%98%E8%B4%A8%E9%87%8FJS%E4%BB%A3%E7%A0%81%E7%9A%8468%E4%B8%AA%E6%9C%89%E6%95%88%E6%96%B9%E6%B3%95%EF%BC%88%E5%8D%81%E4%BA%8C%EF%BC%89.md) * [[20150312]编写高质量JS代码的68个有效方法(十三).md](%E7%BC%96%E5%86%99%E9%AB%98%E8%B4%A8%E9%87%8FJS%E4%BB%A3%E7%A0%81%E7%9A%8468%E4%B8%AA%E6%9C%89%E6%95%88%E6%96%B9%E6%B3%95-%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0/%5B20150312%5D%E7%BC%96%E5%86%99%E9%AB%98%E8%B4%A8%E9%87%8FJS%E4%BB%A3%E7%A0%81%E7%9A%8468%E4%B8%AA%E6%9C%89%E6%95%88%E6%96%B9%E6%B3%95%EF%BC%88%E5%8D%81%E4%B8%89%EF%BC%89.md) * [运维&部署](%E8%BF%90%E7%BB%B4&%E9%83%A8%E7%BD%B2) * [Docker容器管理平台Humpback进阶-私有仓库.md](%E8%BF%90%E7%BB%B4&%E9%83%A8%E7%BD%B2/Docker%E5%AE%B9%E5%99%A8%E7%AE%A1%E7%90%86%E5%B9%B3%E5%8F%B0Humpback%E8%BF%9B%E9%98%B6-%E7%A7%81%E6%9C%89%E4%BB%93%E5%BA%93.md) * [一个简单易用的容器管理平台-Humpback.md](%E8%BF%90%E7%BB%B4&%E9%83%A8%E7%BD%B2/%E4%B8%80%E4%B8%AA%E7%AE%80%E5%8D%95%E6%98%93%E7%94%A8%E7%9A%84%E5%AE%B9%E5%99%A8%E7%AE%A1%E7%90%86%E5%B9%B3%E5%8F%B0-Humpback.md) * [前端监控系统实现.md](%E8%BF%90%E7%BB%B4&%E9%83%A8%E7%BD%B2/%E5%89%8D%E7%AB%AF%E7%9B%91%E6%8E%A7%E7%B3%BB%E7%BB%9F%E5%AE%9E%E7%8E%B0.md) * [记一次Zookeeper数据找回.md](%E8%BF%90%E7%BB%B4&%E9%83%A8%E7%BD%B2/%E8%AE%B0%E4%B8%80%E6%AC%A1Zookeeper%E6%95%B0%E6%8D%AE%E6%89%BE%E5%9B%9E.md) # The End ================================================ FILE: React Native Cookbook/章节一-new.md ================================================ # 章节一、React Native 工具链 React Native 身处由数十种软件工具构成的生态系统中。包含转换器(Babel,Metro 以及 Webpack),包管理工具(NPM,Yarn),检查器,单元测试框架等。本章将介绍语言基础以及需要在您的 React Native 项目中使用到的最小化的开源工具。您可以使用常规的 JavaScript 或者是一些编译到 JavaScript 的语言,如 TypeScript 或者 ES6+ 来编写 React Native 应用。我希望本章能帮助您了解 JavaScript 惊人的速度。 > **Expo** > > 最近,React Native 团队与 Expo 合作推出了不需要本地环境支持的开发环境。这是尝试和探索 React Native 非常棒的方式,但是当你想在硬件上运行应用时,一个本地开发环境将至关重要。 ## 1.1 配置您的开发环境 如果您也在其他 Web 项目中使用这些工具,您可能会自行排查您的环境。就像木匠在工作现场一样,您需要知道所有的工具是如何运行的以及它们是否需要被调整。React Native 是一个包含 Node.js 、iOS 以及 Android 这三个编程环境的软件包。同时,Node 的包管理工具 NPM,也是需要用到的。 ### 问题 React Native 是一个依赖许多不同工具的软件包。我们如何能确保它们都配置正确呢?拭目以待。 #### Node 和 Watchman Node.js (通常称之为 Node)可以让您的计算机像 Web 浏览器运行 JavaScript 一样运行本地 JavaSript。由于 Node.js 直接在您的操作系统上运行,因此 Node 可以包装或者绑定 C 语言库,同时也能像 `PHP、Python、Perl、Ruby` 一样,解决相同的编程问题。 Watchman 则是一个用于监控本地文件变更并触发对应事件的小工具。使用它可以再您的模拟器上执行更新后的代码而不需要重新编译整个项目。同时,也可以非常简单快速的安装它。 > **安装 Node.js** > > 如何安装 Node 取决于您的操作系统,最好的方式是参 [Node.js 官方网站](https://nodejs.org/en/)。如果您使用 Mac OS 系统,您可以使用 Mac OS 的包管理工具 `Homebrew` 来安装 Node.js。 #### 检查 Node.js 是否正确安装 您可能需要在您的电脑上安装多个版本的 Node.js。类似 Node 版本管理器(NVM)这样的工具能够让您在计算机上安装多个版本的 Node.js,这样每个开发项目都可以配置自己的 Node 版本。 POSIX 风格的操作系统(Linux,BSD,Mac OS) 能够使用符号链接(软链接)来支持多版本的 Node.js。 如果您在 Mac OS 上使用 `Homebrew` 安装了两个版本的 Node,也不要诧异。在列出的目录中,除了您自己的用户名和日期信息之外,应该还有如下一些内容: ```bash $> which node /usr/local/bin/node $> node -v v8.6.0 ``` 我正在使用 `8.6.0` 版本的 Node;不过,如果我检查 Homebrew 的目录(默认是 /usr/local/Cellar),将能够找到一个符号链接(指向到实际的 Node 地址): ```bash $>ls -l /usr/local/bin/node lrwxr-xr-x 1 jon admin 29 27 Sep 15:14 /usr/local/bin/node -> ../Cellar/node/8.6.0/bin/node ``` 更深入一点,我们可以找到被取代的其他版本的 Node。 ```bash $>ls -l /usr/local/Cellar/node total 0 drwxr-xr-x 14 jon admin 476 11 May 14:14 7.10.0 drwxr-xr-x 14 jon admin 476 25 Apr 13:41 7.9.0 drwxr-xr-x 14 jon admin 448 27 Sep 15:14 8.6.0 ``` 您计算机上的结果可能有所不同;不过,重要的是您已经安装成功了一个最近版本的 Node,并能够在您的项目中使用。 #### NPM NPM 包含一个命令行运行的包管理工具以及一个全面的开源软件包仓库。NPM 中的 `react-native` 软件包包含了依赖特定平台的 JavaScript ES6 模块代码。例如 `` 组件在 `iOS` 上是基于 `RCTText.m`的,在 `Android` 上是基于 `ReactTextView.java`的。 > **如何使用 Yarn?** > > React Native 的历史版本使用 NPM,但 Yarn 在 JavaScript 社区中也很流行。Yarn 是仍然依赖于 npm 仓库一个更快的改进。 `yarn.lock` 能够确保依赖得到正确的维护。Yarn 将优先检查 `yarn.lock`,其次检查 `package.json`,实现无缝向 Yarn 过渡。 NPM 包既可以全局运行,也可以在给定项目的 `node_modules` 目录下运行。最好是全局安装 React Native,本地安装项目相关的依赖。这种方式可以让你在任何地方运行 React Native 的命令行工具 `react-native-cli`。React Native 的特定版本则是项目依赖项的一部分。(译者注:使用 `npm i -g react-native-cli` 全局安装命令行工具,使用 `npm i -S react-native` 本地安装 React Native 框架) #### 检查 NPM 是否正确安装 ```bash $> which npm /usr/local/bin/npm ``` 您的终端(控制台)将返回一个路径。使用如下命令检查 npm 版本: ```bash $> npm -v 4.2.0 ``` #### 安装 React Native 命令行工具 ```bash npm install -g react-native-cli ``` #### Xcode(iOS 开发需要) Xcode 的 Apple 的官方开发环境,用于构建和运行 Mac OS 和 iOS 的应用程序。你需要安装 Xcode (仅适用于 Mac OS)(译者注:要使用 XCode,请掏钱买 Mac)以编译由 Objective-C 和 Swift 编写的 React Native 组件。 Xcode 还附带了命令行工具,这些通过 Node.js 绑定到 Mac OS 的命令行工具是构建代码所必须的。 > **运行 XCode Beta** > > 随着 iOS 的定期更新,在您的开发机器上,可能会有测试版本的 XCode。多个版本的 Xcode 将会导致模拟器也有多个版本。这种情况下,最好是使用 Xcode 而不是使用命令行(译者注:命令行是指 `react-native run-ios`)来启动模拟器。 #### JDK Android 与 Java 就像糖和黄油,组合在一起将变得美味无比。Android 和 React Native 也不例外。使用 JavaScript 编写的 React 组件能够触及到 Android Java 虚拟机。为了能够本地运行 Android,需要安装 Java Development Kit(JDK)。 [从 Oracle 的官网下载 JDK(至少下载 8 以上版本](http://www.oracle.com/technetwork/java/javase/downloads/index.html) #### Android Studio Android Studio 是免费的用于构建和部署 Android 应用程序的官方开发环境。一旦用上了它,随之而来的是另一个包管理工具。幸运的是,“React Native 入门指南” 提供了详细步骤去了解它。 ## 1.2 通过 Babel 来编写 ES6 Babel 将 20 年的编程语言,带到了 21 世纪。通过 Babel,您可以使用增强的语法来编写 JavaScript,让您的 JavaScript 代码变得更流畅且更具表现力。它将诸如 数据结构转换、处理作用域中的 this 以及类继承等常见模式编程了语言本身的一部分。 通过一系列语法转换,Babel 实现了对语言本身的语法高进。每个转换器都会运行您的代码,将新的 ES6 语言特性转换为等价的 JavaScript 语法。 通过 `react-native` 预设(译者注:`npm i babel-preset-react-native -D`),以下 ES6 代码将会被自动转换。 将以下内容保存到 `babel-transform.js` 文件中: ```js AsyncStorage.getItem('loginParameters').then(login => { this.setState({ login }); }); ``` 通过命令行执行: ```bash $> babel babel-transform.js ``` Babel 将返回(为了可读性,已格式化): ```js var _this = this; AsyncStorage.getItem('loginParams').then(function(login) { _this.setState({ login: login }); }); ``` React Native 完成了以下事情: 1. 展开 `{ login }` 到 `{ login: login }` 2. 在外部方法中定义 `_this`,并替换 `=>` 运算符 ================================================ FILE: React Native Cookbook/章节一.md ================================================ # 章节一、入门 在本章中,将包含以下内容: * 在文本和容器上添加样式 * 使用图片来模仿一个视频播放器 * 创建一个切换按钮 * 将数据项作为列表展示 * 在视窗中添加选项卡 * 使用 Flexbox 布局来创建个人资料页 * 设置导航 ## 简介 **React Native** 是一个快速演进的库。在过去的一年中,它在开源社区中变得非常受欢迎。每隔一周,就会有一个性能更优,组件或者设备 API 更多的新版本产出。 虽然快速演进在大部分时候都是优势,不过有时候也会成为一个缺点。有时新版本会带来一些破坏性更新,这要求我们在更新库版本时,必须非常小心。一般情况下,破坏性变更以及重大变化都会记录在发行说明中,在更新版本的时候,请务必阅读发行说明。最重要的是,我们也要确保所有在项目中的使用的第三方库必须与新版本保持一致。 在本章中,我们将学习库中最常见的组件。阅读本书的前提是已完成 React Native 官方文档入门级别的学习,这也意味着我们不会花时间在安装环境和编写 Hello World 示例上。 为了贯穿整本书的示例,我们将创建一个新的 React Native 应用程序,请确保已正确处理好你的开发环境。推荐参考 React Native 官方网站搭建环境,然后在控制台上执行如下一些命令,创建名为 `AnAppName` 的应用程序: ```bash $ react-native init --verbose AnAppName # 初始化AnAppName项目 $ cd AnAppName # 进入AnAppName目录 $ react-native run-android # 启动Android开发环境 $ react-native run-ios # 启动iOS开发环境(要求Mac OS) ``` ## 为文本和容器添加样式 我们有若干组件可供使用,但其中用来创建布局或其他组件的最常见和有用的组件则是容器组件和文本年组建。在这个小节中,我们将学习如何使用容器组件和文本组件,更最重要的是,我们将看到样式是如何在 React Native 中的工作的。 我们将尝试创建一个简单的音乐播放器界面;暂时,我们先不使用图标,后续我们再来添加图标。 ### 前提 请按照介绍中的步骤进行操作,以创建一个名为 `ContainersText` 的应用程序。 ### 实现 1. 在项目根目录下创建 `src` 目录,用来放置我们的 `JavaScript` 代码。 2. 在 `src` 目录中,创建 `MainApp.js` 文件。 3. 在 `MainApp.js` 文件中,创建一个无状态组件,用来模仿一个简单的音乐播放器。目前,它仅能够显示歌曲名称和进度。 ```js import React from 'react'; import { StyleSheet, Text, View } from 'react-native'; ``` 4. 一旦我们导入了对应的依赖,我们可以继续编写组件: ```js const MainApp = () => { const name = '01 - Hey, this is my life'; return ( Playing: {name} ); }; ``` 5. 至此,我们已经准备好了我们的组件。接下来我们需要通过一些样式代码来添加颜色和字体样式: ```js const styles = StyleSheet.create({ container: { margin: 10, marginTop: 100, backgroundColor: '#e67e22', borderRadius: 5 }, innerContainer: { backgroundColor: '#d35400', height: 50, width: 150, borderTopLeftRadius: 5, borderBottomLeftRadius: 5 }, title: { fontSize: 18, fontWeight: '200', color: '#fff', position: 'absolute', backgroundColor: 'transparent', top: 12, left: 10 }, subtitle: { fontWeight: 'bold' } }); ``` 6. 为了在其他文件中调用该组件,我们需要将它导出导出,如下: ```js export default MainApp; ``` 7. 下一步就是将我们的新组件导入 `index.ios.js` 和 `index.android.js` 中。在这两个平台,代码都如下所示: ```js import React, { Component } from 'react'; import { AppRegistry } from 'react-native'; import MainApp from './src/MainApp'; AppRegistry.registerComponent('ContainersText', () => MainApp); ``` 8. 为了在模拟器中看到程序的变化,我们需要重新加载应用。在 `iOS`,使用 `Command + R` 即可,在`Android` 中,需要摇晃模拟器,呼出菜单按钮,然后点击刷新。 ### 原理分析 让我们回顾下在上一个小节中,我们做了些什么。在步骤 3~6 中,我们创建了包含样式的组件。我们继续挖掘这些步骤中的细节。 在步骤 3 中,我们引入了组件的依赖。我们会使用一个容器组件 `View`,如果您熟悉 Web 开发,那么可以把 `View` 理解为 `div`。可以在其他视图、文本、列表以及我们创建或者从三方库导入的自定义组件中嵌套 `View`。 在步骤 4 中,我们定义了名为 `MainApp` 的组件。我们约定文件和组件应该使用相同的名称。`MainApp` 是不包含任何状态的无状态组件,它是一个纯函数且不支持任何生命周期函数。另外,我们定义了一个名为 `name` 的常量,实际应用中,这个属性应该通过 `props` 进行传递。在返回值中,我们用 `JSX` 定义了需要呈现的组件以及对样式的引用。 每个组件都有一个名为 `style` 的属性,这个属性用来接收我们想附加给组件的样式对象。除文本组件外,样式并不会被继承,所以,我们需要会每个组件设置样式。 在步骤 5 中,我们给组件定义了样式。可以看到,我们使用 `StyleSheet` API 来定义样式。如前所述,需要定义一个包含样式的对象;通过 `StyleSheet` API 来创建样式,有利于性能优化,因为样式将在每次渲染时被重用,而不是每次渲染时再创建一个对象。 样式对象中的属性非常简单。如果你是一个 Web 开发者,那么更容易理解这一点,它和 CSS 属性很像。但要注意,并不是完全一样。在 `React Native` 的样式中,有 `margin padding width height borderWidth borderColor borderRadius`等等,要查看所有属性,建议查阅 `React Native` 文档。 在步骤 7 中,我们导入了新实现的组件,并用来作为根组件来引导我们的 APP。 ### 更多 注意步骤 5 中关于标题样式的定义。在 `title` 中,有一个 `backgroundColor=transparent` 的样式。我们注释掉这句代码,看看会发生什么: 在 `iOS` 中,文本显示了一个非预期的橙色背景。为了解决这个问题,所以需要设置背景色为透明。但问题是,为什么会出现这种情况呢?原因是在 `React Native` 中,使用父元素的背景来作为文本的背景进行渲染,能够改善渲染性能,因为渲染引擎不需要计算文本每个字母周围的像素,同时也会渲染得更快。 **注意** 请认真考虑是否有必要将文本背景设置为透明。如果组件内容更新频繁,这将会带来一些性能问题,特别是文本特别长的时候。 ## 使用图像来模拟视频播放器 ### 前提 请先通过 `React Native CLI` 程序创建一个名为 `LoadingImages` 的空白 App。如果您还不知道如何创建,请参考本章介绍中的说明进行操作。 ### 实现 1. 首先,是创建 `src` 文件夹,并在这个文件夹中,创建 `MainApp.js` 文件和用来存储图标的图片目录。我们的项目结构如图所示: 2. 在 `MainApp.js` 中,引入相关依赖: ```js import React from 'react'; import { StyleSheet, View, Image } from 'react-native'; ``` 3. 通过 `require` 来引入需要在组件中使用到的图片。一般情况下,通过定义一个常量来保存图片,可以让我们在不同的地方使用同一个图像。有时候,我们需要重新启动 package server 来正确加载图像,特别是在 `Windows` 下。 ```js const playIcon = require('./images/play.png'); const volumeIcon = require('./images/sound.png'); const hdIcon = require('./images/hd-sign.png'); const fullScreenIcon = require('./images/full-screen.png'); const remoteImage = { uri: 'https://s3.amazonaws.com/crysfel/public/book/new-york.jpg' }; ``` 4. 使用无状态组件来渲染 `JSX`。同时,将用到我们在前一步中申明的图片: ```js const MainApp = () => { return ( ); }; ``` 5. 一旦有了要呈现的元素,则需要为每个元素定义样式: ```js const styles = StyleSheet.create({ fullscreen: { flex: 1 }, container: { position: 'absolute', backgroundColor: '#202020', borderRadius: 5, flexDirection: 'row', height: 50, padding: 5, paddingTop: 16, bottom: 30, right: 10, left: 10, borderWidth: 1, borderColor: '#303030' }, icon: { tintColor: '#fff', height: 16, width: 16, marginLeft: 5, marginRight: 5 }, progress: { backgroundColor: '#000', borderRadius: 7, flex: 1, height: 14, margin: 10, marginTop: 2 }, progressBar: { backgroundColor: '#bf161c', borderRadius: 5, height: 10, margin: 2, width: 80 } }); ``` 6. 为了使用该组件,需要先导出它。仅仅需要一行代码,如下: ```js export default MainApp; ``` 7. 最后,将我们的新组件导入到 index.ios.js 和 index.android.js 中: ```js import React, { Component } from 'react'; import { AppRegistry } from 'react-native'; import MainApp from './src/MainApp'; AppRegistry.registerComponent('LoadingImages', () => MainApp); ``` 8. 完成!只需要刷新模拟器上的 App,就可以看到如下界面: ### 原理分析 在步骤 2 中,我们引入了用来渲染本地文件或者是远端服务器图片的 `Image` 组件。 在步骤 3 中,我们引入了所有的图像。通过 `require` 加载所有的图像是一种很好的做法,我们仅仅需要引入一次,就可以在我们的组件中使用它们。同时,在每次渲染中,`React Native` 都将使用同一个图像。如果想显示来自远端的动态图像,那么就需要在每次渲染中去请求。 `require` 方法接受图片路径作为参数,支持相对路径。如果是远程图片,则需要通过 `uri` 来指向图片地址。 在步骤 4 中,定义了一个无状态组件。在该组件中,使用 `remoteImage` 作为背景图像。为了将该图片设置为背景,需要将其他元素定义在 `Image` 组件内部。正如 CSS 一样,并没有 `backgroundUrl` 属性。 `Image` 的 `source` 属性接受一个对象来加载远程图片或图片应用。明确 `require` 每张图片,是非常重要的,因为当我们发布时,只要 `require` 的图片才会被自动加入到发布包中。所以,我们应该避免编写如下代码: ```js const iconName = playing ? 'pause' : 'play'; const icon = require(iconName); ``` 上述代码的发布包中将不会包含相关图片。因此,当我们尝试访问这些图像时将会出错。反之,我们应该采用如下的代码来重构: ```js const pause = require('pause'); const play = require('playing'); const icon = playing ? pause : play; ``` 这样,在打包应用程序时,将会包含这两个图片,同时,也能动态的选择展示哪个图片。 在步骤 5 中,我们定义了样式。大多数属性都是不言自明的。`tintColor` 可能会有点特别,这个属性是用来设置图片的颜色的。在此处的代码是将图片颜色设置为白色。我们还在后续的内容中单独讲解 `flex` 布局,在这里,我们只需要知道 `flexDirection: 'row'` 表示图标水平对齐。 在步骤 7 中,将 `MainApp` 导入到 `iOS` 和 `Android` 的引导程序中。之后,就可以在模拟器中运行我们的 App 了。 ### 更多 在这个配方中,我们使用了 `flexbox` 布局来水平排列播放器控制按钮。如果你想了解更多有关 `Flexbox`的内容,请查看后续 `使用Flexbox创建个人资料页面`。对于更高级的内容,可以阅读第二章“实现复杂的用户界面”。 ## 创建一个切换按钮 按钮是每个 App 中不可或缺的 UI 组件。按钮可以用于导航、触发 API 调用等。在本小节中,我们将创建一个切换按钮,默认是取消选中,当用户点击时,则通过修改样式使之看起来像是被选中的。本小节中,我们将学习如何检测点击事件,使用图片作为 UI,保持按钮状态,并根据组件状态添加样式。 ### 前提 使用 `React Native CLI` 创建名为 `ButtonsAndEvents` 的 App。在本小节内容中,会使用到一个图片,请务必下载该小节所需的图片或者随意使用您自己的图片。 ### 实现 1. 首先,是创建 `src` 文件夹来存储源码,并在这个文件夹中,创建 `MainApp.js` 文件和图片目录: 2. 导入该类的依赖: ```js import React, { Component } from 'react'; import { StyleSheet, View, Image, Text, TouchableHighlight } from 'react-native'; const heartIcon = require('./images/plain-heart.png'); ``` 3. 在该小节中,我们需要在按下时保持按钮的状态。因此,需要创建一个继承自 `Component` 的类,如下: ```js class MainApp extends Component { state = { liked: false }; _onPressBtn = () => { // We will define the content on step 6 }; render() { // We will define the content on step 4 } } ``` 4. 在 `render` 中,定义组建的内容。此处,需要定义一个图片按钮和一个文本: ```js render() { return ( Do you like this app? ); } ``` 5. 定义一些样式来设置 `set dimensions, position, margins, colors` 等等: ```js const styles = StyleSheet.create({ container: { marginTop: 50, alignItems: 'center' }, btn: { borderRadius: 5, padding: 10 }, icon: { width: 180, height: 180, tintColor: '#f1f1f1' }, liked: { tintColor: '#e74c3c' }, text: { marginTop: 20 } }); ``` 6. 如果在模拟器中运行,应该能看到如下图所示内容: 7. 为了响应 tap 事件,我们需要定义 `_onPressBtn` 的处理函数并将其作为回调函数分配给 `onPress` 属性: ```js class MainApp extends Component { state = { liked: false }; _onPressBtn = () => { this.setState({ liked: !this.state.liked }); }; render() { return ( Do you like this app? ); } } ``` 8. 如果此时按下按钮,就算组件状态改变成 `ON`,也不会在 UI 上看到任何更改。只有当我们给它添加不同状态的样式时,才会看到 UI 有响应: ```js render() { const likedStyles = this.state.liked ? styles.liked : null; return ( Do you like this app? ); } ``` 9. 差不多完成这个类了,唯一缺少的是导出组件。使用如下代码进行导出: ```js export default MainApp; ``` 10. 最后,更新 `index.ios.js` 以及 `index.android.js` 来导入和使用我们的新组件: ```js import React, { Component } from 'react'; import { AppRegistry } from 'react-native'; import MainApp from './src/MainApp'; AppRegistry.registerComponent('ButtonsAndEvents', () => MainApp); ``` ### 原理分析 在步骤 2 中,导入了 `TouchableHighlight` 组件,用来处理触摸事件。 当用户触摸活动区域时,区域内容将通过我们设置的 `underlayColor` 样式高亮显示。 在步骤 3 中,定义了一个有状态组件。在本小节中,`state` 仅仅包含一个属性,但我们可以根据需要添加更多的状态。在第二章“实现复杂的用户界面”中,我们将在更复杂的场景中看到更多的状态处理。 在步骤 6 中,使用 `setState` 来更新 `liked` 属性的值。这个方法正是从 `Component` 类中继承而来。 在步骤 7 中,根据当前 `state` 的 `liked` 属性来设置图片颜色为红色,通过返回 `null` 来避免应用任何样式。使用数组来组合多个对象样式对于应用样式非常方便,在内部,组件会将所有样式合并为一个对象。后定义的属性将会覆盖先定义的同名属性。 ### 更多 在一个真实的 App 中,我们将可能使用多种按钮,图标左对齐,带标签,不同的尺寸和颜色等等。此时,强烈建议您创建可复用的组件,以避免在 App 中到处重复代码。在第二章“实现复杂的用户界面”中,我们将创建一个按钮来组件来处理其中的一些场景。 ## 显示列表元素 **列表无处不在!**如用户的历史订单列表、商店中的可用商品、待播放的歌曲列表等;基本上,任何 App 都需要用到列表。 在这个小节中,我们将使用列表组件显示几个 Item。先将一些数据定义成 JSON 文件,然后使用 `require` 来加载这个文件,最终将其渲染为一个漂亮又简洁的列表布局。 ### 前提 首先,创建一个名为 `ListItems` 的空 App。为了在列表的每个 Item 中显示 icon 图标,请下载图片资源或使用您自己的 `.png` 图片资源。 ### 实现 1. 在项目中创建 `src` 目录,然后在 `src` 目录中创建 `MainApp.js` 以及 `sales.json`: 2. 我们将要显示的列表数据定义在 `sales.json` 中,示例数据如下: ```json [{ "items": 5, "address": "140 Broadway, New York, NY 11101", "total": 38, "date": "May 15, 2016" }] ``` 3. 为了避免占用大量篇幅,我只定义了一条数据,您可以往数据数组中加入更多内容。使用 `Ctrl+C Ctrl+V` 来创建更多元素,另外,可以修改其中的一些数值。 4. 找到 `index.ios.js` 和 `index.android.js` 文件,移除掉已有的代码,添加如下代码来导入依赖和注册 App: ```js import React, { Component } from 'react'; import { AppRegistry } from 'react-native'; import MainApp from './src/MainApp'; AppRegistry.registerComponent('ListItems', () => MainApp); ``` 5. 在上一步中,虽然导入了 `MainApp`,但实际上并没有定义。打开 `src/MainApp.js`,然后导入如下依赖: ```js import React, { Component } from 'react'; import { StyleSheet, View, ListView, Image, Text } from 'react-native'; import data from './sales.json'; const basketIcon = require('./images/basket.png'); ``` 6. 现在需要创建一个类来渲染列表。将销售数据放到 `state` 中,可以让我们很轻松的插入或删除数据: ```js class MainApp extends Component { constructor(props) { super(props); var ds = new ListView.DataSource({ rowHasChanged: (r1, r2) => r1 !== r2 }); this.state = { dataSource: ds.cloneWithRows(data) }; } renderRow(record) { // Defined on step 8 } render() { // Defined on step 7 } } export default MainApp; ``` 7. 我们需要在 `render` 方法中,使用 `ListView` 组件,并使用 `renderRow` 方法来渲染每个单独的 Item。`dataSource` 属性定义了需要在列表中呈现的数组元素: ```js render() { return ( Sales ); } ``` 8. 现在,我们来补充 `renderRow` 方法的内容。这个方法接收包含所需信息单个对象。我们将要显示三个数据列。第一列中,显示 Icon 图标,第二列中每次销售的商品数量和该订单的发货地址,第三列中将售出日期和总价: ```js renderRow(record) { return ( {record.items} Items {record.address} {record.date} ${record.total} ); } ``` 9. 当我们定义好 JSX 之后,是时候添加样式了。首先,需要为主容器、标题以及行容器定义颜色、外边距、内边距等等。为了为每个列创建三列布局,需要使用到 `flexDirection` 中的 `row` 属性。我们将在本章其他小节中学到更多关于该属性的知识: ```js const styles = StyleSheet.create({ mainContainer: { flex: 1, backgroundColor: '#fff' }, title: { backgroundColor: '#0f1b29', color: '#fff', fontSize: 18, fontWeight: 'bold', padding: 10, paddingTop: 40, textAlign: 'center' }, row: { borderColor: '#f1f1f1', borderBottomWidth: 1, flexDirection: 'row', marginLeft: 10, marginRight: 10, paddingTop: 20, paddingBottom: 20 } }); ``` 10. 当我们刷新模拟器时,我们可以看到如下一些截图: 11. 现在,在 `StyleSheet` 的定义中,我们来给 icon 图标添加样式。添加一个黄色圆圈作为背景,并将图标的颜色设定为白色: ```js iconContainer: { alignItems: 'center', backgroundColor: '#feb401', borderColor: '#feaf12', borderRadius: 25, borderWidth: 1, justifyContent: 'center',height: 50, width: 50, }, icon: { tintColor: '#fff', height: 22, width: 22, }, ``` 12. 应用这些变更后,我们可以每行的左侧有一个好看的 icon 图标,如下图所示: 13. 最后,为文本设置样式。需要设置颜色、字体、字号、内边距以及其他一些样式: ```js info: { flex: 1, paddingLeft: 25, paddingRight: 25, }, items: { fontWeight: 'bold', fontSize: 16, marginBottom: 5, }, address: { color: '#ccc', fontSize: 14, }, total: { width: 80, }, date: { fontSize: 12, marginBottom: 5, }, price: { color: '#1cad61', fontSize: 25, fontWeight: 'bold', }, ``` 14. 最终结果应该类似下图所示: ### 原理分析 在步骤 6 中,创建了数据源并添加到了 `state` 中。`ListView.DataSource` 为 `ListView` 组件提供了高性能的数据处理。`rowhHasChanged` 是一个必须的属性,它是一个用来比较数据元素是否变化的函数。 我们需要调用 `cloneWithRows` 来将数据填充到数据源,并传递给组件。 如果要添加更多数据,我们应该再次调用 `cloneWithRows` 包含旧数据和新数据。数据源将计算差异并在必要时重新渲染。 在步骤 7 中,使用 `render` 方法来渲染列表。步骤 6 中的数据源以及 `renderRow` 方法是两个必备的属性。 `renderRow` 是为每个行返回 JSX 的函数。 ### 更多 我们使用 `flexbox` 创建了一个简单的布局;然而,在本章中还有另外的小节将深入的探讨使用 `flexbox` 的更多细节。 一旦有了列表,我们就应该有机会去看到每个订单的细节。此时,我们应该使用 `TouchableHighlight` 组件作为每行的主容器,所以,请继续尝试。如果还不确定如何使用 `TouchableHighlight` 组件,请参考本章节中“创建一个切换按钮”。 ## 在视窗中添加选项卡 `Tabs(选项卡)` 是一个非常常见的组件,特别是在 `iOS` App 中。在本小节中,我们来学习如何在 `iOS` 中使用选项卡组件。截止目前,还并不支持 `Android`,如果真的需要在 `Android` 中使用选项卡,请选用第三方组件库来添加类似功能。 ### 实现 1. 首先,导入该组件的所有依赖项以及需要使用到的图标: ```js import React, { Component } from 'react'; import { StyleSheet, View, Image, Text, TabBarIOS } from 'react-native'; const homeIcon = require('./images/home.png'); const favIcon = require('./images/star.png'); const blogIcon = require('./images/notebook.png'); const profileIcon = require('./images/user.png'); ``` 2. 为了选中一个选项卡,应该在 `state` 中存储当前选中状态,因此我们需要使用类来定义组件,如下: ```js class MainApp extends Component { state = { selected: 'home' }; selectTab(id) { // Defined on step 5 } renderTab(options) { // Defined on step 4 } render() { // Defined on step 3 } } ``` 3. 在 `render` 方法中,我们需要定义选项卡组件以及我们要展示的每个选项卡。此时,我们可以使用包含参数的 `renderTab` 方法来构建 JSX,这使得我们可以通过调用函数来复用代码: ```js render() { return ( {this.renderTab({title: 'Home', id: 'home', icon: homeIcon})} {this.renderTab({title: 'Favorites', id: 'favorites', icon: favIcon})} {this.renderTab({title: 'Blog', id: 'blog', icon: blogIcon})} {this.renderTab({title: 'Profile', id: 'profile', icon: profileIcon})} ); } ``` 4. 对于 `renderTab` 方法,我们需要定义一些属性,如标签标题、图标、是否选中以及选中时的回调函数。现在,我们为每个标签设定相同的内容,实际应用中,需要将主要内容作为参数传递到 App 中。其中最重要的属性就是选中属性。因为只可以在选项卡中选中一个项,所以将当前选中的项保存在 `state` 中: ```js renderTab(options) { return ( this.selectTab(options.id)} icon={options.icon} > {options.title} ); } ``` 5. 在上一步中,在选项卡项按下时,将会调用 `selectTab` 方法。这里的考虑是,当用户按下选项卡项时,调用此函数把选中项 ID 保存到 `state` 中,用来设置当前选中状态: ```js selectTab(id) { this.setState({ selected: id, }); } ``` 6. 接着,给屏幕上的内容添加一些样式,也为每个标签的 icon 设置合适的颜色。同时,我们也将组件进行导出,便于在其他任何地方使用: ```js const styles = StyleSheet.create({ container: { flex: 1, alignItems: 'center', justifyContent: 'center' }, title: { fontSize: 20, marginTop: 20 }, icon: { width: 30, height: 30, tintColor: '#42b49a' } }); export default MainApp; ``` 7. 最后,更新 `index.ios.js`,导入我们新的类: ```js import React, { Component } from 'react'; import { AppRegistry } from 'react-native'; import MainApp from './src/MainApp'; AppRegistry.registerComponent('TabsComponent', () => MainApp); ``` 8. 在模拟器中查看最终效果,如下: ## 使用 `flexbox` 创建个人资料页面 ## 设置导航器 ================================================ FILE: React Native 开发笔记/RN Aspect-01-环境准备.md ================================================ ================================================ FILE: React Native 开发笔记/RN Aspect-02-Hello React Native.md ================================================ ================================================ FILE: React Native 开发笔记/RN Aspect-03-修改名称与icon.md ================================================ ================================================ FILE: React Native 开发笔记/RN Aspect-04-打包App.md ================================================ ================================================ FILE: React Native 开发笔记/React Native开发之多屏适配.md ================================================ --- title: React Native开发之多屏适配 date: 2018-3-24 08:56:47 --- # 多屏适配 # 字体不随系统字体缩放 **针对 `iOS`** 需要在 `node_modules/react-native` 中找到 `RCTFont.mm` 文件(在目录中搜索),在其中,可以找到如下代码: ```js if (scaleMultiplier > 0.0 && scaleMultiplier != 1.0) { fontSize = round(fontSize); } ``` 将其注释掉,打包出来的 App,就不会受到系统字体尺寸变化的影响。 **针对 `Android`** 找到 `/android` 目录下的 `MainActivity.java` 文件。然后引入如下 Package: ```java import android.content.res.Configuration; import android.content.res.Resources; ``` 接着,在 Class 中,加入如下代码: ```java @Override public Resources getResources() { Resources res = super.getResources(); Configuration config=new Configuration(); config.setToDefaults(); res.updateConfiguration(config,res.getDisplayMetrics() ); return res; } ``` ================================================ FILE: React面面观/JSX中的那些小细节.md ================================================ --- title: JSX中的那些小细节 date: 2017-4-6 10:53:53 --- # 导言 在学习 `React` 的过程中,我们无可厚非,需要接触到 `JSX`。在 `JSX` 中,有一些约定是我们需要遵守的,有一些细节也需要我们去牢记。 本文就将我在学习过程中,了解到的约定与细节总结如下。 ### 1、自定义组件必须大写首字母 `JSX` 规定,只有大写的组件,才会被解析为自定义组件。 ### 2、如果我们使用了自定义组件,那么我们必须导入 `React`和 `组件对象` JSX会把组件编译为一个变量,所以就算在JSX没有直接使用,也需要进行导入,否则编译之后无法找到。 ### 3、属性的默认值为true 如果传递属性时,没有给定值,那么默认访问属性,值将会是 `true`,和HTML属性一致。 ### 4、JSX不支持返回多个元素,当我们有多个元素时,需要用一个外层div来包裹 ### 5、布尔值,NULL和Undefinded将会被忽略(不会渲染) ### 6、一些 `falsy values` 会被渲染,比如数字0 ### 7、想要条件显示某些组件,需要保证 `&&` 之前必须为 `true or false` ### 8、如果要显示 `true, false, null, undefined`,需要先转换为字符串 ================================================ FILE: React面面观/【译】参考手册-React组件.md ================================================ --- title: 参考手册-React组件 date: 2017-4-7 13:27:19 version: 15.4.2 --- # React.Component 组件能够让你将UI拆分为多个独立自治并可重用的部分。在 `React` 中提供了 `React.Component`。 ## 概述 `React.Component` 是一个抽象基类,直接引用 `React.Component` 无太大意义。反而,我们会用子类来继承它,并至少定义一个 `render()` 方法。 通常您将使用纯 [JavaScript class](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Classes) 来定义一个 `React` 组件: ```javascript class Greeting extends React.Component { render() { return

Hello, {this.props.name}

; } } ``` 如果您还没有使用ES6,可以使用 `React.createClass` 来替代。从 [Using React without ES6](https://facebook.github.io/react/docs/react-without-es6.html) 了解更多。 ## 组件生命周期 每个组件都有几个 `生命周期方法`,您可以不同的阶段插入自己的逻辑代码。以 `will` 开头的方法将在事情发生前被调用,以 `did` 开头的方法将在事情发生后被调用。 #### Mounting(挂载) 以下的方法,将在组件实例被创建和插入到DOM前被调用: - [`constructor()`](#constructor) - [`componentWillMount()`](#componentwillmount) - [`render()`](#render) - [`componentDidMount()`](#componentdidmount) #### Updating(更新) 属性或状态变化将会引发更新。以下方法将在重绘之前被调用: - [`componentWillReceiveProps()`](#componentwillreceiveprops) - [`shouldComponentUpdate()`](#shouldcomponentupdate) - [`componentWillUpdate()`](#componentwillupdate) - [`render()`](#render) - [`componentDidUpdate()`](#componentdidupdate) #### Unmounting(卸载) 以下方法将在组件开始从DOM移除时被调用: - [`componentWillUnmount()`](#componentwillunmount) ### 其他APIs 每个组件也都会提供以下几个API: - [`setState()`](#setstate) - [`forceUpdate()`](#forceupdate) ### 类属性 - [`defaultProps`](#defaultprops) - [`displayName`](#displayname) ### 实例属性 - [`props`](#props) - [`state`](#state) * * * ## 引用 ### `render()` ```javascript render() ``` `render()` 方法是必须的。 当调用 `render()` 方法时,将检查 `this.props` 和 `this.state` 并返回单个React元素。这个元素可以是原生DOM组件,如 `
`,也可以是你自定义的复合组件。 如果你不想渲染任何内容,你可以返回 `null` 或者是 `false`。当返回 `null` 或 `false` 时, `ReactDOM.findDOMNode(this)` 也将返回 `null`。 `render()` 函数应该是纯函数,这意味着你不应该修改组件状态,每次调用都应该返回同样的结果,同时也不要直接和浏览器交互(不要操作DOM)。如果你想要操作DOM,请使用 `componentDidMount()` 或其他生命周期方法来替代。使用纯粹的 `render()` 可以使组件易于理解。 > **注意** > > `render()` 不会在 [`shouldComponentUpdate()`](#shouldcomponentupdate) 返回 `false` 的时候被调用。 * * * ### `constructor()` ```javascript constructor(props) ``` React组件的构造函数会在挂载前被调用。当我们在 `React.Component` 的子类中实现构建函数时,我们应该在所有语句之前优先调用 `super(props)`。反之,在构造函数中访问 `this.props` 将会是未定义,这可能会导致bugs。 构造函数是初始化状态的地方。如果你不需要初始化状态,且不绑定方法,那么你不必要实现构造函数。 如果你确信,你可以使用 `props` 来初始化状态。以下是一个合法的 `React.Component` 子类构造函数: ```js constructor(props) { super(props); this.state = { color: props.initialColor }; } ``` 当心这种模式,它能够有效的 "forks" 属性且可能会导致bugs。你通常可以使用状态提升来替代同步属性到状态。 如果你想使用 `state` 来 "fork" `props`,你还需要实现 [`componentWillReceiveProps(nextProps)`](#componentwillreceiveprops) 来保持状态是最新的。但是状态提升通常是更容易且不容易出错的。 * * * ### `componentWillMount()` ```javascript componentWillMount() ``` `componentWillMount()` 将在挂载发生前,被立即调用。它会在 `render` 之前调用,因此在该方法中设置状态将不会触发重新渲染。避免在此方法中引入任何有副作用的行为或者订阅。 这是在服务端渲染中唯一被调用的生命周期钩子。通常,我们用来替代 `constructor()`。 * * * ### `componentDidMount()` ```javascript componentDidMount() ``` `componentDidMount()` 将在组件挂载之后立即被调用。需要使用 DOM 节点的初始化应该放在这里。如果你需要加载远端数据,这也是处理网络请求的好地方。在这个方法中设置状态将会触发重绘。 * * * ### `componentWillReceiveProps()` ```javascript componentWillReceiveProps(nextProps) ``` `componentWillReceiveProps()` 将在已经挂载的组件接受到新属性前被调用。如果你需要更新状态来响应属性变化(例如,重置状态),你可以比较 `this.props` 和 `nextProps`,然后使用 `this.setState()` 来处理状态变化。 请注意,就算属性没有变化,React也可能会调用此方法,因此如果你只想到处理有变更的情况,那么请确保比较当前值和下一次的值。这种情况在父组件变更导致你的组件重绘时可能会发生。 在挂载阶段初始化属性React不会调用 `componentWillReceiveProps`。如果一些组件的属性可能会更新,它仅会调用该方法。调用 `this.setState` 通常不会触发 `componentWillReceiveProps`。 * * * ### `shouldComponentUpdate()` ```javascript shouldComponentUpdate(nextProps, nextState) ``` 使用 `shouldComponentUpdate` 让React知道组件的输出不会受到当前状态和属性变更的影响。默认行为是当每次状态变更时都会引发重绘,绝大多数情况下,您应该依赖默认行为。 当收到新的状态或者属性时,将会调用 `shouldComponentUpdate()`。默认值是 `true`。在初次渲染或调用 `forceUpdate()` 时,`shouldComponentUpdate()` 将不会被调用。 返回 `false` 也不会阻止子组件在它们自己的状态变化时重绘。 目前,当 `shouldComponentUpdate()` 返回 `false`,[`componentWillUpdate()`](#componentwillupdate), [`render()`](#render), 和 [`componentDidUpdate()`](#componentdidupdate) 都不会被调用。请注意,未来React可能将 `shouldComponentUpdate()` 作为一个提示而不是一个强制指令,就算是返回 `false` 仍然有可能导致组件重绘。 如果你在监控分析之后,发现特定组件确实非常缓慢,那么可以将其修改为实现了使用浅表属性和状态比较的 `shouldComponentUpdate()` 方法的 `React.PureComponent`。如果你有信心手动确认,你可以比较 `this.props` 与 `nextProps` 以及 `this.state` 与 `nextState` 的值来告诉React本次更新是可以跳过的。 * * * ### `componentWillUpdate()` ```javascript componentWillUpdate(nextProps, nextState) ``` 当接收到新的属性或者状态时,`componentWillUpdate()` 将会在重绘前被立即调用。可以在此处放置一些重绘前的处理逻辑。这个方法在初次绘制时不会被调用。 注意你也不能在此处调用 `this.setState()`。如果你需要更新状态来响应属性变化,请使用 `componentWillReceiveProps()` 替代。 > **注意** > > 当 `shouldComponentUpdate()` 返回 false 时,`componentWillUpdate()` 将不会被调用。 * * * ### `componentDidUpdate()` ```javascript componentDidUpdate(prevProps, prevState) ``` 在更新完成之后,`componentDidUpdate()` 会被立即调用。初次绘制不会调用该方法。 在这里可以操作组件更新之后的DOM。在你比较了当前属性和上一次属性之后,这里也适合处理网络请求。(例如:如果属性没有变化,可能不需要一个网络请求。) > **注意** > > 当 `shouldComponentUpdate()` 返回 false 时,`componentWillUpdate()` 将不会被调用。 * * * ### `componentWillUnmount()` ```javascript componentWillUnmount() ``` 在组件卸载和释放之前,将会立即调用 `componentWillUnmount()`。可以在该方法中,执行一些清理动作,如无效的定时器,取消网络请求,或者是清理在 `componentDidMount()` 中创建的DOM元素。 * * * ### `setState()` ```javascript setState(nextState, callback) ``` 执行它会执行一次浅表复制,将 `nextState` 合并到当前状态上。这也是从事件处理函数和服务请求回调中触发UI更新的主要方法。 第一个参数可以是一个对象(包含0个或多个要更新的key)或者是一个返回包含更新key的对象的函数(传入状态和属性)。 简单对象用法如下: ```javascript this.setState({mykey: 'my new value'}); ``` 它也可能传递如下签名 `function(state, props) => newState` 的函数。这将引入一个原子更新,会在设置值之前先查看上一次的状态和属性。例如,我们想每次增加 `props.step` 指定的值: ```javascript this.setState((prevState, props) => { return {myInteger: prevState.myInteger + props.step}; }); ``` 第二个参数是一个可选的回调函数,它将在 `setState` 完成和组件重绘之后执行一次。通常,我们建议在 `componentDidUpdate()` 中处理这样的逻辑。 `setState()` 不会立即更新 `this.state`,仅会创建一个挂起的状态转换。调用此方法之后访问 `this.state` 可能会返回现有值。 不能确保 `setState` 是同步操作,多次调用 `setState` 可能会进行批处理以提高性能。 `setState()` 将总是导致重绘,除非 `shouldComponentUpdate()` 返回 `false`。如果使用可变对象且不能在 `shouldComponentUpdate()` 中实现条件重绘逻辑,那么可以仅在当前状态与之前不同时,再调用 `setState()` 来避免不必要的重绘。 * * * ### `forceUpdate()` ```javascript component.forceUpdate(callback) ``` 默认情况下,当你的组件状态和属性变化时,组件将会被重绘。如果你的 `render()` 方法中依赖一些其他的数据,你可以调用 `forceUpdate()` 来告诉React需要重绘。 调用 `forceUpdate()` 将导致组件跳过 `shouldComponentUpdate()` 直接调用 `render()`。这也将触发子组件正常的生命周期方法,包括每个子组件的 `shouldComponentUpdate()`。React仍然将只更新标记变化的DOM元素。 通常,你应该避免使用 `forceUpdate()`,仅在 `render()` 中只读取 `this.props` 和 `this.state`。 * * * ## 类属性 ### `defaultProps` 能够在组件类自身上定义 `defaultProps` 属性,它将为属性设置默认值。当属性未定义时才会被设置为默认属性值,如果属性值被设置为null,则不会被设置为默认属性值。示例如下: ```js class CustomButton extends React.Component { // ... } CustomButton.defaultProps = { color: 'blue' }; ``` 当 `props.color` 没有提供时,它将被设置为默认值 `'blue'`: ```js render() { return ; // props.color will be set to blue } ``` 如果 `props.color` 被设置为 null,它仍然是 null: ```js render() { return ; // props.color will remain null } ``` * * * ### `displayName` `displayName` 字符串用于调试信息。JSX 会自动设置这个值;查看 [深入JSX](【译】高级指南-深入JSX.md) 了解更多。 * * * ### `propTypes` 可以在组件类自身上定义 `propTypes` 属性,用来确定属性的类型。它是属性名称和定义在 `React.PropTypes` 中类型的一个映射。在开发模式中,如果碰到不合法的属性,那么控制台将会打印警告。产线模式下,属性类型检查基于性能考虑将会被跳过。 例如,以下代码确保 `color` 属性是字符串: ``` class CustomButton extends React.Component { // ... } CustomButton.propTypes = { color: React.PropTypes.string }; ``` 我们建议您尽可能的使用 [Flow](https://flow.org/),以便使用编译时的类型检查,而不是运行时的类型检查。查看 [Flow 对 React 的内置支持](https://flow.org/en/docs/frameworks/react/) 来轻松的在 React 应用中使用静态分析。 * * * ## 实例属性 ### `props` `this.props` 包含了组件调用者定义的属性。查看 [组件和属性](【译】快速起步-组件与属性.md) 了解更多。 `this.props.children` 是一个特别的属性,通常是由 JSX 表达式的子标签定义,而不是组件本身的标签。 ### `state` `state` 包含了组件特定的可能随时间变化而变化的数据。 `state` 是用户自定义的,它也是一个纯粹的JavaScript对象。 不需要在 `render()` 中使用的数据,不必要放在 `state` 上。例如,您可以直接在实例上存储定时器ID。 查看 [快速起步-状态和生命周期](【译】快速起步-状态和生命周期.md) 了解更多。 调用 `setState()` 来替代直接修改 `this.state`。保持 `this.state` 是不可变的。 ================================================ FILE: React面面观/【译】快速起步-JSX简介.md ================================================ --- title: 快速起步-JSX简介 date: 2017-4-14 14:12:50 react version: 15.5.0 --- # 快速起步-JSX简介 思考这个变量申明: ```js const element =

Hello, world!

; ``` 这个有趣的标签语法既不是字符串也不是HTML。 它被称之为 `JSX`,是 `JavaScript` 的语法扩展。我们建议使用它来定义React的UI展示。`JSX` 可能会让你想起模板语言,但它可以使用 `JavaScript` 的全部功能。 `JSX` 用于编写 `React "elements"`。在 [下一节](【译】快速起步-渲染元素.md),我们将探索如何将它们渲染到DOM中。下面,我们来了解下 `JSX` 的基础知识。 ### 在 `JSX` 中嵌入表达式 你可以在 `JSX` 中通过 `{xxx}` 来嵌入 [JavaScript 表达式](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Expressions_and_Operators#Expressions)。 例如,`2 + 2`, `user.firstName`, 和 `formatName(user)` 都是合法的表达式: ```js{12} function formatName(user) { return user.firstName + ' ' + user.lastName; } const user = { firstName: 'Harper', lastName: 'Perez' }; const element = (

Hello, {formatName(user)}!

); ReactDOM.render( element, document.getElementById('root') ); ``` [CodePen Demo](http://codepen.io/gaearon/pen/PGEjdG?editors=0010) 为了可读性,我们将 `JSX` 分割为多行。虽然这不是必须的,但在这样做的时候,我们建议将它放在 `{}` 中,以避免 [自动补全分号](http://stackoverflow.com/q/2846283)。 ### JSX 也是一个表达式 编译之后,JSX表达式会变成常规的 `JavaScript` 对象。 这意味着你可以在 `if` 语句和 `for` 循环内部使用 `JSX`,也可以将其作为参数传递或用来作为返回值: ```js{3,5} function getGreeting(user) { if (user) { return

Hello, {formatName(user)}!

; } return

Hello, Stranger.

; } ``` ### JSX中指定属性 您可以使用引号将字符串文本指定为属性: ```js const element =
; ``` 你也可以使用 `{}` 将 `JavaScript` 表达式作为属性: ```js const element = ; ``` 在属性中使用 `JavaScript` 表达式时,不要使用引号包裹大括号。否则,JSX会认为属性值是字符串而不是一个表达式。你可以对字符串使用双引号,对表达式使用花括号,但不能同时使用。 ### JSX中指定子集 如果是空标签,可以像XML那样使用自闭合标签 `/>`: ```js const element = ; ``` JSX 标签也可以包含子集: ```js const element = (

Hello!

Good to see you here.

); ``` >**警告:** > >由于 JSX 更趋近于 JavaScript 而不是 HTML,React DOM 使用 `camelCase`(小驼峰) 属性命名约定而不是HTML的属性名称。 > >例如:`class` 在JSX中是 [`className`](https://developer.mozilla.org/en-US/docs/Web/API/Element/className),`tabindex` 在JSX中是 [`tabIndex`](https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/tabIndex)。 ### JSX 防止注入攻击 在 `JSX` 中嵌入用户输入是安全的: ```js const title = response.potentiallyMaliciousInput; // 安全的: const element =

{title}

; ``` 默认情况下,React DOM 会在渲染前使用 [escapes](http://stackoverflow.com/questions/7381974/which-characters-need-to-be-escaped-on-html) 编码所有嵌入 `JSX` 的值。 因此它能确保您永远不会注入任何未明确写入应用程序的内容。所有内容都将在呈现前转换为字符串。这有助于防御 [XSS (cross-site-scripting)](https://en.wikipedia.org/wiki/Cross-site_scripting) 攻击。 ### JSX 代表对象 `Babel` 将 `JSX` 编译成 `React.createElement()` 调用。 这两个例子是等同的: ```js const element = (

Hello, world!

); ``` ```js const element = React.createElement( 'h1', {className: 'greeting'}, 'Hello, world!' ); ``` `React.createElement()` 会执行一些检查来帮助您编写无误的代码,但基本上,它是创建如下的对象: ```js // 注意:以下是简单结构 const element = { type: 'h1', props: { className: 'greeting', children: 'Hello, world' } }; ``` 这些对象称之为 "React elements". 你可以将它们视为您想要在屏幕上看到的内容。`React` 会读取这些对象,并使用它们来构造DOM且保持为最新状态。 下一节我们将探索如何渲染 `React elements` 到DOM中。 >**提示:** > >我们建议为编辑器选择 `Babel` 语法支持插件,以便 `ES6` 和 `JSX` 都能被高亮显示。 ================================================ FILE: React面面观/【译】快速起步-事件处理.md ================================================ --- title: 快速起步-事件处理 date: 2017-4-18 16:58:04 react version: 15.5.0 --- # 事件处理 使用React元素处理事件和DOM元素上的事件非常相似。但还是有一些语法上的不同: * React 的事件是小驼峰命名的,不是全小写命名的。 * 可以在JSX中传递函数作为事件处理器,而不是字符串。 例如: ```html ``` 在React中略微不同: ```js{1} ``` 另一个不同是,不能通过 `return false` 来阻止默认行为。必须要明确使用 `preventDefault` 。例如,在HTML中,要想阻止a标签的默认行为,可以如下写: ```html Click me ``` 但是在React中,你必须这样写: ```js{2-5,8} function ActionLink() { function handleClick(e) { e.preventDefault(); console.log('The link was clicked.'); } return ( Click me ); } ``` 在这里, `e` 是一个合成事件。`React` 根据 [W3C 标准](https://www.w3.org/TR/DOM-Level-3-Events/) 定义了这些合成事件,所以你不需要担心浏览器兼容性问题。查看 [SyntheticEvent](https://facebook.github.io/react/docs/events.html) 了解更多。 在使用 `React` 的时候,通常不需要调用 `addEventListener` 来给DOM元素添加事件。而是在元素最初渲染时提供一个监听器。 当你在使用 [ES6 class](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Classes) 定义组件时,一个常见的模式是将事件处理程序定义为类上的一个原型方法。例如,`Toggle` 渲染了一个可以在 "ON" 和 "OFF" 之间切换的按钮: ```js{6,7,10-14,18} class Toggle extends React.Component { constructor(props) { super(props); this.state = {isToggleOn: true}; // 必要的绑定,让 `this` 在回调中可用。 this.handleClick = this.handleClick.bind(this); } handleClick() { this.setState(prevState => ({ isToggleOn: !prevState.isToggleOn })); } render() { return ( ); } } ReactDOM.render( , document.getElementById('root') ); ``` [CodePen Demo](http://codepen.io/gaearon/pen/xEmzGg?editors=0010) 您必须要注意JSX回调中 `this` 的含义。在JavaScript中,默认情况下,类方法不是 [bound](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_objects/Function/bind) 的。如果你传递处理函数给 `onClick` 时忘记了绑定 `this` 给 `this.handleClick`,那么当函数被调用时,`this` 将会是 `undefined`。 这不是React特定的行为,它是 [JavaScript中函数如何工作](https://www.smashingmagazine.com/2014/01/understanding-javascript-function-prototype-bind/) 的一部分。通常,如果你引用了没有 `()` 的方法(方法引用),比如 `onClick={this.handleClick}`,那么你需要先 `bind(this)`。 如果调用 `bind` 让你觉得很繁琐,还有两种方法可以解决这个问题。如果你在使用实现性质的语法 [属性初始化](https://babeljs.io/docs/plugins/transform-class-properties/),你可以使用属性初始化来正确的绑定回调的 `this`: ```js{2-6} class LoggingButton extends React.Component { // This syntax ensures `this` is bound within handleClick. // Warning: this is *experimental* syntax. handleClick = () => { console.log('this is:', this); } render() { return ( ); } } ``` 在 [Create React App](https://github.com/facebookincubator/create-react-app) 中,这个语法是默认启用的。 如果你不想使用属性初始化语法,你还可以在回调中使用 [箭头函数](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Functions/Arrow_functions): ```js{7-9} class LoggingButton extends React.Component { handleClick() { console.log('this is:', this); } render() { // 这个语法确保 `this` 在 handleClick中指向正确 return ( ); } } ``` 这个语法的问题是,每次 `LoggingButton` 渲染时,都会创建一个不同的回调函数。大多数情况下,这都没啥问题。但是,如果这个回调函数会传递给层次更低的组件,那么可能会导致这些组件进行额外的渲染。我们通常建议在构造函数中使用绑定,或者使用属性初始化语法来避免这个问题。 ================================================ FILE: React面面观/【译】快速起步-列表与KEY.md ================================================ --- title: 快速起步-数组与KEY date: 2017-4-20 13:34:37 react version: 15.5.0 --- # 数组与KEY 首先,我们来看看如何在 `JavaScript` 中转换列表。 如下代码中,我们使用 `map()` 方法来让我们的数字数组值翻倍。我们通过 `map()` 方法得到了一个新数组,并打印: ```javascript{2} const numbers = [1, 2, 3, 4, 5]; const doubled = numbers.map((number) => number * 2); console.log(doubled); ``` 代码将会在控制台上打印:`[2, 4, 6, 8, 10]`。 在 `React` 中,将数组转换为 [elements](【译】快速起步-渲染元素.md) 列表,几乎和上面的代码一样。 ### 渲染多个组件 你可以构建元素集合,并使用 `{}` 将它们包含在 `JSX` 中。 下面,我们使用 [`map()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map) 来循环一个数字数组。并对每个项目都返回一个 `
  • ` 元素。最终将结果赋值给 `listItems`: ```javascript{2-4} const numbers = [1, 2, 3, 4, 5]; const listItems = numbers.map((number) =>
  • {number}
  • ); ``` 我们将整个 `listItems` 包含在 `
      ` 元素中,并渲染到DOM中: ```javascript{2} ReactDOM.render(
        {listItems}
      , document.getElementById('root') ); ``` [Try it on CodePen.](https://codepen.io/gaearon/pen/GjPyQr?editors=0011) 这个代码显示了 1-5 的数字列表。 ### 基础列表组件 通常你会在 [组件](【译】快速起步-组件与属性.md) 中显示列表。 我们将上一个示例重构到一个组件中,这个组件接收一个数字数组,并输出一个无序的元素列表。 ```javascript{3-5,7,13} function NumberList(props) { const numbers = props.numbers; const listItems = numbers.map((number) =>
    • {number}
    • ); return (
        {listItems}
      ); } const numbers = [1, 2, 3, 4, 5]; ReactDOM.render( , document.getElementById('root') ); ``` 当你运行这段代码时,你会得到 `a key should be provided for list items(应该为列表提供一个key)` 这样的警告。"Key" 是创建元素列表时,需要包含的特殊字符串属性。我们接下来将会讨论为什么 “Key” 对于数组是非常重要的。 我们可以在 `numbers.map()` 方法中给列表项传递一个 `key` 属性,这样可以修复该问题。 ```javascript{4} function NumberList(props) { const numbers = props.numbers; const listItems = numbers.map((number) =>
    • {number}
    • ); return (
        {listItems}
      ); } const numbers = [1, 2, 3, 4, 5]; ReactDOM.render( , document.getElementById('root') ); ``` [Try it on CodePen.](https://codepen.io/gaearon/pen/jrXYRR?editors=0011) ## Keys Keys 会帮助 React 识别哪些项是修改、新增或者是移除掉的。Keys 会给数组元素一个固定的标识: ```js{3} const numbers = [1, 2, 3, 4, 5]; const listItems = numbers.map((number) =>
    • {number}
    • ); ``` 最好的选择 Key 的方式是使用一个唯一字符串来标识列表项。最常用的做法是使用数据项的ID作为 Key: ```js{2} const todoItems = todos.map((todo) =>
    • {todo.text}
    • ); ``` 当需要渲染的项没有固定的ID时,你可以把项的索引当成 key: ```js{2,3} const todoItems = todos.map((todo, index) => // Only do this if items have no stable IDs
    • {todo.text}
    • ); ``` 我们并不建议使用索引来作为 key,这可能会很慢。如果有兴趣,可以阅读 [深入理解Key的必要性](https://facebook.github.io/react/docs/reconciliation.html#recursing-on-children)。 ### 带Key的组件拆分 Keys 仅仅在数组上下文才有意义。 例如,如果你拆分出一个 `ListItem` 组件,你需要将 Key 设置在 `` 元素上,而不是放在 `ListItem` 组件本身的 `
    • ` 上。(PS:设置在循环的ITEM上) **示例: 不正确的Key用法** ```javascript{4,5,14,15} function ListItem(props) { const value = props.value; return ( // 错误,这里并不需要指定key:
    • {value}
    • ); } function NumberList(props) { const numbers = props.numbers; const listItems = numbers.map((number) => // 错误,这个需要指定key: ); return (
        {listItems}
      ); } const numbers = [1, 2, 3, 4, 5]; ReactDOM.render( , document.getElementById('root') ); ``` **示例: 正确的 Key 用法** ```javascript{2,3,9,10} function ListItem(props) { // 正确,此处不需要指定key: return
    • {props.value}
    • ; } function NumberList(props) { const numbers = props.numbers; const listItems = numbers.map((number) => // 正确,应该在这里指定key: ); return (
        {listItems}
      ); } const numbers = [1, 2, 3, 4, 5]; ReactDOM.render( , document.getElementById('root') ); ``` [Try it on CodePen.](https://codepen.io/rthor/pen/QKzJKG?editors=0010) 一个很好的经验法则就是 `map()` 方法内的元素需要key。 ### Keys 必须在兄弟节点中唯一 数组元素中指定的 key 应该在当前列表中是独立无二的,但不需要全局唯一。我们可以在不同的数组中,使用相同的 key: ```js{2,5,11,12,19,21} function Blog(props) { const sidebar = (
        {props.posts.map((post) =>
      • {post.title}
      • )}
      ); const content = props.posts.map((post) =>

      {post.title}

      {post.content}

      ); return (
      {sidebar}
      {content}
      ); } const posts = [ {id: 1, title: 'Hello World', content: 'Welcome to learning React!'}, {id: 2, title: 'Installation', content: 'You can install React from npm.'} ]; ReactDOM.render( , document.getElementById('root') ); ``` [Try it on CodePen.](https://codepen.io/gaearon/pen/NRZYGN?editors=0010) Keys 为 React 提供了一些隐藏信息,但不会传递给您的组件。如果你的组件中需要相同的值,请通过其他不同的属性名称进行传递: ```js{3,4} const content = posts.map((post) => ); ``` 以上这个例子,`Post` 组件能够读取到 `props.id`,但读取不到 `props.key`。 ### 在 JSX 中嵌入 `map()` 在上面的例子中,我们定义了一个独立的 `listItems` 变量,并在 JSX 中进行了使用: ```js{3-6} function NumberList(props) { const numbers = props.numbers; const listItems = numbers.map((number) => ); return (
        {listItems}
      ); } ``` JSX 允许在 `{}` 中嵌入表达式,所以我们内联 `map()` 的结果: ```js{5-8} function NumberList(props) { const numbers = props.numbers; return (
        {numbers.map((number) => )}
      ); } ``` [Try it on CodePen.](https://codepen.io/gaearon/pen/BLvYrB?editors=0010) 有时这会让代码更清晰,但这种风格也可能被滥用。像JavaScript一样,是否值得提取为变量是由你决定的。请记住,如果 `map()` 嵌套太多,那么是时候进行组件拆分了。 ================================================ FILE: React面面观/【译】快速起步-条件渲染.md ================================================ --- title: 快速起步-条件渲染 date: 2017-4-20 15:38:23 react version: 15.5.0 --- # 条件渲染 在 `React` 中,你可以创建不同的组件来封装你需要的行为。然后,你可以通过应用程序的状态来决定渲染它们中的一部分。 `React` 中的条件渲染和 `JavaScript` 的条件表达式一致。使用像 [`if`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/if...else) 或者是 [条件运算符](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Operators/Conditional_Operator) 来创建表示当前状态的元素,并使用 `React` 更新UI。 思考下面两个组件: ```js function UserGreeting(props) { return

      Welcome back!

      ; } function GuestGreeting(props) { return

      Please sign up.

      ; } ``` 我们创建一个 `Greeting` 组件,它将根据用户的登录状态,显示以上两个组件中的一个: ```javascript{3-7,11,12} function Greeting(props) { const isLoggedIn = props.isLoggedIn; if (isLoggedIn) { return ; } return ; } ReactDOM.render( // 尝试设置 isLoggedIn={true}: , document.getElementById('root') ); ``` [Try it on CodePen.](https://codepen.io/gaearon/pen/ZpVxNq?editors=0011) 这个例子将根据 `isLoggedIn` 属性的值来渲染不同的问候语。 ### 元素变量 你可以使用变量来存储元素。这可以帮助您有条件的渲染组件的一部分,而其他的部分则不会更改。 思考下面两个表示注销和登录按钮的新组件: ```js function LoginButton(props) { return ( ); } function LogoutButton(props) { return ( ); } ``` 在上面的例子中,我们将会创建一个名为 `LoginControl` 的有状态组件。 根据它自身的状态,它会渲染 `` 或者 `` 中的一个。它也会渲染上一个例子中的 ``: ```javascript{20-25,29,30} class LoginControl extends React.Component { constructor(props) { super(props); this.handleLoginClick = this.handleLoginClick.bind(this); this.handleLogoutClick = this.handleLogoutClick.bind(this); this.state = {isLoggedIn: false}; } handleLoginClick() { this.setState({isLoggedIn: true}); } handleLogoutClick() { this.setState({isLoggedIn: false}); } render() { const isLoggedIn = this.state.isLoggedIn; let button = null; if (isLoggedIn) { button = ; } else { button = ; } return (
      {button}
      ); } } ReactDOM.render( , document.getElementById('root') ); ``` [Try it on CodePen.](https://codepen.io/gaearon/pen/QKzAgB?editors=0010) 虽然,申明一个变量,并使用 `if` 语句是进行条件渲染的好办法,但有时你可能想使用更短的语法。在 `JSX` 中有几种内联条件的写法,如下。 ### 逻辑 `&&` 操作符实现内联if `JSX` 允许在 `{}` 中嵌入表达式。这包含JS中的 `&&` 操作符。它有助于条件渲染一个元素: ```js{6-10} function Mailbox(props) { const unreadMessages = props.unreadMessages; return (

      Hello!

      {unreadMessages.length > 0 &&

      You have {unreadMessages.length} unread messages.

      }
      ); } const messages = ['React', 'Re: React', 'Re:Re: React']; ReactDOM.render( , document.getElementById('root') ); ``` [Try it on CodePen.](https://codepen.io/gaearon/pen/ozJddz?editors=0010) 它可以正常工作是因为在 `JavaScript`中,`true && expression` 总是执行 `expression`,且 `false && expression` 总是执行 `false`。(PS:短路表达式) 因此,如果条件是 `true`, `&&` 右侧的元素将会被渲染。如果为 `false`, `React` 将会跳过右侧的元素。 ### 三目表达式实现内联if...else 另一个内联条件渲染的方法是使用 `JavaScript` 中的三目运算符 [`condition ? true : false`](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Operators/Conditional_Operator)。 在下面的例子中,我们使用三目运算来条件渲染一小段文本。 ```javascript{5} render() { const isLoggedIn = this.state.isLoggedIn; return (
      The user is {isLoggedIn ? 'currently' : 'not'} logged in.
      ); } ``` 它也可以用于更大的表达式,虽然不够清晰: ```js{5,7,9} render() { const isLoggedIn = this.state.isLoggedIn; return (
      {isLoggedIn ? ( ) : ( )}
      ); } ``` 就像在 `JavaScript` 中一样,你可以根据您和您的团队的规范来选择更合适的风格。还需要记住,当条件变得越来越复杂的时候,那就应该进行组件拆分了。 ### 阻止组件渲染 在极少数情况下,您可能希望组件隐藏自身,即使它是由另一个组件呈现的。为此,我们可以在 `render()` 中返回 `null` 来替代返回渲染元素。 在下面的示例中,`` 依赖 `warn` 属性的值来进行渲染。如果属性值是 `false`, 组件将不会被渲染: ```javascript{2-4,29} function WarningBanner(props) { if (!props.warn) { return null; } return (
      Warning!
      ); } class Page extends React.Component { constructor(props) { super(props); this.state = {showWarning: true} this.handleToggleClick = this.handleToggleClick.bind(this); } handleToggleClick() { this.setState(prevState => ({ showWarning: !prevState.showWarning })); } render() { return (
      ); } } ReactDOM.render( , document.getElementById('root') ); ``` [Try it on CodePen.](https://codepen.io/gaearon/pen/Xjoqwm?editors=0010) 从组件的 `render` 方法中返回 `null`,不会影响组件生命周期方法的触发。例如,`componentWillUpdate` 和 `componentDidUpdate` 仍然会被调用。 ================================================ FILE: React面面观/【译】快速起步-渲染元素.md ================================================ --- title: 快速起步-渲染元素 date: 2017-4-14 14:10:50 react version: 15.5.0 --- # 渲染元素 `Elements` 是 `React` 应用中的最小单元。 一个元素定义了你会在屏幕上看到什么: ```js const element =

      Hello, world

      ; ``` React 元素是很容易创建的纯对象,不同于浏览器的DOM元素。React DOM 负责从 React元素来更新DOM。 >**注意:** > >人们可能会将元素与更广为人知的组件混淆。我们将在 [下一节](【译】快速起步-组件与属性.md) 讨论组件。组件是由元素构成的,请不要跳过阅读本章节。 ## 渲染元素到DOM中 在您的HTML文件中有一个 `
      ` : ```html
      ``` 我们称它为根节点,因为它的内容都将被React DOM管理。 使用 `React` 构建的应用程序通常拥有单个根节点。如果你是整合 `React` 到已经存在的应用中,你可以以你喜欢的方式使用多个独立的根节点。 通过 `ReactDOM.render()` 来将 `React 元素` 渲染到 根节点中: ```js const element =

      Hello, world

      ; ReactDOM.render( element, document.getElementById('root') ); ``` [CodePen Demo](http://codepen.io/gaearon/pen/rrpgNB?editors=1010) 它会在页面上显示 "Hello, world"。 ## 更新渲染完成的元素 React的元素是 [不可变的](https://en.wikipedia.org/wiki/Immutable_object). 当你创建好一个元素后,你就不能改变它的子集和它的属性。一个元素就代表了某个时间点的UI,就像电影中的帧一样。 到目前我们所知,更新UI的唯一方式就是创建一个新元素,并传递给 `ReactDOM.render()`。 以滴答作响的时钟为例: ```js{8-11} function tick() { const element = (

      Hello, world!

      It is {new Date().toLocaleTimeString()}.

      ); ReactDOM.render( element, document.getElementById('root') ); } setInterval(tick, 1000); ``` [CodePen Demo](http://codepen.io/gaearon/pen/gwoJZk?editors=0010) 通过 [`setInterval()`](https://developer.mozilla.org/en-US/docs/Web/API/WindowTimers/setInterval) 的回调函数,每一秒都会调用 `ReactDOM.render()`。 >**注意:** > >在实际运用中,许多React应用都仅仅只会调用一次 `ReactDOM.render()`。在下一节,我们将会学习如何将这些代码封装为 [有状态组件](【译】快速起步-状态和生命周期.md)。 > >我们建议不要跳过某些主题,因为它们彼此之间具有联系。 ## React 只更新有必要的部分 React DOM将元素及其子元素与上一个进行比较,然后仅将变化了的内容更新到指定DOM上。 可以通过浏览器DOM检查工具来验证 [Demo](http://codepen.io/gaearon/pen/gwoJZk?editors=0010) 的局部更新,![DOM inspector 展示具体更新](https://facebook.github.io/react/img/docs/granular-dom-updates.gif)。 即使我们每时每刻都创建一个表示整个UI树的元素,也只有内容有变化的文本节点才会被React DOM更新。 以我们的经验,考虑UI在某个时刻的展现而不是考虑如何随时间去改变UI,可能会避免很多bug。 ================================================ FILE: React面面观/【译】快速起步-状态和生命周期.md ================================================ --- title: 快速起步-状态和生命周期 date: 2017-4-13 16:43:28 version: 15.4.2 --- # 状态和生命周期 到目前为止,我们仅学到了一种更新UI的方法。 我们通过调用 `ReactDOM.render()` 来改变渲染输出: ```js{8-11} function tick() { const element = (

      Hello, world!

      It is {new Date().toLocaleTimeString()}.

      ); ReactDOM.render( element, document.getElementById('root') ); } setInterval(tick, 1000); ``` [CodePen地址](http://codepen.io/gaearon/pen/gwoJZk?editors=0010) 在本节中,我们将学习如何使得 `Clock` 组件是可重用的和封装的。 它将创建自己的计时器并每秒更新一次。 我们可以用如下方式进行封装: ```js{3-6,12} function Clock(props) { return (

      Hello, world!

      It is {props.date.toLocaleTimeString()}.

      ); } function tick() { ReactDOM.render( , document.getElementById('root') ); } setInterval(tick, 1000); ``` [CodePen地址](http://codepen.io/gaearon/pen/dpdoYR?editors=0010) 然而,它缺失了一个关键要求:在 `Clock` 组件的具体实现中,需要能创建它自己的定时器,并能每秒更新UI。 理想情况下,我们只需要写一次即可实现自更新的 `Clock`: ```js{2} ReactDOM.render( , document.getElementById('root') ); ``` 想要实现这个目标,我们需要给 `Clock` 组件添加 `state`。 `state` 和 `props` 类似,但它是组件内部管理的的私有变量。 我们 [之前提到](https://facebook.github.io/react/docs/components-and-props.html#functional-and-class-components),当使用 `Class` 定义组件时,可以使用一些附加的特性。本地状态就是这样的特性,仅在用 `Class` 定义组件时可用。 ## 将 `Function` 转换为 `Class` 使用以下五步,可以将组件 `Clock` 从函数定义方式转换到类定义方式: 1. 创建一个同名的继承自 `React.Component` 的 [ES6 class](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Classes)。 2. 在类中添加空的 `render()`方法。 3. 将函数体内的代码复制到 `render()` 方法中。 4. 在 `render()` 方法中使用 `this.props` 来替代 `props`。 5. 删除多余的空函数定义。 ```js class Clock extends React.Component { render() { return (

      Hello, world!

      It is {this.props.date.toLocaleTimeString()}.

      ); } } ``` [CodePen地址](http://codepen.io/gaearon/pen/zKRGpo?editors=0010) 现在 `Clock` 组件是用类定义的,而不是函数定义的。 这让我们可以使用一些附加特性,如本地状态和生命周期钩子。 ## 添加本地状态到类 通过以下三个步骤,我们将会把 `date` 从 `props` 移动到 `state` 中: 1) 在 `render()` 方法中,使用 `this.state.date` 来替代 `this.props.date` : ```js{6} class Clock extends React.Component { render() { return (

      Hello, world!

      It is {this.state.date.toLocaleTimeString()}.

      ); } } ``` 2) 添加 [class constructor](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Classes#Constructor) 并初始化 `this.state`: ```js{4} class Clock extends React.Component { constructor(props) { super(props); this.state = {date: new Date()}; } render() { return (

      Hello, world!

      It is {this.state.date.toLocaleTimeString()}.

      ); } } ``` 注意我们是如何传递 `props` 到基类构造函数的: ```js{2} constructor(props) { super(props); this.state = {date: new Date()}; } ``` 类组件应始终传递 `props` 并调用基类构造函数。 3) 从 `` 元素中移除 `date` 属性: ```js{2} ReactDOM.render( , document.getElementById('root') ); ``` 稍后,我们将定时器代码块添加到组件本身上。 代码看起来如下: ```js{2-5,11,18} class Clock extends React.Component { constructor(props) { super(props); this.state = {date: new Date()}; } render() { return (

      Hello, world!

      It is {this.state.date.toLocaleTimeString()}.

      ); } } ReactDOM.render( , document.getElementById('root') ); ``` [CodePen地址](http://codepen.io/gaearon/pen/KgQpJd?editors=0010) 接下来,我们将使 `Clock` 启动自己的定时器,并每秒更新一次。 ## 将生命周期方法添加到类 在包含多个组件的应用程序中,在组件释放时,销毁(清理)组件所占用的资源是非常重要的。 我们希望当 `Clock` 渲染到DOM时,第一时间 [启动定时器](https://developer.mozilla.org/en-US/docs/Web/API/WindowTimers/setInterval)。这个阶段在 `React` 中称之为 `mounting`。 我们也希望当 `Clock` 产生的DOM被移除时 [清除定时器](https://developer.mozilla.org/en-US/docs/Web/API/WindowTimers/clearInterval) 。这个阶段在 `React` 中称之为 `unmounting`。 在组件的 `mounting` 和 `unmounting` 阶段,我们可以定义特定的方法来运行我们的代码: ```js{7-9,11-13} class Clock extends React.Component { constructor(props) { super(props); this.state = {date: new Date()}; } componentDidMount() { } componentWillUnmount() { } render() { return (

      Hello, world!

      It is {this.state.date.toLocaleTimeString()}.

      ); } } ``` 这些方法就被称之为生命周期钩子(lifecycle hooks)。 `componentDidMount()` 钩子在组件渲染到DOM之后运行,这是启动定时器的好地方: ```js{2-5} componentDidMount() { this.timerID = setInterval( () => this.tick(), 1000 ); } ``` **注意我们是如何在 `this` 上保存定时器ID的** `this.props` 是由 `React` 自身管理的,`this.state` 也具有特定的意义。当您需要存储与渲染无关的内容时,也可以手动的在类中添加其他字段。 与 `render()` 无关的内容,没必要存储在 `state` 中。 我们将会在 `componentWillUnmount` 生命周期钩子中清除定时器: ```js{2} componentWillUnmount() { clearInterval(this.timerID); } ``` 最后,我们将实现每秒运行的 `tick()` 方法。 它将使用 `this.setState()` 来实现组件本地状态的更新: ```js{18-22} class Clock extends React.Component { constructor(props) { super(props); this.state = {date: new Date()}; } componentDidMount() { this.timerID = setInterval( () => this.tick(), 1000 ); } componentWillUnmount() { clearInterval(this.timerID); } tick() { this.setState({ date: new Date() }); } render() { return (

      Hello, world!

      It is {this.state.date.toLocaleTimeString()}.

      ); } } ReactDOM.render( , document.getElementById('root') ); ``` [CodePen地址](http://codepen.io/gaearon/pen/amqdNA?editors=0010) 现在 `Clock` 会每秒跳动。 我们来简单回顾下发生了什么以及调用方法的顺序: 1) 当 `` 传递给 `ReactDOM.render()` 时,`React` 调用了 `Clock` 组件的构造函数。由于 `Clock` 需要显示当前时间,我们通过一个包含当前时间的对象来初始化 `this.state`。稍后,我们将更新这个 `state`。 2) 接下来,`React` 调用 `Clock` 组件的 `render()` 方法。这决定了 `React` 将会在屏幕上显示什么。接着,React 将组件的输出更新到DOM上。 3) 当 `Clock` 组件被添加到DOM之后,React会调用 `componentDidMount()` 钩子函数。 在它的内部, `Clock` 组件启动浏览器定时器,并每秒执行一次 `tick()`。 4) 每一秒,浏览器都会调用 `tick()` 方法。在它的内部,`Clock` 组件将包含当前时间的对象传递给 `this.setState()`,并以此来调度UI变更。当调用 `setState()` 方法,`React` 知道了 `state` 变化,然后就调用 `render()` 来确定要在屏幕上渲染的内容。这个时候, `render()` 中的 `this.state.date` 将和之前不同,会产生包含当前时间的新的输出。于是,React则对应的更新DOM。 5) 当 `Clock` 组件从DOM移除时,`React` 将调用 `componentWillUnmount()` 来停止定时器。 ## 正确的使用 `state` 关于 `setState()`,您需要知道以下三点: ### 不要直接修改 `state` 如下代码将不会重新渲染组件: ```js // Wrong this.state.comment = 'Hello'; ``` 请使用 `setState()` 替代: ```js // Correct this.setState({comment: 'Hello'}); ``` 唯一可以给 `this.state` 赋值的地方是构造函数。 ### 状态更新可能是异步的 为了提高性能,React可能会在单个更新中批量处理多个 `setState()`。 由于 `this.props` 和 `this.state` 可能是异步更新的,您不应该依靠它们的值来计算下一个状态。 以下代码可能不会正确更新计数器: ```js // Wrong this.setState({ counter: this.state.counter + this.props.increment, }); ``` 要解决这个问题,可以 `setState()` 的第二种形式,接受一个函数。这个函数的第一个参数是上一个状态,第二个参数是当前属性: ```js // Correct this.setState((prevState, props) => ({ counter: prevState.counter + props.increment })); ``` 以上代码我们使用了 [arrow function](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Functions/Arrow_functions) ,但常规函数也是可以正常使用的: ```js // Correct this.setState(function(prevState, props) { return { counter: prevState.counter + props.increment }; }); ``` ### 状态是合并更新 当你调用 `setState()` 时,React 会将您传递的对象合并到当前状态。 您的状态可能包含几个独立变量,如下: ```js{4,5} constructor(props) { super(props); this.state = { posts: [], comments: [] }; } ``` 您可以通过 `setState()` 更新其中一个: ```js{4,10} componentDidMount() { fetchPosts().then(response => { this.setState({ posts: response.posts }); }); fetchComments().then(response => { this.setState({ comments: response.comments }); }); } ``` 这个合并是浅复制,所以 `this.setState({comments})` 完全不影响 `this.state.posts` ,但会整个替换 `this.state.comments`。 ## 数据流向 父组件和子组件都不必要知道某个组件是有状态的还是无状态的,并且它们不应该关心它是否被定义函数或类。 这也是为什么 `state` 通常被称为本地状态或封装状态。因为除了组件本身之外,其他组件都是不能直接访问组件状态的。 父组件可以选择将状态通过子组件的属性进行传递: ```js

      It is {this.state.date.toLocaleTimeString()}.

      ``` This also works for user-defined components: ```js ``` `FormattedDate` 组件将通过它自身的 `props` 接收到 `date`,不需要知道 `date` 是来自哪里(`Clock` 组件的状态或属性,乃至手动输入): ```js function FormattedDate(props) { return

      It is {props.date.toLocaleTimeString()}.

      ; } ``` [CodePen地址](http://codepen.io/gaearon/pen/zKRqNB?editors=0010) 这通常被称作 `从上到下` 或 `单向` 数据流。任何 `state` 始终由某个特定组件拥有,并且从该 `state` 导出的任何数据或UI都只会影响子集。 如果把组件树想象为瀑布,每个组件的状态都是一个额外的水源,它可以从任一点汇入,但都向下流动。 为了表明组件都是相互隔离的,我们可以创建一个包含三个 `Clock` 组件的 `App` 组件: ```js{4-6} function App() { return (
      ); } ReactDOM.render( , document.getElementById('root') ); ``` [CodePen地址](http://codepen.io/gaearon/pen/vXdGmd?editors=0010) 每个 `React` 有自己的定时器,并独立更新(并不会相互影响)。 在 `React` 应用中,组件有无状态被认为是可变的,它是组件的具体实现细节。你可以在有状态组件中使用无状态组件,反之亦然。 ================================================ FILE: React面面观/【译】快速起步-状态提升.md ================================================ --- title: 快速起步-状态提升 date: 2017-4-25 17:27:42 version: 15.5.0 --- # 状态提升 通常,多个组件会响应同样的变化数据。我们建议将这种共享的状态提升到最近的共同的祖先。让我们看看它是如何工作的。 在这一节中,我们会创建一个温度计算器用来计算水是否在给定的温度下沸腾。 我们先创建一个 `BoilingVerdict`(沸腾判断)组件。它有一个叫 `celsius` (摄氏温度)的属性,并会打印水是否沸腾: ```js{3,5} function BoilingVerdict(props) { if (props.celsius >= 100) { return

      The water would boil.

      ; } return

      The water would not boil.

      ; } ``` 下一步,我们创建一个叫 `Calculator`(计算器)的组件。它会渲染一个可让你输入摄氏温度的输入框,并将该输入框的值绑定到 `this.state.temperature` 上。 另外,它也会通过输入框的值渲染 `BoilingVerdict` 组件。 ```js{5,9,13,17-21} class Calculator extends React.Component { constructor(props) { super(props); this.handleChange = this.handleChange.bind(this); this.state = {temperature: ''}; } handleChange(e) { this.setState({temperature: e.target.value}); } render() { const temperature = this.state.temperature; return (
      Enter temperature in Celsius:
      ); } } ``` [Try it on CodePen.](http://codepen.io/valscion/pen/VpZJRZ?editors=0010) ## 添加第二个输入 我们的新需求是,我们不仅要提供摄氏温度输入,还需要提供华氏温度输入,并要自动进行转换。 我们从 `Calculator` 提取出一个叫 `TemperatureInput` 的组件,并运行传入 `"c"` 或者 `"f"` 给它的属性 `scale`: ```js{1-4,19,22} const scaleNames = { c: 'Celsius', f: 'Fahrenheit' }; class TemperatureInput extends React.Component { constructor(props) { super(props); this.handleChange = this.handleChange.bind(this); this.state = {temperature: ''}; } handleChange(e) { this.setState({temperature: e.target.value}); } render() { const temperature = this.state.temperature; const scale = this.props.scale; return (
      Enter temperature in {scaleNames[scale]}:
      ); } } ``` 现在我们来更新 `Calculator`,让它渲染两个独立的温度输入组件: ```js{5,6} class Calculator extends React.Component { render() { return (
      ); } } ``` [Try it on CodePen.](http://codepen.io/valscion/pen/GWKbao?editors=0010) 现在,我们有两个输入框了,但是当我们在其中一个输入值时,另外一个并不会更新。这违背了我们的需求,我们想让它们保持同步。 此时,我们也不能从 `Calculator` 中展示 `BoilingVerdict`。 因为 `Calculator` 并不知道当前的温度,温度是隐藏在 `TemperatureInput` 组件内部的。 ## 编写转换功能 首先,我们先编写两个函数来对华氏温度和摄氏温度进行转换: ```js function toCelsius(fahrenheit) { return (fahrenheit - 32) * 5 / 9; } function toFahrenheit(celsius) { return (celsius * 9 / 5) + 32; } ``` 这两个函数将会转换不同的温度。我们还需要编写另一个函数,它将字符串 `temperature`(温度)和一个转换器作为参数,并返回一个字符串。我们将使用它来根据一个输入计算另外一个输入。 当 `temperature`(温度)不合法时,它会返回一个空字符串,它也会四舍五入到小数点后第三位: ```js function tryConvert(temperature, convert) { const input = parseFloat(temperature); if (Number.isNaN(input)) { return ''; } const output = convert(input); const rounded = Math.round(output * 1000) / 1000; return rounded.toString(); } ``` 比如,`tryConvert('abc', toCelsius)` 返回空字符串,`tryConvert('10.22', toFahrenheit)` 返回 `'50.396'`。 ## 状态提升 目前,所有的 `TemperatureInput` 组件都将值作为本地状态保存在组件内部: ```js{5,9,13} class TemperatureInput extends React.Component { constructor(props) { super(props); this.handleChange = this.handleChange.bind(this); this.state = {temperature: ''}; } handleChange(e) { this.setState({temperature: e.target.value}); } render() { const temperature = this.state.temperature; ``` 然而,我们希望它们能够相互同步这个值。当我们更新摄氏温度时,华氏温度也会自动显示,反之亦然。 在 `React` 中,共享状态是通过将状态移动的最近的共同祖先来实现的。这被称之为 “状态提升”。我们将从 `TemperatureInput` 组件中移除本地状态,并在 `Calculator` 组件中保存状态。 如果 `Calculator` 拥有共享状态,它就成为了多个温度输入组件的的 “source of truth”(译者理解:唯一来源)。它可以让输入组件具有彼此一致的值。自从将 `TemperatureInput` 组件的属性提取到共同的父组件 `Calculator` 后,它们的输入值会总是同步的。 我们看看看它是如何一步步开始工作的。 首先,我们会在 `TemperatureInput` 组件中,使用 `this.props.temperature` 来替换 `this.state.temperature`。现在,先假设 `this.props.temperature` 总是存在的。将来,我们会在 `Calculator` 组件中传递给它: ```js{3} render() { // Before: const temperature = this.state.temperature; const temperature = this.props.temperature; ``` 我们知道 [属性是只读的](【译】快速起步-组件与属性.md)。当 `temperature` 是本地状态时,`TemperatureInput` 组件通过 `this.setState()` 来改变它。然而,`temperature` 成为了父组件的属性,`TemperatureInput` 组件就没有该属性的控制权了。 在 `React` 中,通常让组件 `controlled` 来解决该问题。就像DOM中的 `` 接收一个 `value` 和 `onChange` 属性,自定义的 `TemperatureInput` 会从 `Calculator`(父组件)中接收 `temperature` 和 `onTemperatureChange`。 现在,当 `TemperatureInput` 想更新它的温度,可以调用 `this.props.onTemperatureChange`: ```js{3} handleChange(e) { // Before: this.setState({temperature: e.target.value}); this.props.onTemperatureChange(e.target.value); ``` 注意自定义组件中的 `temperature` 和 `onTemperatureChange` 没有特殊的含义。我们也可以用其他名称代替,比如 `value` 和 `onChange` 这种常用的惯例。 组件中的 `onTemperatureChange` 属性将会通知父组件 `Calculator` 温度变化。父组件将通过修改自己的本地状态来处理更新,并为两个输入组件提供新的值。我们将很快看到新的 `Calculator` 实现。 在更新 `Calculator` 之前,我们先更新 `TemperatureInput`。先移除它的本地状态,使用 `this.props.temperature` 来替代`this.state.temperature`。同时,我们调用 `Calculator` 提供的 `this.props.onTemperatureChange()` 来替代自身的 `this.setState()`: ```js{8,12} class TemperatureInput extends React.Component { constructor(props) { super(props); this.handleChange = this.handleChange.bind(this); } handleChange(e) { this.props.onTemperatureChange(e.target.value); } render() { const temperature = this.props.temperature; const scale = this.props.scale; return (
      Enter temperature in {scaleNames[scale]}:
      ); } } ``` 现在,我们切换到 `Calculator` 组件。 我们将当前输入组件的 `temperature` 和 `scale` 存储到本地状态中。这是从输入组件中提升的状态,并为输入组件提供真实的值。为了呈现两个输入,我们需要知道所有数据的最小表示。 例如,我们在摄氏温度中输入了 37 ,`Calculator` 组件的状态将会是: ```js { temperature: '37', scale: 'c' } ``` 如果我们之后在华氏温度中输入了 212,`Calculator` 组件的状态将会是: ```js { temperature: '212', scale: 'f' } ``` 我们可以存储两个输入的值,但实际上是不必要的。存储最近更改的输入值,以及它所代表的比例就足够了。我们可以基于当前的温度(temperature)和温度类别(scale)来推断另一个输入的值。 这将是输入保持同步,因为它们的值是从相同的状态计算出来的: ```js{6,10,14,18-21,27-28,31-32,34} class Calculator extends React.Component { constructor(props) { super(props); this.handleCelsiusChange = this.handleCelsiusChange.bind(this); this.handleFahrenheitChange = this.handleFahrenheitChange.bind(this); this.state = {temperature: '', scale: 'c'}; } handleCelsiusChange(temperature) { this.setState({scale: 'c', temperature}); } handleFahrenheitChange(temperature) { this.setState({scale: 'f', temperature}); } render() { const scale = this.state.scale; const temperature = this.state.temperature; const celsius = scale === 'f' ? tryConvert(temperature, toCelsius) : temperature; const fahrenheit = scale === 'c' ? tryConvert(temperature, toFahrenheit) : temperature; return (
      ); } } ``` [Try it on CodePen.](http://codepen.io/valscion/pen/jBNjja?editors=0010) 现在,无论你是在哪个温度输入组件中输入,`Calculator` 组件中的 `this.state.temperature` 和 `this.state.scale` 都会更新。其中一个输入值,另一个的输入值总是基于它重新计算,所以随便输入哪个,值都会生效。 让我们回顾一下编辑输入框时会发生什么: * React会调用DOM元素 `` 上的 `onChange` 函数。在我们的示例中, 是 `TemperatureInput` 组件的 `handleChange` 方法。 * `TemperatureInput` 组件的 `handleChange` 方法会调用 `this.props.onTemperatureChange()` 并传入新的期望值。这是由父组件 `Calculator` 传递来的属性。 * 在渲染呈现之前,如果在 `TemperatureInput` 组件中输入摄氏温度,`onTemperatureChange` 将会调用父组件的 `handleCelsiusChange` 方法,如果是输入的华氏温度,`onTemperatureChange` 将会调用父组件的 `handleFahrenheitChange` 方法。 因此,会根据具体的输入,调用这两个方法中的一个。 * 在这些方法内部,`Calculator` 组件通过 `this.setState()` 通知 `React` 使用最新的输入值来重新渲染它自己。 * React 调用 `Calculator` 组件的 `render` 方法来确定需要展示的UI。所有输入框的值,都将通过当前温度和温度类型来重新计算。温度就在这个阶段进行转换的。 * 当 `Calculator` 传递给 `TemperatureInput` 的属性发生变化时,React调用 `TemperatureInput` 组件的 `render()` 方法来确定如何渲染。 * 最终,React DOM 使用期望的输入值,来更新DOM。在我们更新一个的时候,另一个输入框也就同步更新。 每个更新都会执行相同的步骤,以便输入保持同步。 ## 经验教训 对于在 `React` 中更改的任何数据,应该有一个唯一的来源。通常,状态首先会被添加到使用它的组件中。然后,当其他组件也需要它的时候,可以将它提升到最近的公共祖先上,而不是尝试同步这些状态。我们应该使用 [单向数据流](【译】快速起步-状态和生命周期.md)。 提升状态会比双向绑定编写更多的样板代码,但它有一个好处,不容易制造bug。提升状态到单一组件后,仅有该组件可以修改它,这导致错误的可能性大大降低。此外,您可以实现任何自定义的逻辑来拒绝或者转换用户的输入。 如果一些东西可以通过 `props` 或者 `state` 得到,那么他可能并不需要放在共享状态中。例如,我们并没有存储 `celsiusValue` 和 `fahrenheitValue`,而是存储最后编辑的 `temperature` 和 `scale`。其他的值总是可以在 `render()` 中被计算出来。这使得我们可以清理或者四舍五入到其他字段,而不会在用户输入中丢失精度。 当你在UI中看到错误时,您可以使用 [React Developer Tools](https://github.com/facebook/react-devtools) 来检查属性,并向上查找组件树直至到负责更新的组件上。这可以让你跟踪这些错误来源: Monitoring State in React DevTools ================================================ FILE: React面面观/【译】快速起步-组件与属性.md ================================================ --- title: 快速起步-组件与属性 date: 2017-4-14 16:01:04 version: 15.5.0 --- # 组件与属性 组件允许您将UI拆分为多个独立的,可重用的部分。 概念上,组件就类似于 `JavaScript` 函数。它们接收任意的输入(`props`),返回用于描述屏幕显示内容的 `React elements`。 ## 函数式组件和类组件 最简单定义组件的方式,是编写一个 `JavaScript` 函数: ```js function Welcome(props) { return

      Hello, {props.name}

      ; } ``` 这个函数是一个合法的组件,因为它接收包含数据的单个 `props` 对象参数,返回了一个 `React elements`。我们称这种用JavaScript函数定义的组件为函数式组件。 你也可以使用 [ES6 class](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Classes) 来定义组件: ```js class Welcome extends React.Component { render() { return

      Hello, {this.props.name}

      ; } } ``` 上述两个组件在视图展示上是等同的。 在 [下一章节](【译】快速起步-状态和生命周期.md) 我们会讨论类组件的一些附加特性。在这之前,我们先使用函数式组件来简化演示。 ## 渲染组件 之前,我们仅仅遇到表示DOM标签的 `React elements`: ```js const element =
      ; ``` 然而,元素也可以表示用户定义的组件: ```js const element = ; ``` 当 `React` 发现元素表示一个用户定义的组件时,它将 `JSX` 属性作为单个对象传递给组件。我们称之为 `props`。 例如,以下代码在页面上渲染 "Hello, Sara": ```js{1,5} function Welcome(props) { return

      Hello, {props.name}

      ; } const element = ; ReactDOM.render( element, document.getElementById('root') ); ``` [CodePen Demo](http://codepen.io/gaearon/pen/YGYmEG?editors=0010) 我们回顾下这个例子中发生了什么: 1. 使用 `` 元素调用 `ReactDOM.render()`。 2. React 将 `{name: 'Sara'}` 作为属性调用 `Welcome` 组件。 3. `Welcome` 组件将 `

      Hello, Sara

      ` 元素作为结果返回。 4. React DOM 使用 `

      Hello, Sara

      ` 来更新DOM。 >**警告:** > >总是用大写字母开头命名自定义组件 > >例如, `
      ` 表示一个DOM标签,但是 `` 表示一个组件,且需要作用域中存在 `Welcome` 变量。 ## 复合组件 组件可以在其输出中引用其他组件。这使我们可以对不同级别的实现抽象为相同的组件。在 `React` 中,按钮、表单、对话框,屏幕都通常被抽象为组件。 例如,我们可以创建 `App` 组件来渲染多个 `Welcome` 组件: ```js{8-10} function Welcome(props) { return

      Hello, {props.name}

      ; } function App() { return (
      ); } ReactDOM.render( , document.getElementById('root') ); ``` [CodePen Demo](http://codepen.io/gaearon/pen/KgQKPr?editors=0010) 通常,新的React应用程序在最顶层都会有一个 `App` 组件。然而,如果你集成 `React` 到一个现有的应用程序,你可以在内部先使用像 `Button` 这样的小部件,然后逐步的替换到最顶层。 >**警告:** > >组件必须返回单个根元素。这是为什么我们用 `
      ` 来包含多个 `` 组件。 ## 提取组件 不要害怕将组件分成更小的组件。 例如,思考这个 `Comment` 组件: ```js function Comment(props) { return (
      {props.author.name}
      {props.author.name}
      {props.text}
      {formatDate(props.date)}
      ); } ``` [CodePen Demo](http://codepen.io/gaearon/pen/VKQwEo?editors=0010) 它接收 `author`(对象类型),`text`(字符串)和 `date`(日期) 作为属性,然后在社交网站上呈现一个评论。 由于多层嵌套,这个组件很难变化,且很难重用它其中的各个部分。我们可以从中提取几个组件。 首先,我们提取 `Avatar` 组件: ```js{3-6} function Avatar(props) { return ( {props.user.name} ); } ``` `Avatar` 不需要知道谁会渲染它。这也是为什么我们使用 `user` 这个更通用的属性名称来替代 `author`。 我们建议从组件自身的角度来命名属性,而不是它的使用者。 我们现在已经稍微简化了一下 `Comment` 组件: ```js{5} function Comment(props) { return (
      {props.author.name}
      {props.text}
      {formatDate(props.date)}
      ); } ``` 下一步,我们将提取 `UserInfo` 组件,用于在用户名旁边显示 `Avator`: ```js{3-8} function UserInfo(props) { return (
      {props.user.name}
      ); } ``` 这让我们进一步简化了 `Comment` 组件: ```js{4} function Comment(props) { return (
      {props.text}
      {formatDate(props.date)}
      ); } ``` [CodePen Demo](http://codepen.io/gaearon/pen/rrJNJY?editors=0010) 提取组件可能是看起来很繁琐的工作,但是在较大的应用程序中,可重用的组件能够付出更小的代价。 如果您的UI的一部分使用了几次(按钮,面板,头像)或者它自身就很复杂(App, Comment),它们则是拆分出可重用组件的最佳候选人,这是一个相当实用的经验法则。 ## 属性是只读的 当你定义一个组件时,它不应该修改自己的 `props`。思考下面的 `sum` 函数。 ```js function sum(a, b) { return a + b; } ``` 这些不会修改输入参数,且当输入参数相同时,总是返回相同结果的函数被称之为 [纯函数](https://en.wikipedia.org/wiki/Pure_function)。 相比之下,以下的函数是不纯的,因为它改变了自己的输入: ```js function withdraw(account, amount) { account.total -= amount; } ``` `React` 非常灵活,但是也有一个严格的规则: **所有的React组件必须像纯函数那样使用 `props`(也就是不要在组件内部修改 `props`)** 当然,应用程序的UI是动态的,可以随时间的推移而变化。在 [下一节](【译】快速起步-状态和生命周期.md) 中,我们将介绍新的概念 `state`。`state` 允许React组件根据用户操作,网络响应以及其他随时间推移产生的变化来修改组件输出,而不会违背该规则。 ================================================ FILE: React面面观/【译】快速起步-组合与继承.md ================================================ --- title: 快速起步-组合与继承 date: 2017-4-26 13:19:51 version: 15.5.0 --- # 组合 VS. 继承 `React` 具有很强大的组合模型,我们推荐使用组合而不是继承来重用组件之间的代码。 在这一章节中,我们将思考一些React新手通常遇到的问题,并展示如何使用组合来解决它们。 ## 容器 一些组件并不能提前知道它们的子集。这在通用盒子组件,如 `Sidebar` 或者 `Dialog` 上尤其常见。 我们推荐这类组件使用特定的 `children` 属性,将子元素传递到其输出中: ```js{4} function FancyBorder(props) { return (
      {props.children}
      ); } ``` 这允许其他组件通过嵌套JSX传递任意的子集到组件内部: ```js{4-9} function WelcomeDialog() { return (

      Welcome

      Thank you for visiting our spacecraft!

      ); } ``` [Try it on CodePen.](http://codepen.io/gaearon/pen/ozqNOV?editors=0010) 任何JSX标签 `` 内部的内容,都会作为 `children` 属性传递给 `FancyBorder` 组件。由于 `FancyBorder` 会在 `
      ` 中渲染 `{props.children}`,传递的元素将会出现在最终输出中。 虽然这不太常见,但有时您可能需要在组件中设置多个“孔”(holes,译者注:插槽)。在这种情况下,您可以采用您自己的约定,而不是使用 `children`: ```js{5,8,18,21} function SplitPane(props) { return (
      {props.left}
      {props.right}
      ); } function App() { return ( } right={ } /> ); } ``` [Try it on CodePen.](http://codepen.io/gaearon/pen/gwZOJp?editors=0010) 像 `` 和 `` 这样的React元素只是对象,所以你可以像传递其他数据一样将其作为属性传递。 ## 具象化 有时我们认为某些组件是其他组件的“特殊情况”。比如,我们认为 `WelcomeDialog` 是一个特殊的 `Dialog`。 在React中,这也可以通过组合来实现,其中更“特殊”的组件会渲染更“通用”的组件,并且使用属性来配置通用组件: ```js{5,8,16-18} function Dialog(props) { return (

      {props.title}

      {props.message}

      ); } function WelcomeDialog() { return ( ); } ``` [Try it on CodePen.](http://codepen.io/gaearon/pen/kkEaOZ?editors=0010) 组合对应定义为类的组件同样适用: ```js{10,27-31} function Dialog(props) { return (

      {props.title}

      {props.message}

      {props.children}
      ); } class SignUpDialog extends React.Component { constructor(props) { super(props); this.handleChange = this.handleChange.bind(this); this.handleSignUp = this.handleSignUp.bind(this); this.state = {login: ''}; } render() { return ( ); } handleChange(e) { this.setState({login: e.target.value}); } handleSignUp() { alert(`Welcome aboard, ${this.state.login}!`); } } ``` [Try it on CodePen.](http://codepen.io/gaearon/pen/gwZbYa?editors=0010) ## 那么继承呢? 在Facebook,我们在数以千计的组件中使用React,并且我们没有发现任何用例会要求我们创建组件继承层次结构。 当你需要用安全的方式定制组件的外观和形式时,属性和组合给了你所有的灵活性。 请记住,组件接受任意props,包括原始值、React元素或函数。 如果你想在组件之中重用非UI的功能,我们建议您将其提取到单独的 `JavaScript` 模块中。组件可以导入它,并在不扩展它的情况下,使用哪些函数,对象或者是类。 ================================================ FILE: React面面观/【译】快速起步-表单.md ================================================ --- title: 快速起步-表单 date: 2017-4-28 08:35:09 version: 15.5.0 --- # 表单 HTML的表单元素和React中的表单元素有所不同,因为表单元素会保持一些内部状态。例如,以下是一个接收单个name的纯HTML表单: ```html
      ``` 当用户提交表单时,该表单具有打开新页面的默认行为。这种用法放在React中也是可用的。但是,大多数时候,我们希望使用JavaScript函数处理表单提交并能方便的访问用户输入的表单数据。实现这一点的标准方式是使用一种称之为受控组件(controlled components)的技术。 ## 受控组件 在HTML中,诸如 ``, ` ``` 在React中,`