[
  {
    "path": ".Net Platform/01_Dotnet Core尝鲜.md",
    "content": "---\ntitle: 01_Dotnet Core尝鲜\ndate: 2017/02/21 14:47:10\n---\n\n# 0、About Dotnet Core\n\n`Dotnet Core` 是新一代的 `.Net Framework`，是一个具有跨平台能力的应用程序开发框架。它本身是由多个子项目组成的。包括 ``Core Fx``、``Core CLR``、``.Net Compiler Platform`` 等等。\n\n`Dotnet Core` 具有高效的开发效率，高性能和跨平台能力，是 ``.Net平台`` 的一次大跃进。\n\n# 1、尝试 Dotnet Core\n\n### 1.1、Install\n\n``Dotnet Core`` 从发布至今，已经有很长一段时间了。期间也发布了beta，rc等版本。就在前不久，正式版也已经发布了，经过了之前大量的api变化，现在core已经非常稳定了。这个阶段，已经值得我们去尝试、去使用它了。\n\n要尝试 ``Dotnet core``, 我们先进入它的网站[https://dotnet.github.io/](https://dotnet.github.io/)，[https://www.microsoft.com/net/core#windows](https://www.microsoft.com/net/core#windows) 。\n\n根据我们的操作系统版本，选择合适的开发包。我这里是Windows下开发，理所当然的下载 [the .NET Core SDK for Windows](https://go.microsoft.com/fwlink/?LinkID=809122) 。\n\n安装好之后，在命令行输入 ``dotnet --version`` ，如果输出了版本信息，那就表示安装成功了。\n\n**注意：如果之前尝试过Dotnet Core，请保证在安装最新版本的SDK之前，先卸载干净。**\n\n### 1.2、Console App\n\n在安装好SDK之后，我们就可以开始创建项目了。新建一个文件夹，进入控制台，执行 ``dotnet new``，即可看到在目录下生成了如下文件结构：\n\n```\nProgram.cs\nproject.json\n```\n\n内容如下：\n\n```csharp\n//Program.cs\n\nusing System;\n\nnamespace ConsoleApplication\n{\n    public class Program\n    {\n        public static void Main(string[] args)\n        {\n            Console.WriteLine(\"Hello World!\");\n        }\n    }\n}\n\n```\n\n```json\n//project.json\n\n{\n  \"version\": \"1.0.0-*\",\n  \"buildOptions\": {\n    \"debugType\": \"portable\",\n    \"emitEntryPoint\": true\n  },\n  \"dependencies\": {},\n  \"frameworks\": {\n    \"netcoreapp1.0\": {\n      \"dependencies\": {\n        \"Microsoft.NETCore.App\": {\n          \"type\": \"platform\",\n          \"version\": \"1.0.0\"\n        }\n      },\n      \"imports\": \"dnxcore50\"\n    }\n  }\n}\n\n```\n\n此时我们可以通过 ``dotnet restore`` 来安装依赖。\n\n**注意：就算project.json中的dependencies属性为空对象，我们也要执行 ``dotnet restore``，该命令会生成project.lock.json文件。**\n\n**注意2：如果我们的网络环境的走的代理，那么可能会在安装依赖这个步骤遇到407错误，此时我们需要配置Nuget的代理设置，找到 ``%AppData%/NuGet/NuGet.Config`` 文件，然后添加如下配置项：**\n\n```xml\n<configuration>\n  <config>\n    <add key=\"http_proxy\" value=\"s1firewall:8080\" />\n    <add key=\"http_proxy.user\" value=\"jh3r\" />\n    <add key=\"http_proxy.password\" value=\"xxx\" />\n  </config>\n</configuration>\n```\n\n**通过这样的设置，就可以使用代理来安装依赖了。**\n\n当我们安装好依赖之后，通过执行 ``dotnet run`` 来编译和运行我们的程序，此时可以在控制台看到输出： ``Hello World!``\n\n以上，就是我们使用 ``Dotnet Core`` 的一般步骤了。\n\n### 1.3、Web App\n\n在尝试了Console App之后，我们也来试试Web App 在 ``Dotnet Core`` 下是如何运行的。\n\n首先，基本步骤如上，先创建一个项目模板。\n\n接着，首先要使用Web功能，我们需要指定依赖，在project.json中的dependencies属性中增加依赖：\n\n```\n\"dependencies\": {\n  \"Microsoft.AspNetCore.Server.Kestrel\": \"1.0.0\"\n}\n```\n\n**注意：在设定依赖的时候，一定要注意版本号，如果依赖库的版本号不兼容Core的版本，那么很可能会出现一些莫名其妙的错误，而找不到原因。**\n\n增加依赖之后，我们再次通过 ``dotnet restore`` 来安装依赖。\n\n这个时候，我们来编写Web宿主程序，内容如下：\n\n```csharp\n//Program.cs\n\nusing System;\nusing Microsoft.AspNetCore.Hosting;\nnamespace WebApplication\n{\n    public class Program\n    {\n        public static void Main(string[] args)\n        {\n            var host = new WebHostBuilder()\n                .UseKestrel()\n                .UseStartup<Startup>() //此处使用了类型Startup，来自于Startup.cs\n                .UseUrls(\"http://localhost: 10000\")\n                .Build();\n            host.Run();\n        }\n    }\n}\n```\n\n```csharp\n//Startup.cs\n\nusing System;\nusing Microsoft.AspNetCore.Builder;\nusing Microsoft.AspNetCore.Http;\n\nnamespace WebApplication\n{\n    public class Startup\n    {\n        public void Configure(IApplicationBuilder app)\n        {\n            app.Run((context) => \n            {\n                return context.Response.WriteAsync(\"Hello, From Core.\");\n            });\n        }\n    }\n}\n```\n\n编写好如上代码之后，我们在执行 ``dotnet run``，访问 ``http://localhost:10000`` 就可以看到我们的Web程序已经成功运行起来了。\n\n以上则是一个简单的Web程序所需要的代码。\n\n# 2、Other\n\n### 2.1、``Dotnet Core`` 与 ``Node.js`` 的性能测试。\n\n在 ``Dotnet Core`` 的官方宣传中，号称比 ``Node.js`` 快8倍。实际上，通过计算从0到100000000的累加来看，两者的差距并不大（几十ms的差别，Node稍弱）。\n\n由于时间环境关系，没有进行更复杂测试，但我想在高并发下，Node的机制可能会有更高的性能。在大量IO操作处理中，``Dotnet Core``会有绝对的优势【后续可验证】。\n\n### 2.2、如何直接运行Dotnet Core程序？\n\n在开发模式下，我们通过 ``dotnet run`` 来运行程序，那我们如何来运行发布好的程序呢？\n\n首先，我们可以通过 ``dotnet publish`` 来生成好我们的应用程序（在Windows下生成的是dll，其他平台未测试）。\n\n在发布模式，我们的程序所依赖的包，也会被一同发布到目录下，我们可以在 ``/appRoot/bin/Debug/netcoreapp1.0/publish`` 中找到我们发布好的文件。\n\n此时，我们就可以将publish目录拷贝到其他电脑运行了。\n\n由于发布好的文件入口点是 ``.dll`` 文件，我们要运行它的话，需要通过 ``dotnet xxx.dll`` 来进行启动。\n\n**注意：在publish的时候，我们可以使用参数``dotnet publish -c Release`` 生成Release版本的发布包，目录对应变更为 ``bin/Release/``**\n\n**注意2：请不要删除publish目录下的文件，否则可能导致无法运行。经测试，<appName>.deps.json 和 <appName>.pdb可以删除，但依赖和 <appName>.runtimeconfig.json是绝对不能删除的。**\n\n### 2.3 后续\n\n此文为 ``Dotnet Core`` 系列第一篇，后续计划将Web开发所需要用到的一些基本知识点，库等均在 ``Dotnet Core`` 调试通，且成文。\n\n**加油，``Dotnet!``**\n\n\n**【6.30号更新】**\n\n### 2.4、如何创建web项目\n\n在 1.3 中，我们知道如何把一个Console App 改造为一个Web项目，但这对应开发一个Web应用来说还不够。\n\n其实，``dotnet new`` 可以默认创建 Web 项目开发模板。\n\n通过 ``dotnet new -t --help`` 我们可以看到 ``dotnet new`` 能帮我们创建的项目类型有如下四种：\n\n1. Console\n2. Web\n3. Lib\n4. xunittest\n\n我们可以直接通过 ``dotnet new -t Web`` 来创建一个 Web 项目模板，简单快捷。\n\n### 2.5、如果在Linux下发布（CentOS7）\n\n``Dotnet Core`` 开发的程序，具有跨平台能力，那如何在非Windows上发布呢？各大操作系统方式并不同。\n\n在CentOS7（仅支持7+）上发布非常简单。\n\n首先是在CentOS7上安装 ``Dotnet Core``，不知道如何安装？请查阅 [https://www.microsoft.com/net/core#centos](https://www.microsoft.com/net/core#centos) 。\n\n安装好之后，只需要在Windows把开发好的程序，通过 ``dotnet publish`` 生成发布目录，然后将该目录拷贝到CentOS上即可。\n\n最后，在CentOS上执行 ``dotnet xxx.dll`` 即可运行项目了。\n\n**注意： xxx.dll是你开发的项目的主程序**"
  },
  {
    "path": ".Net Platform/02_Dotnet Core V2.md",
    "content": "---\ntitle: 02_Dotnet Core V2\ndate: 2017-05-20 09:14:21\n---\n\n# 前言\n\n时隔三个月，再来看 `.Net Core`，已经到 `2.x preview` 了，虽然这个版本有赶工的嫌疑，但并不妨碍它成为当前的主力版本（可用API翻倍，API相对更稳定）。\n\n在 `.Net Core` 的开发中，可以采用 `VSC` 或者是 `VS`，在我个人体验来看，如果要开发稍大的（一个解决方案，多个子项目）项目，还是选择VS吧。对于`.Net Core` ，一个简单的编辑器，体验还是太弱了。\n\n**注意：要在VS中使用Core2.x Preview，请安装 [VS 15.3 Preview](https://www.visualstudio.com/vs/preview/) 版本。**\n\n# V2命令行\n\n在安装好 [.Net Core 2.0 Preview 1](https://www.microsoft.com/net/core/preview#windowscmd) 之后，通过 `dotnet new -h` 可以发现可以创建更多的模板了，其中一部分是项目模板：\n\n1. Console Application （控制台应用程序）\n2. Class Library （类库）\n3. Unit Test Project （使用微软测试库的单元测试）\n4. xUnit Test Project （使用xUnit作为测试库的单元测试）\n5. ASP.NET Core Empty （Core的空项目）\n6. ASP.NET Core Web App(MVC) （网站项目-Application）\n7. ASP.NET Core Web App(Razor Pages) （网站项目 - WebPage，和Application不一样的是一个cshtml对应一个.cs，两者之前的区别类似.Net的WebApplication和WebPage）\n8. ASP.NET Core Web API （Web API项目）\n\n还有另外一部分是一些其他的单个文件，如：\n\n1. Nuget Config\n2. Web Config\n3. Solution Config\n4. Razor Page\n5. MVC ViewImports\n6. MVC ViewStart\n\n我们最常用的还是上面的一些项目模板（如果用VS，这都不是事~）\n\n除了这些，还提供了不少的命令行，不用去死记硬背，用的时候通过帮助命令查看使用说明即可，如 `dotnet -h` 列出所有的命令， `dotnet build -h` 列出命令的具体用法。\n\n# Console App\n\n在升级到2.0之后，曾今的 `project.json` 就不再使用了，回归到了 `.csproj` 这样的项目文件，但这个和 `.Net` 中的 `.csproj` 并不一样，最大的一个区别是，`.Net Core` 中，并不会将文件路径映射到 `.csproj` 中。这对于开发Web非常有好处（现代Web有构建等一大套工具链）。\n\n通过 `dotnet new console -o ConsoleDemo` （创建一个控制台项目，并输出到ConsoleDemo目录中）创建一个项目，我们可以看到一个基本的 `ConsoleDemo.csproj` 内容如下：\n\n```\n<Project Sdk=\"Microsoft.NET.Sdk\">\n\n  <PropertyGroup>\n    <OutputType>Exe</OutputType>\n    <TargetFramework>netcoreapp2.0</TargetFramework>\n  </PropertyGroup>\n\n</Project>\n```\n\n仅仅是标记一下我们的Framework版本。\n\n**Console不做过多尝试，相信和.Net差不多。更多的，我们还是使用 `.Net Core` 来做Web应用。``\n\n# Web Application\n\n我们还是先使用常用的MVC方式创建一个WebApplication，`dotnet new mvc -o MvcDemo`，创建好之后，可以看到如下结构：\n\n```\nControllers/ --标准MVC三大目录\nModels/ --标准MVC三大目录\nViews/ --标准MVC三大目录\nwwwroot/ --此处用来放置静态资源\n.bowerrc --Bower配置文件\nappsettings.Development.json 特定环境配置文件（Development环境）\nappsettings.json 标准配置文件\nbower.json  -- Bower项目文件\nbundleconfig.json -- 资源打包配置\nMvcDemo.csproj -- 项目文件\nProgram.cs -- 自托管入口文件\nStartup.cs -- 启动配置\n```\n\n其中，`bower` 相关和 `bundleconfig`，并没有什么大用，直接干掉。至于 `bower` 是什么，查看这里：[Bower官网](https://bower.io/)。\n\n抛开MVC三大目录不说，我是建议创建一个src目录，用于放置相关的前端资源文件（JS,CSS,LESS,TS等），然后通过构建打包工具（gulp，webpack这类），将代码生成到 `wwwroot` 中。\n\n另，需要注意项目配置文件 `appsettings.json` 是采用的类似继承的方式读取值，也就是说：最终的配置 = (appSettings.json).extend(appsettings.<Env>.json)。如果将环境设置为 `Development`，那么会根据节点，先从 `appsettings.Development.json` 中找，找不到则从 `appsettings.json` 中找。 \n\n注意看 `Program.cs` 和 `Startup.cs` 的内容。其中 `Program.cs` 是非常独立的一个文件，仅依赖 `Startup.cs`，而 `Startup.cs` 则全是Web相关的配置文件。\n\n看一下 `Program.cs` 的关键代码：\n\n```csharp\nnamespace MvcDemo\n{\n    public class Program\n    {\n        public static void Main(string[] args)\n        {\n            BuildWebHost(args).Run();\n        }\n\n        public static IWebHost BuildWebHost(string[] args) =>\n            WebHost.CreateDefaultBuilder(args)\n                .UseStartup<Startup>()\n                .Build();\n    }\n}\n```\n\n为什么是这样的结构呢？`.Net Core` 支持IIS托管和自托管。在 `.Net` 中，我们知道Web程序是不会有 `Program.cs`，也就没有 `Main` 方法的。\n\n实际上，这个文件在 `.Net Core` 中的作用就是自托管Web程序，从它的内容中，可以理解到自托管就是利用控制台程序创建了一个WebHost，然后将我们Web程序的真正入口文件 `Startup` 传递到Web容器中运行。\n\n在IIS中托管程序的话，这个 `Program.cs` 通过调试，发现也会执行，但其中的一些配置则不会生效，如：`.UseUrls(\"http://*:5000\")`。\n\n**注意：具体在IIS中是如何执行的，还有待分析。**\n\n## Web Application QA\n\n1、如何设置环境？\n\n在上面的内容中，有提到实际配置和环境是有关系的，那我们如何设定环境呢？最简单的方式，就是在 `Program.cs` 中，通过如下方式设置：\n\n```csharp\npublic static IWebHost BuildWebHost(string[] args) =>\n    WebHost.CreateDefaultBuilder(args)\n    .UseEnvironment(\"Development\") // 设置环境\n        .UseStartup<Startup>()\n        .Build();\n```\n\n2、如果在自托管的时候设置多个IP/端口绑定？\n\n同上，也是在 `Program.cs` 中进行配置:\n\n```csharp\npublic static IWebHost BuildWebHost(string[] args) =>\n    WebHost.CreateDefaultBuilder(args)\n    .UseUrls(\"http://*:5000;http://*:6000\") // 多个用分号隔开\n        .UseStartup<Startup>()\n        .Build();\n```\n\n3、如何在视图中根据不同的环境加载不同的内容？\n\nMVC为我们提供了相关的Helper，所以我们可以在View（一般是Layout视图）中，通过 `<environment></environment>` 来实现不同的内容加载，如下：\n\n```html\n<environment include=\"Development\">\n    <!-- 当环境是Development时加载 -->\n    <link rel=\"stylesheet\" href=\"~/static/mdl/material.css\">\n</environment>\n<environment exclude=\"Development\">\n    <!-- 当环境不是Development时加载 -->\n    <link rel=\"stylesheet\" href=\"~/static/mdl/material.css\">\n</environment>\n```\n\n4、既然有配置文件，那我们就需要读取配置，应该如何读取呢？\n\n在 `.Net Core` 中，配置是以JSON字符串的方式存在的，那么我们怎么来读取呢？\n\n在 `Startup.cs` 中，构造函数中，注入了一个 `IConfiguration` 对象，并赋值到了这个类的 `Configuration` 属性上，这个属性就类似于 `.Net` 下的 `ConfigurationManager`。所以我们可以用它来进行读取，\n\n假设有如下配置文件：\n\n```json\n{\n    ...忽略默认配置,\n    \"v1\": \"xxx\",\n    \"s2\": {\n        \"v2\": 1\n    },\n    \"s3\": {\n        \"Key1\": \"key1\",\n        \"Key2\": \"key2\"\n    }\n}\n```\n\n那么读取方式如下：\n\n```csharp\nConfiguration.GetValue<string>('v1'); // 读取指定的配置项，返回 \"xxx\"\n\nConfiguration.GetSection('s2').GetValue<int>('v2'); // 从嵌套对象中读取配置项, 返回 1\n```\n\n当配置项过多的时候，这种方式可以比较麻烦，这个时候，我们可以创建一个配置类来映射，如下：\n\n```csharp\npublic class S3\n{\n    public string Key1{ get; set; }\n\n    public string Key2{ get; set; }\n}\n\n// 然后我们可以获取到s3这个节点，然后绑定到S3上。\nvar s3 = new S3();\nConfiguration .GetSection(\"s3\").Bind(s3);\nConsole.WriteLine(s3.Key1); // 可以打印出 \"key1\" 了。\n```\n\n# 总结\n\n`.Net Core` 正在变得越来越好，赶紧跟上吧~\n"
  },
  {
    "path": ".Net Platform/C#中处理耗时任务的几种方式.md",
    "content": "---\ntitle: C#中处理耗时任务的几种方式\ndate: 2017/02/21 14:47:10\n---\n\n## 0、准备\n首先，我们先创建几个耗时任务：\n\n\tpublic class TestTasks\n\t{\n\t\t//无参、无返回值任务\n\t\tpublic void Task1()\n\t\t{\n\t\t    Console.WriteLine(\"task1.\");\n\t\t    Thread.Sleep(5000);\n\t\t    Console.WriteLine(\"task1 completed.\");\n\t\t}\n\t\n\t\t//有参数、无返回值任务\n\t\tpublic void Task2(int x)\n\t\t{\n\t\t\tif (x < 2000) \n\t\t\t{\n\t\t\t\tx += 2000;\n\t\t\t}\n\t\t    Console.WriteLine(\"task2.\");\n\t\t    Thread.Sleep(x);\n\t\t    Console.WriteLine(\"task2 completed.\");\n\t\t}\n\t\n\t\t//有参数，有返回值任务\n\t\tpublic int Task3(int x)\n\t    {\n\t        if (x < 2000)\n\t        {\n\t            x += 2000;\n\t        }\n\t        Console.WriteLine(\"task3.\");\n\t        Thread.Sleep(x);\n\t        Console.WriteLine(\"task3 completed.\");\n\t        return x;\n\t    }\n\t}\n\n## 1、创建新线程执行方法\n\n\tvar tt = new TestTasks();\n\n\tnew Thread(tt.Task1).Start();\n\n\t//针对有参数的任务，需要用Lambda进行包装或者使用ParameterizedThreadStart对象\n\tnew Thread(x=>tt.Task2((int)x)).Start((object)1000);\n\n\t//使用ParameterizedThreadStart，要求要执行的方法参数必须为object，同时无返回值。\n\t//new Thread(new ParameterizedThreadStart(tt.Task2)).Start((object)1000);\n\n**注意：使用该方式无法执行带返回值的方法。**\n\n**推荐指数：★★**\n\n## 2、使用异步调用方式执行方法\n\n\tvar tt = new TestTasks();\n\n\tAction ac = tt.Task1;\n\tAction<int> ac2 = tt.Task2;\n\tac.BeginInvoke(null, null);\n\tac2.BeginInvoke(1000, null, null);\n\t\n\t//以下是调用有参数，有返回值的方法\n\t//代码一\n\tprivate delegate int AsyncCaller(int x); //该代码放在方法体外部\n\t\n    AsyncCaller ac = new AsyncCaller(tt.Task3);\n    var asyncResult =  ac.BeginInvoke(1000,null,null);\n    int result = ac.EndInvoke(asyncResult); //接收返回值\n\n\t//代码二，使用Func简化代码\n    Func<int,int> ac = tt.Task3;\n    var asyncResult =  ac.BeginInvoke(1000,null,null);\n    int result = ac.EndInvoke(asyncResult);\n\n**注意：通过这种方式生成新线程是运行在后台的（background）,优先级为normal**\n\n**推荐指数：★★**\n\n## 3、通过ThreadPool（线程池）执行方法\n\n\tvar tt = new TestTasks();\n          \n\tThreadPool.QueueUserWorkItem(o => tt.Task1());\n\tThreadPool.QueueUserWorkItem(o => tt.Task2(1000));\n\n**注意：该方式不支持返回值，可以将返回值保存在引入类型的参数上，然后进行迂回实现**\n\n**推荐指数：★★★**\n\n## 4、通过BackgroundWorker（后台Worker）执行方法\n\n\tvar tt = new TestTasks();\n\t\n\tvar bw = new BackgroundWorker();\n\tbw.DoWork += (sender, e) => tt.Task1();\n\tbw.DoWork += (sender, e) => tt.Task2(1000);\t\n\n\t//要接收返回值，必须将返回值赋值给Result。\n\tbw.DoWork += (sender, e) => e.Result = tt.Task3(1000);\n\n\tbw.RunWorkerAsync();\n\n\t//注册事件使用返回值\n\tbw.RunWorkerCompleted += (sender, e) => Console.WriteLine(e.Result);\n\n**注意：使用BackgroundWorker注册DoWork事件的任务只能挨个执行，如果要同时执行多个任务，需要多个BackgroundWorker。要使用返回值，一定要记得赋值给Result。**\n\n**推荐指数：★★**\n\n## 5、同时Task执行方法\n\n\tvar tt = new TestTasks();\n\t\n\tvar t1 = Task.Factory.StartNew(tt.Task1);\n\tvar t2 = Task.Factory.StartNew(() => tt.Task2(1000));\n\tvar t3 =Task.Factory.StartNew(() => tt.Task3(1000));\n\n\t//等待t1,t2,t3执行完成\n\tTask.WaitAll(t1,t2,t3);\n\tConsole.WriteLine(t3.Result);\n\n**注意：Task具有灵活的控制能力，同时可以单个等待，多个等待。**\n\n**推荐指数：★★★★★**\n\n## 6、使用async/await执行方法\n\n\tprivate async void AsyncRunTask()\n\t{\n\t    var tt = new TestTasks();\n\t    await Task.Factory.StartNew(tt.Task1);\n\t    await Task.Factory.StartNew(() => tt.Task2(1000));\n\t    var result = await Task.Factory.StartNew(() => tt.Task3(1000));\n\t    Console.WriteLine(result);\n\t}\n\n\tAsyncRunTask();\n\tConsole.WriteLine(\"不用等待，我先执行了。\");\n\n**注意：需要Framework4.5的支持**\n\n**推荐指数：★★★★**\n\n## The End\n\n**没有原理，没有言语，相信以大家聪明的大脑，已经学会如何在C#中执行耗时任务和使用多线程了。**\n\n"
  },
  {
    "path": ".Net Platform/[20140913]可替代反射的几种方式.md",
    "content": "---\ntitle: C#可替代反射的几种方式\ndate: 2014/09/13\n---\n\n##标准的反射代码##\n\n\tvar type = obj.GetType();\n\tvar fieldInfo = type.GetField(\"age\", BindingFlags.Instance | BindingFlags.NonPublic);\n\tfieldInfo.SetValue(obj, 20);\n\t//  Console.WriteLine(\"设置年龄成功：{0}\", (obj as ModelTest).Age);\n\tvar s1 = type.InvokeMember(\"TestMethod1\", BindingFlags.InvokeMethod, null, obj, null);\n\tvar s2 = type.InvokeMember(\"TestMethod2\", BindingFlags.InvokeMethod | BindingFlags.NonPublic | BindingFlags.Instance, null, obj, null);\n\t// Console.WriteLine(s1);\n\t// Console.WriteLine(s2);\n###说明###\n1. 能动态获取对象属性、方法、字段等信息\n2. 能访问对象的私有字段，属性，方法等成员\n3. 能动态修改对象属性\n4. **注：**访问public方法使用BindingFlags.InvokeMethod，访问私有方法时，必须加上BindingFlags.NonPublic|BindingFlags.Instance，否则会出现找不到方法的异常\n\n##采用dynamic对象达到发射的效果##\n\tdynamic d = obj;\n    var s1 = d.TestMethod1();\n    Console.WriteLine(s1);\n\n###说明###\n1. 可使用公开的属性，字段，方法等成员\n2. 代码足够简洁\n3. **注：**不能访问非公开的成员\n\n##依赖Microsoft的测试组件Microsoft.VisualStudio.TestTools.UnitTesting来达到反射的效果##\n\tvar privateObj = new PrivateObject(obj);\n\tprivateObj.SetField(\"age\", 20);\n\tvar age = privateObj.GetProperty(\"Age\");\n\tConsole.WriteLine(age);\n\tprivateObj.Invoke(\"TestMethod1\");\n\tprivateObj.Invoke(\"TestMethod2\");\n###说明###\n1. 采用第三方组件实现\n\n##性能说明##\n测试代码如下：\n\tprivate static void Main(string[] args)\n\t{\n\t    RunSpecialTest(new SimpleReflection(), 1000);\n\t    RunSpecialTest(new DynamicReflection(), 1000);\n\t    RunSpecialTest(new PrivateObjectReflection(), 1000);\n\t    Console.ReadKey();\n\t}\n\t\n\tprivate static void RunSpecialTest(ITest test, int runCount)\n\t{\n\t    var modelTest = new ModelTest();\n\t    var stopwatch = new Stopwatch();\n\t    stopwatch.Start();\n\t    for (int i = 0; i < runCount; i++)\n\t    {\n\t        test.TestRun(modelTest);\n\t    }\n\t    stopwatch.Stop();\n\t    Console.WriteLine(\"运行{0} {1}次，共耗时：{2}ms\", test.Name, runCount, stopwatch.ElapsedMilliseconds);\n\t}\n\n结果：\n\n运行SimpleReflection 1000次，共耗时：2ms\n\n运行DynamicReflection 1000次，共耗时：717ms\n\n运行PrivateObjectReflection 1000次，共耗时：14ms\n\n\n##疑问##\n1. 采用标准的反射用法，除了第一次耗时较慢意外，后面耗时都很短，可以说是最快的方式，猜测是缓存，具体未知。\n2. PrivateObjectReflection这个和发射有类似的情况，初次慢，后面快。何解？"
  },
  {
    "path": ".Net Platform/实战Asp.Net Core：DI生命周期.md",
    "content": "---\ntitle: 实战Asp.Net Core：DI生命周期\ndate: 2018-11-30 21:54:52\n---\n\n# 1、前言\n\n`Asp.Net Core` 默认支持 `DI（依赖注入）` 软件设计模式，那使用 `DI` 的过程中，我们势必会接触到对象的生命周期，那么几种不同的对象生命周期到底是怎么样的呢？我们拿代码说话。\n\n**关于 DI 与 IOC：**\n\n个人理解：`IOC（控制反转）` 是目的（降低代码、服务间的耦合），而 `DI` 是达到该目的的一种手段（具体办法）。\n\n# 2、DI生命周期\n\nDI的生命周期，根据框架、库的不同，会略有差异。此处，我们就以微软的DI扩展为例，来说下DI中常用的几种生命周期。\n\n首先，我们想象一个这样一个场景。假设我们有寄快递的需求，那么我们会致电快递公司：“我们要寄快递，派一个快递员过来收货”。接着，快递公司会如何做呢？\n\n1. 一直派遣同一个快递员来收货。\n2. 第一周派遣快递员A、第二周派遣快递员B收货。\n3. 每次都派遣一个新的快递员收货。\n\n这对应到生命周期就是：\n\n1. 单例（Singleton），单一实例，每次使用都是该实例。\n2. 作用域实例（Scoped），在一个作用域（比如单次请求）内是同一个实例，不同的作用域实例不同。\n3. 瞬时实例（Transient），每次使用都创建新的实例。\n\n快递公司也就是我们在DI中常说的容器（Container）了。\n\n## 2.1、验证准备\n\n首先，我们需要三个Services（Service1\\Service2\\Service3）内容一致，如下：\n\n```c#\n// Service1.cs，Service2、Service3除类名以外，内容一致\npublic class Service1\n{\n    private int value = 0;\n\n    public int GetValue()\n    {\n        this.value++;\n        return this.value;\n    }\n}\n```\n\n然后，我们需要一个业务类，再一次注入这三个Service，内容如下：\n\n```c#\n// DefaultBusiness.cs \npublic class DefaultBusiness\n{\n    private readonly Service1 s1;\n    private readonly Service2 s2;\n    private readonly Service3 s3;\n\n    public DefaultBusiness(Service1 s1, Service2 s2, Service3 s3)\n    {\n        this.s1 = s1;\n        this.s2 = s2;\n        this.s3 = s3;\n    }\n\n    public int GetS1Value()\n    {\n        return this.s1.GetValue();\n    }\n\n    public int GetS2Value()\n    {\n        return this.s2.GetValue();\n    }\n\n    public int GetS3Value()\n    {\n        return this.s3.GetValue();\n    }\n}\n```\n\n最后，还需要在Startup.cs进行注入\n\n```c#\n// Startup.cs\npublic void ConfigureServices(IServiceCollection services)\n{\n  // 单例模式\n  services.AddSingleton<Service1>();\n  // 作用域模式\n  services.AddScoped<Service2>();\n  // 瞬时模式\n  services.AddTransient<Service3>();\n  // 为了保证结果，将Business类注册为瞬时模式，每次注入都是全新的。\n  services.AddTransient<Business.DefaultBusiness>();\n}\n```\n\n## 2.2、验证单例（Singleton）\n\n对于单例来说，我们期望，在整个程序启动期间，都是同一个实例，所以，我们只需要在Service中，增加一个局部变量，做累加就可以验证。\n\n```c#\n// DefaultController.cs\n[Route(\"singleton\")]\npublic IActionResult SingletonTest()\n{\n    this.defaultBiz.GetS1Value();\n    return Json(new\n    {\n        s1 = s1.GetValue()\n    });\n}\n```\n然后我们访问 `http://localhost:5000/singleton` 多次，输入如下：\n\n```c#\n// 第一次\n{ s1: 2 }\n// 第二次\n{ s1: 4 }\n// 第三次\n{ s1: 6 }\n```\n\n可以发现，每请求一次，value都会增加2。分析下怎么来的呢？\n\n1. 首先是执行  `defaultBiz.GetValue()` 中，根据内部代码，此处会对注入的实例，value + 1。\n2. 之后，`Json()`中的，`s1 = s1.GetValue()`，此处再一次增加了1。\n3. 综上，一次请求，s1 中的value值会增加2，由于是单例模式，在整个服务运行期间，都是一个实例，所以，每次请求都会累加2。\n\n\n## 2.3、验证作用域实例（Scoped）\n\n```c#\n// DefaultController.cs\n[Route(\"scoped\")]\npublic IActionResult ScopedTest()\n{\n    this.defaultBiz.GetS2Value();\n    return Json(new\n    {\n        s2 = s2.GetValue()\n    });\n}\n```\n然后我们访问 `http://localhost:5000/scoped` 多次，输入如下：\n\n```c#\n// 第一次\n{ s2: 2 }\n// 第二次\n{ s2: 2 }\n// 第三次\n{ s2: 2 }\n```\n\n从结果可以看出，每次请求的返回值是固定的，都为2，也就是证明了Service2中，value++执行了两次。对于执行value++的代码，只有 `defaultBiz.GetS2Value()` 和 `s2 = s2.GetValue()`，所以这两处操作的是同一个实例。这也就证明了，对于 `Scoped` 生命周期，在作用域（可以简单理解为单次请求，实际上并不准确，**注意，此处为考虑多线程的情况**）内，都是使用的同一个实例。在不同的请求之间，则是不同的实例。\n\n## 2.4、验证瞬时实例（Transient）\n\n```c#\n// DefaultController.cs\n[Route(\"transient\")]\npublic IActionResult TransientTest()\n{\n    return Json(new\n    {\n        s3 = s3.GetValue()\n    });\n}\n```\n然后我们访问 `http://localhost:5000/transient` 多次，输入如下：\n\n```c#\n// 第一次\n{ s3: 1 }\n// 第二次\n{ s3: 1 }\n// 第三次\n{ s3: 1 }\n```\n\n从结果来看，每次请求的都是相同的返回值，`s3 = 1`，这说明了，两次操作的value++,是针对的不同实例。也就是每次使用 Service1，都是全新的实例。\n\n# 3、扩展（Autofac DI 类库）\n\nAsp.Net Core中默认的DI，相对还是比较简单的，只有三个生命周期。对于时下比较的依赖注入库，一般都会有更多的生命周期，有些还会有生命周期事件可以监控。\n\n以 `Autofac` 为例，该类库提供了如下一些生命周期，可以做到更精细化的控制：\n\n1. 单次依赖（Instance Per Dependency）- 也就是Transient，每次获取实例都是全新的。\n2. 单例（Single Instance） - 也就是单例，整个服务周期都是一个实例。\n3. 作用域隔离的实例（Instance Per Lifetime Scope） - 也就是一个作用域一个，示例代码如下：\n\n```c#\n// 先创建作用域\nusing(var scope1 = container.BeginLifetimeScope())\n{\n  \n  for(var i = 0; i < 100; i++)\n  {\n    // 在作用域内，Resolve 的都是同一个实例\n    var w1 = scope1.Resolve<Worker>();\n  }\n}\n\n// 创建另一个作用域\nusing(var scope2 = container.BeginLifetimeScope())\n{\n  for(var i = 0; i < 100; i++)\n  {\n    // 在作用域内，Resolve 的都是同一个实例，但是这个实例和 scope1 作用域中的 w1 不是同一个。\n    var w2 = scope2.Resolve<Worker>();\n  }\n}\n```\n4. 带标签的作用域隔离实例（Instance Per Matching Lifetime Scope）\n5. 单次请求作用域实例（Instance Per Request） - 每个请求作为一个作用域。\n6. 指定Owner的作用域实例（Instance Per Owned）- 对于同一个Owner，实例保持一致\n7. 线程作用域实例（Thread Scope）\n\n**更多 Autofac 生命周期相关内容，请参考：[https://autofac.readthedocs.io/en/latest/lifetime/instance-scope.html](https://autofac.readthedocs.io/en/latest/lifetime/instance-scope.html)**\n\n\n# 4、总结\n\n本文主要简单演示了 Asp.Net Core 中默认的几种服务生命周期效果，也抛砖引玉的说了下 Autofac 中的服务生命周期。合理的利用生命周期，可以减少对象的创建，提交程序性能。但是，用错了生命周期，则容易产生隐含的bug。在使用 DI 类库的时候，一定要理解清楚不同的生命周期的应用场景。\n\n本文示例代码：[https://github.com/hstarorg/HstarDemoProject/tree/master/dotnet_demo/servicelifttime-demo/ServicelifttimeDemo](https://github.com/hstarorg/HstarDemoProject/tree/master/dotnet_demo/servicelifttime-demo/ServicelifttimeDemo)\n"
  },
  {
    "path": ".Net Platform/实战Asp.Net Core：中间件.md",
    "content": ""
  },
  {
    "path": ".Net Platform/实战Asp.Net Core：构建一个Core Lib.md",
    "content": ""
  },
  {
    "path": ".Net Platform/实战Asp.Net Core：部署应用.md",
    "content": "# 1、前言\n\n某一刻，你已经把 .Net Core 的程序写好了。接下来，还可以做什么呢？那就是部署了。\n\n作为一名开发工程师，如果不会部署自己开发的应用，那么这也是不完整的。接下来，我们就来说说，如何部署我们的 .Net Core 应用程序（主要是 Asp.Net Core 应用）。\n\n# 2、Asp.Net Core 的部署方式\n\n对于虚拟机中执行的语言来说，大都会有 SDK(Software Development Kit) 以及 XRE(X Runtime Environment)。对于 C#来说，也不例外。在 C#中，这两者的区别在于：\n\n- .Net Core SDK 用于开发者构建 App，包含有较多开发相关的工具包（实际上，SDK 也是包含 Runtime）\n- .Net Core Runtime 仅作为运行时使用，不包含开发工具包，体积较小。\n\n既然要部署 Asp.Net Core，自然离不开 Runtime（如果装 SDK 也能跑，不过不推荐在运行环境装 SDK）。以下的部署方式的前提都是已经安装 Runtime 环境。\n\n**下载地址：[https://dotnet.microsoft.com/download](https://dotnet.microsoft.com/download)**\n\n## 2.1、控制台直接运行\n\nAsp.Net Core 程序在发布后，会产生一个入口 dll 文件，要运行该程序，只需要通过 dotnet 命令执行该 dll 文件。所以，第一种方式就是直接找到 dll 文件，并使用 dotnet 命令来运行。（你说 dotnet 命令哪来的？安装了 Runtime 就有了。）\n\n```bash\n# 进行控制台执行\ndotnet xxx.dll\n```\n\n**优势：**\n\n1. 足够简单，拷贝文件就开整。\n2. 兼容多平台部署。\n\n**缺陷：**\n\n1. 想象一下，如果控制台关掉了，服务器重启了会怎样？此时需要手动重新去重新执行。（你说，可不可以设置为开机启动？如果创建一个批处理，放在启动项中，还是能做到的）\n\n## 2.2、IIS 部署\n\n用 .Net Framework 开发的应用，大家都比较熟悉用 IIS 来部署。那 .Net Core 呢？虽然两者的运行模式并不相同，但微软为了减少迁移难度，自然也提供了用 IIS 的部署方法。\n\n与 Asp.Net 不同，ASP.NET Core 不再是由 IIS 工作进程（w3wp.exe）托管，而是使用自托管 Web 服务器（Kestrel）运行，IIS 则是作为反向代理的角色转发请求到 Kestrel 不同端口的 ASP.NET Core 程序中，随后就将接收到的请求推送至中间件管道中去，处理完你的请求和相关业务逻辑之后再将 HTTP 响应数据重新回写到 IIS 中，最终转达到不同的客户端（浏览器，APP，客户端等）。\n\n如果要使用 IIS 部署 Asp.Net Core 程序，步骤如下：\n\n1. 首先，需要安装 .Net Core 托管捆绑包，[点此下载捆绑包](https://www.microsoft.com/net/permalink/dotnetcore-current-windows-runtime-bundle-installer)\n2. 进行 IIS 控制台，确保能在模块中找到 AspNetCoreModule 托管模块。(如果执行了步骤 1，未找到，可以尝试控制台执行 `iisreset`)\n3. 按照常规部署 .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)\n4. 至此，就算部署成功了。\n\n**优势：**\n\n1. 学习成本低，和 .Net Framework 应用的部署方式保持类似。\n2. 能够自动开机自启。\n3. 可以在可视化界面中，配置端口域名绑定。\n\n**劣势：**\n\n1. 该方式仅能在 Windows 服务器上使用。\n2. 通过 IIS 桥接一层，有性能损失。\n\n**了解更多，请参考：[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)**\n\n## 2.3、部署为 Windows Service\n\n在 2.2 的部署方式中，较大的缺陷就是性能损失。那么，有没有什么办法能够可以避开这个问题呢？。答案就是 Windows Service，通过 Windows Service，我们能够解决 2.1 中的开机启动和持久运行问题，也能避开 2.2 中的性能损失。具体如何做呢？如下提供一种方式（当然，也可以用其他方式来部署 Windows Service）：\n\n1. 借助 nssm 来管理 Windows Service，[Nssm](https://nssm.cc/)，用法，请参考：[https://nssm.cc/usage](https://nssm.cc/usage)\n2. 配置 Service 开机启动。\n\n**优势：**\n\n1. 高性能部署，稳定性好。\n2. 支持开机启动。\n\n**劣势：**\n\n1. 仅能用于 Windows 服务器。\n2. 引入了一个外包依赖 NSSM。\n\n## 2.4、Linux 部署\n\n由于 .Net Core 天生支持跨平台，如果在廉价又稳定的 Linux 上部署 .Net Core 程序逐渐成为主流。对于 Linux 上的部署，和 Windows 上并没有什么区别。首先是安装 Runtime 环境，然后拷贝程序，并通过命令行运行。\n\n再进一步，可以使用后台模式，让程序在后台运行。\n\n更进一步，也可以效仿 Windows，把程序启动管理作为一个服务，来达到开机启动和灵活管理的目的。\n\n## 2.5、Docker 部署\n\n作为当前个人认为的最棒的 .Net Core 应用部署方式，建议大家都了解下。\n\n首先，是 Docker 的基本使用：\n\n1. 编写 Dockerfile\n2. 使用 `docker build` 构建镜像\n3. 使用 `docker run` 创建容器并运行\n\n好，我们来依次说明，对于 Docker 来说，需要先安装 Docker 环境。\n\n接着，我们假设发布包路径如下：\n\n```bash\nroot-folder/\n  app/ # 发布包目录\n    xxx.dll # 程序入口点 \n  Dockerfile # Dockerfile文件\n```\n\n然后针对该程序，编写如下 Dockerfile：\n\n```bash\n# 根镜像\nFROM microsoft/dotnet:2.2-runtime\n\n# 拷贝程序发布包\nCOPY app /app\n\n# 设置工作目录\nWORKDIR /app\n\n# 导出的端口\nEXPOST 80\n\n# 程序运行命令\nCMD [\"dotnet\", \"xxx.dll\"]\n```\n\n接下来，通过在 `root-folder` 中执行 `docker build -t xxx:0.0.1 .` 来构建一个镜像。\n\n接着，再通过 `docker run -it -p 8000:80 --name xxx-demo xxx:0.0.1` 来创建并运行容器。\n\n这样，就可以通过 `http://localhost:8000` 来访问到你的应用程序了。\n\n此处只是大概写下 Docker 部署的步骤，抛砖引玉。真正需要将其用于产线，还需要去学习下足够的 Docker 知识。\n\n**额外提一下，如何选择基础镜像**\n\n> 对于 .Net Core 来说，一般有如下几类基础镜像：\n\n* sdk -- 相信这个都比较容易理解，就是包含了 .Net Core SDK。\n* runtime -- 这个也相对容易理解，包含了.Net Core Runtime。\n* runtime-deps --这个就不是很好理解， runtime? deps? 什么意思呢？就是说，这个连 Runtime都不是全的，需要你在打包的时候，选择自寄宿模式，把Runtime也打进去。\n\n**综上，我个人推荐大家选择 runtime 这类作为基础镜像。**\n\n# 参考文档\n\n1. [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)\n2. [https://hub.docker.com/r/microsoft/dotnet](https://hub.docker.com/r/microsoft/dotnet)\n3. [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)\n"
  },
  {
    "path": ".gitignore",
    "content": ".DS_Store\nThumbs.db\ndb.json\n*.log\nnode_modules/\npublic/\n.deploy*/"
  },
  {
    "path": "AngularJS相关/Angular1.x升级指南.md",
    "content": "---\ntitle: Angular1.x升级指南\ndate: 2017/02/21 14:47:10\n---\n\n## 0、导言\n\nAngular从1.2.x到1.3.x是一个大的跳跃。在1.3.x之后，也有1.4.x，1.5.x这么两个大版本。\n\nNewkit从1.2.x跳跃到1.3.x经历过一次大的变化，在之后的较大版本升级中，基本上没有大多大改动。但是其中的差异点，我们也需要去了解。\n\n1.3.x以前的版本，我们就不去深究了，毕竟现在不是主流，我们就从1.3.x开始，来看看Angular到底有了哪些变化。\n\n## 1、从1.3.x到1.4.x\n\n从1.3到1.4，Angular的变化涉及到很多个方面，一一列举如下。\n\n### 1.1、动画\n\n在1.4中，动画功能进行了很大的重构，但是API基本保持一致。在新的版本中，我们通过注入 ``$animationCss`` 来实现用JS创建CSS动画。\n\n**1.1.1、定义CSS动画如下：**\n\n```javascript\nangular.module('app', ['ngAnimate'])\n  .animation('.slide-animation', ['$animateCss', function ($animateCss) {\n    return {\n      enter: function (element, doneFn) {\n        var animation =  $animateCss(element, {\n          from: { background: 'black' },\n          to: { background: 'blue' },\n          duration: 10 // one second\n        });\n        animation.start().done(doneFn);\n      },\n      leave: function(element, doneFn){\n         var animation =  $animateCss(element, {\n          from: { fontSize: '12px' },\n          to: { fontSize: '25px' },\n          duration: 10 // one second\n        });\n        animation.start().done(doneFn);\n      }\n    }\n  }]);\n```\n\n如何使用？\n\n```html\n<div class=\"test slide-animation\" ng-if=\"vm.ck1\"> AAAAAAAAAAA</div>\n```\n\n只需要在元素上设置一个class，然后当元素从隐藏到显示，则会执行 ``enter`` 动画，从显示到隐藏，则会执行 ``leave`` 动画。\n\n**1.1.2、监听动画事件**\n\n```javascript\n// < 1.4\nelement.on('$animate:before', function(e, data) {\n  if (data.event === 'enter') { ... }\n});\nelement.off('$animate:before', fn);\n\n// 1.4+\n$animate.on('enter', element, function(data) {\n  //...\n});\n$animate.off('enter', element, fn);\n```\n\n在1.4版本之前，我们需要通过在element上去监听动画开始和结束，另外还必须要通过 ``data.event`` 来判断动画类型。\n\n在1.4及以后，我们可以直接通过 ``$animate`` 来监控了。\n\n**1.1.3、触发动画**\n\n```javascript\nvar $el = $('.slide-animation');\n$animate.enter($el, $el.parent()).then(function(){\n  console.log('enter');\n});\n```\n\n我们需要使用如上的方式，来手动启动动画，此时会触发元素的对应动画事件。\n\n**注意：触发动画的回调中，如果要操作$scope,在<1.4中会失败，需要借助$apply，但在1.4+，就不需要了。示例如下：**\n\n```javascript\n$animate.enter(element, elementParent).then(function() {\n  $scope.$apply(function() {\n    $scope.explode = true;\n  });\n});\n\n// 1.4+\n$animate.enter(element, elementParent).then(function() {\n  $scope.explode = true;\n});\n```\n\n**1.1.4、启用/禁用动画**\n\n```javascript\n// < 1.4\n$animate.enabled(false, element);\n\n// 1.4+\n$animate.enabled(element, false);\n```\n\n实现该操作的方法参数1.4+刚好和小于1.4相反。\n\n### 1.2、表单\n**1.2.1、ngMessages**\n\n```javascript\n<!-- AngularJS 1.3.x -->\n<div ng-messages=\"model.$error\" ng-messages-include=\"remote.html\">\n  <div ng-message=\"required\">Your message is required</div>\n</div>\n\n<!-- AngularJS 1.4.x -->\n<div ng-messages=\"model.$error\">\n  <div ng-message=\"required\">Your message is required</div>\n  <div ng-messages-include=\"remote.html\"></div>\n</div>\n```\n\n在1.3版本中，``ng-messages-include`` 跟随在 ``ng-messages`` 元素上，这样并不灵活。\n\n在1.4+中，``ng-messages-include`` 不允许更随在 ``ng-messages`` 元素上，必须放在内部，这样使得使用远程模板非常灵活。\n\n另外，当存在多个form时，我们在使用ng-messages获取指定form的方法也有变化，如下：\n\n```javascript\n// < 1.4\n<div ng-messages=\"ctrl.form['field_{{$index}}'].$error\">...</div>\n\n// 1.4 + \n<div ng-messages=\"ctrl.getMessages($index)\">...</div>\nctrl.getMessages = function($index) {\n  return ctrl.form['field_' + $index].$error;\n}\n```\n\n\n**1.2.2、ngOptions**\n\nngOptions仅仅只是内部实现变化，在使用上并没有多大差异。其中当遍历Object时，之前的版本是用的 ``for in``，导致输出的key是字符序的。新版本采用 ``Object.keys``，输出的key是定义时候的顺序。\n\n另外当ngOptions表达式执行之后，将不会再触发ngOptions了。\n\n**1.2.3、select**\n\n在 **select** 元素中，这是一个非常大的变更。简单理解，如下：在1.3中，ngModel和&lt;option>的value比较仅仅是 ``==``，所以 ``200 == '200' //true``。在1.4+中，比较方式成了 ``===``,所以 ``200 === '200' //false``。\n\n这个时候我们可以通过如下方式处理：\n\n```javascript\nngModelCtrl.$parsers.push(function(value) {\n  return parseInt(value, 10); // Convert option value to number\n});\n\nngModelCtrl.$formatters.push(function(value) {\n  return value.toString(); // Convert scope value to string\n});\n```\n\n实现指令如下：\n\n```javascript\napp.directive('convertNumber', function() {\n  return {\n    require: 'ngModel',\n    link: function(scope, el, attr, ctrl) {\n      ctrl.$parsers.push(function(value) {\n        return parseInt(value, 10);\n      });\n\n      ctrl.$formatters.push(function(value) {\n        return value.toString();\n      });      \n    }\n  }\n});\n```\n当然，如果我们保证ngModel的值为string类型，那就没啥问题了（在不使用ng-value的情况下）。\n\n**1.2.4、Form**\n\n表单的变化，主要是name属性，在 < 1.4 的版本中，我们可以设置name为 \"my:form1\"，在1.4+版本中，不在允许这种特殊的用法。\n\n### 1.3、模板相关\n\n**1.3.1、ngRepeat**\n\n< 1.4 版本中，ng-repeat的遍历顺序是字母序。\n\n1.4+版本中，顺序是有浏览器的 ``for in`` 来返回的。\n\n**1.3.2、ngInclude**\n\n```javascript\n// < 1.4\n\n<div ng-include=\"findTemplate('https://example.com/templates/myTemplate.html')\"></div>\n$scope.findTemplate = function(templateName) {\n  return $sce.trustAsResourceUrl(templateName);\n};\n\n\n// 1.4+\nvar templateCache = {};\n$scope.findTemplate = function(templateName) {\n  if (!templateCache[templateName]) {\n    templateCache[templateName] = $sce.trustAsResourceUrl(templateName);\n  }\n\n  return templateCache[templateName];\n};\n// Or\nangular.module('myApp', []).config(function($sceDelegateProvider) {\n  $sceDelegateProvider.resourceUrlWhitelist(['self', 'https://example.com/templates/**'])\n});\n```\n\n### 1.4、Cookie\n\n在1.4+中，cookie增加了新的API：\n\n```javascript\nget\nput\ngetObject\nputObject\ngetAll\nremove\n```\n另外，``$cookieStore`` 将不推荐使用。\n\n### 1.5、HTTP\n\n在1.4+中，$http的 ``transformRequest`` 将不允许修改请求header。但是我们可以使用如下方式进行动态的header设置：\n\n```javascript\n$http.get(url, {\n  headers: {\n    'X-MY_HEADER': function(config) {\n      return 'abcd'; //you've got access to a request config object to specify header value dynamically\n    }\n  }\n})\n```\n\n### 1.6、Filter\n\n``fliter``如果作用在非数组上，将会抛出一个异常，之前则是返回一个空数组。\n\n``limitTo``作用在不合法的value上，现在将会原样返回，之前是返回空对象，空数组。\n\n\n## 2、从1.4.x到1.5.x\n\n从 1.4.x 到 1.5.x 是一个为升级ng2做准备的变更版本。\n\n该版本主要是增加了一些功能，所以从1.4.x升级到1.5.x基本不需要太大的改动。\n\n在 1.5.x 版本中，增加了一些接近ng2的新特性，如：\n\n1. angular.component() //一种偏向于angular2风格的组件（特殊的指令）\n2. $onInit //生命周期钩子\n3. ngAnimateSwap // ngAnimate中一个新的指令\n\n接着，我们来看下它的一些具体变更。\n\n### 2.1、Core\n\n**2.1.1、$parse**\n\n``$parse`` 增加新特性，可以使用locals来覆盖原本的context。用法如下：\n\n```javascript\nvar context = { user: { name: 'Jay' } };\nvar locals = { user: { name1: 'Local Jay' } };\nvar getter = this.$parse('user.name');\nconsole.log(getter(context)); // 'Jay'\nconsole.log(getter(context, locals)); // 'Local Jay'\n```\n\n**2.1.2、ngOptions**\n\n如果元素上有 ``ngOptions`` 指令，那么 ``ngModel`` 指令也必须存在，否则就会抛出错误。\n\n另外 ``ngOptions`` 接受假值（'', 0, false, null）\n\n**2.1.3、orderBy**\n\n对 ``undefined`` 或者 ``null`` 进行 ``orderBy`` 将会抛出错误。\n\n### 2.2、ngAria\n\n取消了部分元素的可访问性设置。\n\n### 2.3 ngMessages\n\n将 ``ngMessages`` 指令的优先级设置为1，如果有优先级低于1的指令有transclude功能，那么需要设置为更高的优先级。\n\n### 2.4、ngResource\n\n``$resource`` 增加了 ``$cancelRequest()`` 方法。\n\n### 2.5、ngRoute\n\n增加了 ``$resolveAs`` 配置属性，允许对 ``$resolve`` 指定别名。\n\n### 2.6、ngTouch\n\n默认禁用了 ``ngClickOverrideEnabled``，在触摸屏上，可能还有300ms的延迟。\n\n如果要启用，那么可以使用如下方式：\n\n```javascript\nangular.module('myApp').config(function($touchProvider) {\n  $touchProvider.ngClickOverrideEnabled(true);\n});\n```\n\n另外，建议使用FastClick来实现该功能。\n\n需要注意，某些现代浏览器在某些场景下已经删除了300ms延迟：\n\nChrome 和 Firefox for Android 发现设置了 ``<meta name=\"viewport\" content=\"width=device-width\">`` 将会删除300ms延迟。\n\nIE当设置 ``touch-action`` 为 ``none`` 或者 ``manipulation`` 移除延迟。\n\n**注意：这些变化并不影响ngSwipe指令**。\n\n## 3、总结\n\nAngular1.x基本已经进入维护期了，很少会有新特性加入了。现在的重心在angular2，所以我们也可以优先对angular2做一些技术储备。"
  },
  {
    "path": "AngularJS相关/AngularJS官方FAQ.md",
    "content": "---\ntitle: AngularJS官方FAQ\ndate: 2015/3/13 11:24:39\n---\n\n## 相关：最佳实践，反模式\n\n### 1、为什么会觉得jQuery插件缺失？\n\n请记住：当你在使用jQuery插件时，请在AngularJS之前加载jQuery库\n\n**分析：** 因为AngularJS自带jqLite（可以理解为jQuery的精简版），如果先引入AngularJS的话，那么AngularJS会采用jqLite，而不是完整的jQuery库。\n\n### 2、如何从一个Controller中访问DOM元素？\n\n不要从Controller中执行DOM的选择/遍历。HTML还没有被渲染。查一查”directive“\n\n### 3、为什么Angular说 controller/directive等缺失？\n\n调用 `angular.module('myApp',[])` 总是会创建一个新的模块（同时干掉已有的重名模块）。相反，使用一个参数的方式调用 angular.module('myApp') 来引用已经存在的模块。\n\n### 4、如何渲染未转义的数据？\n\n```javascript\n$sce.trustAsHtml(data)\n如何禁用$sce?\napp.config(['$sceProvider', function($sceProvider) {\n    $sceProvider.enabled(false);\n}]);\n```\n### 5、当array/object/$resource-result变化时，应该如何监视？\n\n```javascript\n$scope.$watch 有第三个参数设置来监视值变化（非引用变化）\n$watch(watchExpression, listener, [objectEquality]);\n[objectEquality]设置为true，则使用angular.equals对象相等，而不是使用引用相等比较。\n```\n\n### 6、怎样才能序列化表单数据提交？\n\n**不要这么做！** 不要尝试手动收集输入框值。仅仅只需要在每一个表单元素上附加 `ng-model=\"data.myField`，在需要使用的地方，使用 `$scope.data` 即可\n\n### 7、总是在 ng-models 上使用(.) =>最佳实践\n\n```html\n<any ng-model=\"book.price\" />\n<any ng-model=\"book.name\" />\n```\n\n### 8、应该如何从 service 中访问 scope ？\n\n`$rootScope` 相当于 `ng-app` 标记，它能够被引导或者是服务注入，可以用于在所有的 scopes 上添加新功能和值\n\n**注意：避免这样做--这相当于定义全局变量**\n\n### 9、`module().factory()` 和 `module().service()` 不同点是什么？\n\n[查看讨论信息](https://groups.google.com/forum/?fromgroups#!topic/angular/56sdORWEoqg)\n\n### 10、如何防止无样式的内容闪现（页面显示双大括号绑定表达式）？\n\n在一些地方使用 `ng-bind` 来替换双括号表达式\n\n### 11、为什么 `<a ng-click=\"go({{myVal}})\">` 不工作？\n\n仅有的 `ng-*` 属性中，需要 `{{xxx}}` 的只有 `ng-src` 和 `ng-href`，因为最终的结果必须是一个字符串，不是一个表达式。所以其他的不能工作。\n\n### 12、嵌套 routes/views? \n\n或许吧\n\n### 13、可以在行内指定模板或者是分部视图吗？\n\n可以。可以采用 `<script id=\"some/partial.html\" type=\"text/ng-template\"></script>` ，Angular会使用它来替换。\n\n### 14、 如何在 ngResource 地址中使用端口？\n\n\t如下：$resource('example.com\\\\:8080')\n\n\n### 15、为什么插件触发的change事件似乎不工作？\n\nAngular监视 [input](https://developer.mozilla.org/en-US/docs/Web/Events/input) 事件，不是'change' 事件。\n\n\n### 16、不要使用jQuery来切换crap(待定：无效元素)，在行内使用一些变量标记。\n\n```html\n<a ng-click=\"flags.open=!flags.open\">...<div ng-class=\"{active:flags.open}\">\n```\n\n### 17、如何从DOM检查上查看 scope ？\n\nGoogle Chrome:安装 Batarang extension,检查一个DOM元素，然后在console中键入$scope\n\nFirefox/Firebug：检查一个DOM元素，然后在console中键入 `angular.element($0).scope()`\n或者 `$($0).scope()`\n\nIE10+: 使用F12工具，检查一个元素。然后在console中键入 `angular.element($0).scope()`\n或者 `$($0).scope()`\n\n### 18、你有一些好的指令示例/库吗？\n\n[AngularUI](http://angular-ui.github.com/) 是非常棒的AngularJS工具集合（甚至是更好的示例代码）\n\n\n### 19、IE？\n\n针对IE8.0或者更早，你需要[阅读这个](https://docs.angularjs.org/guide/ie)和[使用这个](http://angular-ui.github.io/#ieshiv)\n\n\n### 20、必须对路由使用#?\n\n参考 [$locationProvider](https://docs.angularjs.org/api/ng/provider/$locationProvider)\n\n\n### 21、你应该在尝试用指令包装jQuery插件前，优先尝试使用[AngularUI Passthru Directive (uiJq) ](http://angular-ui.github.io/#directives-jq)\n\n\n### 22、为什么我的 $scope.$watch() 递归触发?\n\n如果你在 $scope.$watch(newVal,oldVal)中改变 newVal ，它会重复触发。在 $watch 运行后，$scope 会重新评估，被观察对象将被重新触发。\n\n\n### 23、何时我需要使用 $scope.$apply() ?\n\n仅仅需要在没有angular 事件/回调时 使用 $scope.$apply()。它通常不属于任何地方。\n\n\n### 24、启用了 html5Mode ，如何获取&lt;a href />的后退行为？\n\n如果你想一个链接能够全页面刷新，那么只需要在a标记上添加 target=\"_self\"\n\n\n### 25、如何 .preventDefualt() 或 .stopPropagation() ?\n\n所有的 ng-click 和相关的绑定都注入了 $event 事件对象，你可以用它来调用 .preventDefualt()，甚至是对象传递给你的方法。\n\n\n### 26、AngularJS在我的Chrome扩展中不工作！\n\n你需要使用 [ng-csp](http://docs.angularjs.org/api/ng.directive:ngCsp)\n\n\n### 27、如何缓存 $http 和html 分部视图\n\n```javascript\n//使用装饰器，添加缓存功能\nmyAppModule.config(function($routeProvider, $provide) {\n  $provide.decorator('$http', function($delegate){\n    var get = $delegate.get;\n    $delegate.get = function(url, config){\n      url += (url.indexOf('?') !== -1) ? '?' : '&';\n      url += 'v=' + cacheBustVersion;\n      return get(url, config);\n    };\n    return $delegate;\n  });\n}); \n```\n\n## 测试\n\n### 1、拒绝/解决一个 $q.defer() 不通过\n\n你必须在处理它们的时候添加 `$scope.$apply()`\n\n### 2、Jasmine spyOn() 不执行 spy'd 功能\n\n不一定是AngularJS的问题，但是你需要追加 `.addCallThrough()`\n\n"
  },
  {
    "path": "AngularJS相关/AngularJS教程：1W字综合指南.md",
    "content": "---\ntitle: AngularJS教程：1W字综合指南\ndate: 2015/3/13 11:24:39\n---\n\n# AngularJSj教程：1W字指南（译）\n**原文地址：**[http://www.airpair.com/angularjs](http://www.airpair.com/angularjs)\n\n## 1、AngularJS简介\nAngular 是用于编写引人注目的Web应用程序的是客户端 MVW JavaScript框架。它由Google创建好维护，（offers a futuristic spin on the web and its upcoming features and standards.\nRead more at http://www.airpair.com/angularjs#tY7q00WpGrTLB71Z.99）\n\nMVW 即 Model-View-Whatever,它是能在开发应用程序时，为我们提供灵活性的一种设计模式。我们可以选择MVC(Model-View-Controller)或者是MVVM(Model-View-ViewModel)方式。\n\n本教程可以作为一个最终的资源来开始学习AngularJS,它的概念和它背后的API，同时能帮助您学习如何实现现代的Web应用程序。\n\nAngularJS自身作为增强HTML的一个框架。它从多种语言包括JavaScript和服务端语言中获得灵感，使得HTML也成为了动态语言。这意味着我们获得了一个完全的数据数据方式来开发应用程序，不再需要刷新实体，更新DOM和其他费时任务如浏览器bug和不一致。我们可以只关注数据，让数据关心HTML的方式来编写我们的应用程序。\n\n## 2、JavaScript框架中的工程概念\nAngularJS在处理提供数据绑定和其他工程概念上，和其他框架如Backbone.js和Ember.js采取了不同了做法。我们坚持使用熟悉的、令人喜欢的HTML，使Angular拦截它，并增强它。Angular将纯粹的JavaScript对象用于数据绑定，保证任何模型变化都会更新DOM。当模型值更新了一次，Angular会更新来自应用程序的状态来源对象。\n\n### 2.1、MVC 和 MVVM\n如果你已经习惯了构建静态网站，你可能更熟悉手动一块一块的构建HTML，通过数据一遍一遍的打印相同的HTML。这可能是grid中的列，一个导航结构，一个链接列表或者是图片等等。在这个实例中，你需要习惯一点小东西的变化都需要手动更新HTML的痛苦，你必须更新模板来保持其他用途的一致性。你还要为每个导航项目杜绝相同的HTML。\n\n深呼吸一下，通过Angular我们能实现恰当的关注点分离以及动态HTML。这意味着数据在模Model中，HTML是作为一个微小的模板被渲染为View，我们能使用Controller来连接它们两个，并驱动Model和View值的变化。"
  },
  {
    "path": "AngularJS相关/AngularJS：Looking under the hood.md",
    "content": "---\ntitle: AngularJS：Looking under the hood\ndate: 2015/3/13 11:24:39\n---\n\n原文地址：[https://www.binpress.com/tutorial/angular-js-looking-under-the-hood/153](https://www.binpress.com/tutorial/angular-js-looking-under-the-hood/153)\n\n**用AngularJS写得越多，你就越惊叹于它的神奇。我对Angular能做的一些奇妙的事情非常好奇，然后我决定分析它的源代码，看看我能否揭示它的一些秘密。我记录了我在23000多行Angular源码中发现的真正有用的，能够解释Angular先进（和隐藏）的方面的一些内容。**\n\n## 1、Dependency Injection annotation process\n依赖注入（DI）是除开用代码获取或创建依赖之外的一条不同的请求依赖的方式。简单的说，依赖是作为一个注入对象传递给我们的。Angular允许我们在我们的应用程序中通过像Controllers和Directives的方法来使用DI。我们能创建自己的依赖，同时允许Angular在请求它们的时候被注入。\n\n在Angular中，一个最常用的被请求的依赖是 *$scope*。例如：\n\n\tfunction MainCtrl ($scope){\n\t\t//access to $scope\n\t}\n\tangular.module('app').controller('MainCtrl', MainCtrl);\n\n对于没有使用过Angular提供的依赖注入的JavaScript开发者来说，这看起来像一个局部变量名。实际上，它仅仅是我们所请求的依赖名称的一个占位符。Angular查找这些占位符，然后通过DI将它们转换为真正的依赖对象，让我们来仔细看看。\n\n### 方法参数 ###\n直到我们压缩我们的应用前，方法参数都运行正常。当你压缩你的代码，你的方法定义将会用字符表示参数而不是单词-这意味着Angular不能找到你想要的！Angular使用了一个方式来解决，调用function的 *toString()* 方法。这将返回函数的字符串形式！接下来我们就能访问正在被请求的参数。Angular\n\t\n\tvar FN_ARGS = /^function\\s*[^\\(]*\\(\\s*([^\\)]*)\\)/m;\n\tvar FN_ARG_SPLIT = /,/;\n\tvar FN_ARG = /^\\s*(_?)(\\S+?)\\1\\s*$/;\n\tvar STRIP_COMMENTS = /((\\/\\/.*$)|(\\/\\*[\\s\\S]*?\\*\\/))/mg;\n\nAngular做的第一件事就是将函数转换为字符串，这是JavaScript中非常有用的一个特性。这将给我们一个字符串类型的函数，如：\n\n\t'function MainCtrl ($scope) {...}'\n\n接下来，Angular使用如下方法，移除所有的注释：\n\n\tfnText = fn.toString().replace(STRIP_COMMENTS, '');\n\n紧接着，Angular从处理好的function中分割参数来创建真正有用的部分，\n\n\targDecl = fnText.match(FN_ARGS);\n\nAngular接下来使用 *.split()* 来移除空白字符，同时返回我们请求的参数数组。为了更完美，Angular使用了一个内部的forEach方法来迭代这个数组，并匹配参数名称然后将它们添加到 *$inject* 数组中。\n\n\tforEach(argDecl[1].split(FN_ARG_SPLIT), function(arg) {\n\t  arg.replace(FN_ARG, function(all, underscore, name) {\n\t    $inject.push(name);\n\t  });\n\t});\n\n这是你能想象的一个昂贵的处理流程。对每个函数的4个正则查找和一很多转换会造成性能损耗。当我们得到了Angular抽象的 *$inject* 数组，我们可以直接切入且田填充 *$inject* 数组来保存Angular困难和开销时间长的操作。\n\n### $inject 对象 ###\n\n我们可以通过在函数上添加 *$inject*属性来指定依赖自身，其中，如果存在的话，Angular使用DI注解。这是很容易的最可读的语法。例子如下：\n\n\tfunction SomeCtrl ($scope) {\n\t}\n\tSomeCtrl.$inject = ['$scope'];\n\tangular.module('app', [])\n\t\t.controller('SomeCtrl', ['$scope', SomeCtrl]);\n\n这样节省了Angular的许多工作-替代了检查方法参数，或者是操纵数组（详情请查看下一章节：Array Arguments）,它仅仅返回和运行指定的 *$inject* 数组。简单，高性能。\n\n理想情况下，由于依赖注入在我们自己的时间和Angular的转换时间上开销很大，我们可以使用任务运行工具如Grunt.js或者是Gulp.js 来自动化注入任务或者是数组语法。\n\n**Note：这个并没有实例化被依赖的所有服务，Angular所做的只是标注相关的名字-框架的其他部分关心对象注入。**\n\n### Array Arguments\n\n最后一个例子使用了我们通常看见的数组索引对应函数参数序号的语法，例如：\n\n\t['$scope', function($scope){}]\n\n数组的顺序是非常重要的，因为函数的参数将会按照同样的顺序，以此来避免依赖被错误的实例化和可能引发的错误。\n\n\tfunction SomeCtrl ($scope, $rootScope) {\t\n\t}\t\n\tangular.module('app', [])\n\t\t.controller('SomeCtrl', ['$scope', ‘$rootScope’, SomeCtrl]);\n\n我们需要做的是传递函数作为数组的最后一个项，Angular会删除这个函数，并遍历数组所注明的依赖名称，就好像我们创建的 *$inject* 属性。当Angular解析一个方法的时候，它会检查参数是不是一个数组，如果是，那么最后一项是函数，其他的则是依赖。\n\n\telse if (isArray(fn)) {\n\t  last = fn.length - 1;\n\t  assertArgFn(fn[last], 'fn');\n\t  $inject = fn.slice(0, last);\n\t}\n\n## 2、Factory vs Service ##\n\nFactory 和 Service 非常类似，但往往开发人员都难以理解它们。\n\n当 *.service()* 已经实例化，那么 *new Service()* 将被引擎调用，返回一个新实例给我们。本质上，*。.service()* 是作为构造函数被使用的。\n\nservice 基本上是一个 factory，然而它是创建时被实例化，因为，你需要在 service 中使用this来注册变量和函数，来替代在factory中返回一个对象的方式。\n\nfactory 是非常接近面向对象中的“工厂模式”，当你注入了这个 factory ,你就获得了完整的方法，允许你创建你需要的新的实例-本质上是通过一个对象创建多个新对象。\n\n你可以看下在Angular源码中的内部的工作：\n\n\tfunction factory(name, factoryFn) { \n\t\treturn provider(name, { $get: factoryFn }); \n\t}\n\tfunction service(name, constructor) {\n\t    return factory(name, ['$injector', function($injector) {\n\t\t    return $injector.instantiate(constructor);\n\t    }]);\n\t}\n\n## 3、New $scope creation from $rootScope\n\nAngular中所有的scope都是 *$rootScope* 的下级。 *$rootScope* 是通过 *new Scope()*创建的，进一步的子 scope 是通过 *$scope.$new()* 创建的。\n\n\tvar $rootScope = new Scope();\n\n在 *$new* 方法里面，Angular设置了一个原型链来允许允许 scope 引用它们的父亲，它们的自己跟踪（作为生命周期），和以前的兄弟 scope 。\n\n从下面的代码，如果你请求了一个隔离的 scope ，它会创建一个 *new Scope()* ,否则，它会创建一个从父级继承的子 scope 。\n\n我省略了一些不必要的代码，但这里的是重点：\n\n\t$new: function(isolate) {\n\t    var child;\n\t\n\t    if (isolate) {\n\t      child = new Scope();\n\t      child.$root = this.$root;\n\t    } else {\n\t      // Only create a child scope class if somebody asks for one,\n\t      // but cache it to allow the VM to optimize lookups.\n\t      if (!this.$$ChildScope) {\n\t        this.$$ChildScope = function ChildScope() {\n\t          this.$$watchers = null;\n\t        };\n\t        this.$$ChildScope.prototype = this;\n\t      }\n\t      child = new this.$$ChildScope();\n\t    }\n\t    child['this'] = child;\n\t    child.$parent = this;\n\t    return child;\n\t  }\n\n当你使用*$scope.$new()*来测试Controller的时候，这也是非常好的能了解测试目的。这有助于明确对我来说Angular是如何创建新的scope的，为什么用Angular mocks 模块来嘲笑测试驱动开发（TDD）。\n\n## 4、Digest Cycle\n\nDigest Cycle 经常作为 *$digest* 被我们看到，这是Angular双向绑定的能力。当一个模型值更新的时候，它会运行，检查它最后已知的值，如果值有变化，呼叫适当的监听器。这是基本的脏检查 - 它针对所有有可能的值来检查，如果是脏值，那么呼叫相关的监听器，直到他没有脏值。我们快速看一下它是如何工作的：\n\n\t$scope.name = 'Todd';\n\t\n\t$scope.$watch(function() {\n\t    return $scope.name;\n\t}, function (newValue, oldValue) {\n\t    console.log('$scope.name was updated!');\n\t} );\n\n当你调用 *$scope.$watch*，你注册了两件事。参数一是一个函数，返回你想要监视的值（当你提供一个字符串的时候，Angualr会将他转换为函数）。当 $digest 运行时，监视的参数将被调用，返回任何你想要的值。参数二是当你的参数一变化时，想要执行的函数。看一下Angular是怎样注册watch的。\n\n\t$watch: function(watchExp, listener, objectEquality) {\n\t    var get = $parse(watchExp);\n\t\n\t    if (get.$$watchDelegate) {\n\t      return get.$$watchDelegate(this, listener, objectEquality, get);\n\t    }\n\t    var scope = this,\n\t        array = scope.$$watchers,\n\t        watcher = {\n\t          fn: listener,\n\t          last: initWatchVal,\n\t          get: get,\n\t          exp: watchExp,\n\t          eq: !!objectEquality\n\t        };\n\t\n\t    lastDirtyWatch = null;\n\t\n\t    if (!isFunction(listener)) {\n\t      watcher.fn = noop;\n\t    }\n\t\n\t    if (!array) {\n\t      array = scope.$$watchers = [];\n\t    }\n\t    // we use unshift since we use a while loop in $digest for speed.\n\t    // the while loop reads in reverse order.\n\t    array.unshift(watcher);\n\t\n\t    return function deregisterWatch() {\n\t      arrayRemove(array, watcher);\n\t      lastDirtyWatch = null;\n\t    };\n\t }\n\n这个函数推送你提供的参数到 scope 的 *$$watchers* 数组中，同时，返回一个方法允许你停止watch。\n\n然后，每当*$scope.$apply*或者*$scope.$digest* 运行时，digest cycle将被运行。\n\n### 未完...待续...\n"
  },
  {
    "path": "AngularJS相关/Angular从0到1：function（上）.md",
    "content": "---\ntitle: Angular从0到1：function（上）\ndate: 2017/02/21 14:47:10\n---\n\n## 1、前言\n\nAngular作为最流行的前端MV*框架，在WEB开发中占据了重要的地位。接下来，我们就一步一步从官方api结合实践过程，来学习一下这个强大的框架吧。\n\nNote：每个function描述标题之后的★标明了该function的重要程度（1~5星）。\n\n## 2、function（上）\n\nAngular封装了一系列公共方法，帮助我们更简单的使用JS。这些就是Angular的function。\n\n### 2.1、angular.bind(★)\n\nangular.bind类似于Function.prototype.bind，实现函数柯里化，返回一个函数代理。eg:\n\n\t函数原型\n\tangular.bind(/*this对象*/self, /*要封装的function*/fn, /*参数列表*/args);\n\n\t//原始函数\n\tfunction fun(arg1, arg2, arg3) {\n\t  console.log(this);\n\t  console.log(arg1);\n\t  console.log(arg2);\n\t  console.log(arg3);\n\t}\n\tfun('arg1', 'arg2', 'arg3');\n\t//如果几个常用参数都是固定的，而且该函数被调用频繁，那么就可以包装一下\n\tvar fun2 = angular.bind(window, fun, 'arg1', 'arg2');\n\t//新的调用方式\n\tfun2('arg3');\n\n### 2.2、angular.bootstrap(★★★★)\n\n用于使用angular执行渲染元素。也是angular的启动方法，如果没有在页面上指定ng-app,必须要手动调用该函数进行启动。\n\n\tangular.bootstrap(/*Dom元素*/element, /*Array/String/Function*/[modules], /*Object*/[config]);\n\t\n\t//常规用法\n\tangular.bootstrap(document, ['app'])\n\t//带配置项\n\tangular.bootstrap(document, ['app'], {strictDi: true/*Defaults: false*/})\n\n### 2.3、angular.copy(★★★★★)\n\nAngular.copy用于复制对象，由于angular的双向绑定特点，那么如果直接操作$scope对象，那么很容易就会改变ui的显示，这个时候就需要借助angular.copy来创建一个对象副本，并进行操作。\n\n\t//原型\n\tangular.copy(source, [destination]);\n\n\tvar obj = {a: 1};\n\tvar obj2 = angular.copy(obj);\n\tvar obj3;\n\tangular.copy(obj, obj3);\n\tconsole.log(obj2 === obj) //false\n\tconsole.log(obj3 === obj) //false\n\tvar obj4;\n\t//第二个和参数和返回值是相等的，而且第二个参数不管以前是什么，都会被重新赋值\n\tvar obj5 = angular.copy(obj, obj4);\n\tconsole.log(obj4 === obj5); //true\n\n### 2.4、angular.element(★★★★)\n\n等价与jQuery的选择器，如果在angular之前没有引入jQuery，那么就使用jqLite包装.\n\n\tangular.element('body');\n\t//等价于\n\t$('body');\n\t\n\t//用法\n\tvar $body = angular.element('body');\n\n### 2.5、angular.equals(★★)\n\n用于比较两个对象是否相等，如下示例的规则和JS有区别，注意识别。\n\n\tvar obj1 = {a: 1};\n\tvar obj2 = obj1;\n\t//引用一致，则相等\n\tconsole.log(angular.equals(obj1, obj2)); // true\n\t\n\tobj2 = {a: 1};\n\t//引用不一致，对象表现一致，则相等\n\tconsole.log(angular.equals(obj1, obj2)); // true\n\t\n\tobj2 = {a: 1,$a: 2};\n\t//对象比较时，忽略以$开头的属性\n\tconsole.log(angular.equals(obj1, obj2)); // true\n\t\n\tobj1 = /aa/;\n\tobj2 = /aa/;\n\t//正则表达式表现相等，则相等\n\tconsole.log(angular.equals(obj1, obj2)); // true\n\t\n\t//NaN与NaN比较，则相等\n\tconsole.log(angular.equals(NaN, NaN)); // true\n\n### 2.6、angular.extend(★★)\n\n功能上和jQuery.extend没多大差异\n\n\t//原型-第一个参数为目标，之后的参数为源，同时返回dst\n\tangular.extend(dst, src);\n\t\n\t//示例\n\tvar obj1 = {a: 1};\n\tvar obj2 = angular.extend(obj1, {a: 2}, {b: 3});\n\tconsole.log(obj1)\n\tconsole.log(obj1 === obj2); //true\n\n### 2.7、angular.forEach(★★★★★)\n\nangular.forEach用于遍历对象或者数组，类似于ES5的Array.prototype.forEach\n\n\t//原型\n\tangular.forEach(obj, iterator, [context]);\n\n\tvar values = {name: 'misko', gender: 'male'};\n\tvar arr = ['misko', 'male'];\n\tangular.forEach(values, function (value, key) {\n\t  console.log(key + ' = ' + value);\n\t});\n\tangular.forEach(arr, function (value, i) {\n\t  console.log(i + ' = ' + value);\n\t});\n\t//还可以传递this\n\tvar obj = {};\n\tangular.forEach(values, function (value, key) {\n\t  obj[key] = value;\n\t}, obj);\n\tconsole.log(obj);\n\n\n### 2.8、angular.fromJson(★★★)\n\nangular.fromJson将JSON字符串转换为JSON对象，注意，必须严格满足JSON格式，比如属性必须双引号，该函数内部实现是利用JSON.parse()。\n\n\t//原型\n\tangular.fromJson(/*string*/ jsonString)\n\n\tvar jsonString = '{\"p1\": \"xx\", \"p2\": 1, \"p3\": true}';\n\tvar jsonObj = angular.fromJson(jsonString);\n\tconsole.log(jsonObj);\n\n\n### 2.9、angular.toJson(★★★)\n\nangular.toJson可以将对象，数组，日期，字符串，数字转换为json字符串\n\n\t//原型\n\tangular.toJson(obj, pretty);\n\n\tvar obj = {p1: 1, p2: true, p3: '2'};\n\tvar jsonString = angular.toJson(obj);\n\tconsole.log(jsonString);\n\t//美化输出格式（设置为true，默认采用2个字符缩进）\n\tjsonString = angular.toJson(obj, true);\n\tconsole.log(jsonString);\n\t//还可以设置缩进字符数\n\tjsonString = angular.toJson(obj, 10);\n\tconsole.log(jsonString);\n\n### 2.10、angular.identity(★)\n\nangular.identity返回该函数参数的第一个值。编写函数式代码时，非常有用（待使用）。\n\n\t//官方示例\n\tfunction transformer(transformationFn, value) {\n\t  return (transformationFn || angular.identity)(value);\n\t};\n\t//简单演示\n\tvar value = angular.identity('a', 1, true);\n\tconsole.log(value); // 'a'\n\n### 2.11、angular.injector(★★)\n\nangular.injector能够创建一个injector对象，可以用于检索services以及用于依赖注入。\n\n\t//原型，[strictDi]默认false，如果true，表示执行严格依赖模式，\n\t//angular则不会根据参数名称自动推断类型，必须使用['xx', function(xx){}]这种形式。\n\tangular.injector(modules, [strictDi]); \n\n\t//定义模块A\n\tvar moduleA = angular.module('moduleA', [])\n\t  .factory('F1', [function () {\n\t    return {\n\t      print: function () {\n\t        console.log('I\\'m F1 factory');\n\t      }\n\t    }\n\t  }]);\n\tvar $injector = angular.injector(['moduleA'])\n\t$injector.invoke(function (F1) {\n\t  console.log(F1.print());\n\t});\n\t//此种写法会报错，因为是严格模式\n\tvar $injector2 = angular.injector(['moduleA'], true)\n\t$injector2.invoke(function (F1) {\n\t  console.log(F1.print());\n\t});\n\t//可以采用如下写法\n\t$injector2.invoke(['F1', function (F1) {\n\t  console.log(F1.print());\n\t}]);\n\n### 2.12、angular.module(★★★★★)\n\nangular.module可以说是最常用的function了。通过它，可以实现模块的定义，模块的获取。\n\n\t//定义模块A\n\tvar moduleA = angular.module('moduleA', []);\n\t//定义模块B，模块B依赖moduleA\n\tvar moduleB = angular.module('moduleB', ['moduleB']);\n\t\n\t//可以在第三个参数上设置配置函数\n\tvar moduleB = angular.module('moduleB', ['moduleB'], ['$locationProvider', function ($locationProvider) {\n\t  console.log($locationProvider);\n\t}]);\n\t//等价于\n\tvar moduleB = angular.module('moduleB', ['moduleB'])\n\t.config(['$locationProvider', function ($locationProvider) {\n\t  console.log($locationProvider);\n\t}]);\n\t\n\t//获取模块\n\tangular.bootstrap(document, ['moduleB']);"
  },
  {
    "path": "AngularJS相关/Angular从0到1：function（下）.md",
    "content": "---\ntitle: Angular从0到1：function（下）\ndate: 2017/02/21 14:47:10\n---\n\n## 1、前言\n\n## 2、function（下）\n\n### 2.13、angular.isArray(★★)\n\n``angular.isArray``用于判断对象是不是数组，等价于``Array.isArray``\n\n\tconsole.log(angular.isArray([])); // true\n\tconsole.log(angular.isArray({0: '1', 1: '2', length: 2})); // false\n\n### 2.14、angular.isDate(★★)\n\n通过判断toString.call(value)是不是等于'[object Date]' 来判断对象是个是一个Date对象.\n\n\tconsole.log(angular.isDate(new Date())); // true\n\tconsole.log(angular.isDate(223)); // false\n\n### 2.15、angular.isDefined(★★)\n\n判断对象或者属性是否定义\n\n\tvar obj = {a: 1, b: null, c: undefined};\n\tconsole.log(angular.isDefined(obj.a)); // true\n\tconsole.log(angular.isDefined(obj.b)); // true\n\tconsole.log(angular.isDefined(obj.c)); // false\n\tconsole.log(angular.isDefined(obj.d)); // false\n\n### 2.16、angular.isElement(★★)\n\n此方法判断元素是不是一个元素（包含dom元素，或者jquery元素）\n\n\tconsole.log(angular.isElement(document.getElementsByTagName('body')[0])); // true\n\tconsole.log(angular.isElement($('body'))); // true\n\n### 2.17、angular.isFunction(★★)\n\n此方法判断对象是不是一个function ，等价于 typeof fn === 'function'\n\n\tconsole.log(angular.isFunction(new Function('a', 'return a'))); // true\n\tconsole.log(angular.isFunction(function(){})); // true\n\tconsole.log(angular.isFunction({})); // false\n\n### 2.18、angular.isNumber(★★)\n\n判断数字是否为number\n\n\tfunction isNumber(value) {\n\t  return typeof value === 'number';\n\t}\n\n### 2.19、angular.isObject(★★)\n\n\tfunction isObject(value) {\n\t  return value !== null && typeof value === 'object';\n\t}\n\n### 2.20、angular.isString(★★)\n\n\tfunction isString(value) {\n\t\treturn typeof value === 'string';\n\t}\n\n### 2.21、angular.isUndefined(★★)\n\n\tfunction isUndefined(value) {\n\t\treturn typeof value === 'undefined';\n\t}\n\n### 2.22、angular.lowercase(★★)\n\n转换字符串为小写模式，如果参数不是字符串，那么原样返回\n\n\tvar lowercase = function(string) {\n\t  return isString(string) ? string.toLowerCase() : string;\n\t};\n\t\n\tconsole.log(angular.lowercase(1)); // 1\n\tconsole.log(angular.lowercase('ABCdef')); // 'abcdef'\n\n### 2.23、angular.uppercase(★★)\n\n转换字符串为大写模式，如果参数不是字符串，那么原样返回\n\n\tvar uppercase = function(string) {\n\t  return isString(string) ? string.toUpperCase() : string;\n\t};\n\t\n\tconsole.log(angular.uppercase(1)); // 1\n\tconsole.log(angular.uppercase('ABCdef')); // 'ABCDEF'\n\n### 2.24、angular.merge(★★)\n\n将多个对象进行深度复制，与extend()不同，merge将会递归进行深度拷贝。该拷贝是完全深拷贝，就连对象引用也不一样。\n\n\tvar o = {};\n\tvar obj1 = {a1: 1, a2: 2, a3: [1]};\n\tvar obj2 = {b1: [2], b2: /abc/};\n\tvar obj3 = [o];\n\tvar obj4 = {d: o};\n\tvar result = angular.merge({}, obj1, obj2, obj3);\n\tconsole.log(result);\n\tconsole.log(result[0] === o); // false\n\tconsole.log(result.d === o); // false\n\n### 2.25、angular.noop(★★)\n\n一个空函数，调试时非常有用。可以避免callback未定义引发的error。\n\n\tfunction foo(callback) {\n\t  var result = calculateResult();\n\t  (callback || angular.noop)(result);\n\t}\n\n\n### 2.26、angular.reloadWithDebugInfo(★★)\n\n启用DebugInfo，该设置优先级高于``$compileProvider.debugInfoEnabled(false)``"
  },
  {
    "path": "AngularJS相关/Angular再回首(1)-Component组件.md",
    "content": "---\ntitle: Angular再回首(1)-Component组件\ndate: 2017/02/21 14:47:10\n---\n\n## 0、再谈组件\n\n``Component(组件)`` 在 ``Angular1`` 就已经有雏形了，那就是指令。在 ``Angular2`` 中，组件的概念被大大的强化，甚至是Angular2的核心概念。\n\n在前端这么多年的演变中，组件也反哺到 ``Angular1``，成为 ``Angular1`` 的一种重要特性，在此之前，我们仅仅可以用 ``Directive`` 来实现类似组件的效果。\n\n## 1、Angular组件与指令\n\n在 ``Angular 1.5.x`` 中，新增加了 ``angular.component`` 方法，用于实现组件的构造。\n\n在此之前，我们可能用 ``angular.directive`` 来实现类似的效果。\n\n这个时候我们可能就会疑惑，它们有什么区别呢？\n\n| Feature | Directive | Component |\n|---|---|---|\n| bindings | No | Yes (binds to controller) |\n| bindToController | Yes | (default: false)\tNo (use bindings instead) |\n| compile function |Yes |\tNo |\n| controller |\tYes |\tYes (default function() {}) |\n| controllerAs |\tYes | (default: false)\tYes (default: $ctrl)|\n| link functions |\tYes |\tNo|\n| multiElement\t| Yes|\tNo|\n| priority|\tYes|\tNo|\n| require\t| Yes|\tYes|\n| restrict\t|Yes|\tNo (restricted to elements only)|\n| scope\t|Yes (default: false)\t|No (scope is always isolate)|\n| template\t|Yes\t|Yes, injectable|\n| templateNamespace|\tYes|\tNo|\n| templateUrl\t|Yes\t|Yes, injectable|\n| terminal|\tYes| \tNo|\n| transclude\t|Yes (default: false)\t|Yes (default: false)|\n\n更多信息，请参考 [Angular 官方说明](https://docs.angularjs.org/guide/component)\n\n从上表我们可以看出，对于 ``Directive``，``Component`` 从设计思路上更加完善，也更加纯粹。总得来说，组件显得更易理解，更简单易用。\n\n## 2、组件生命周期\n\n在 ``angular.directive()`` 中，是没有生命周期这个概念的，我们无法在指令的特定阶段插入自己的逻辑。\n\n但是在 ``angular.component()`` 中，则是具有特定的生命周期，以方便我们进行控制。\n\n生命周期如下：\n\n1. $onInit  -- 指令初始化时执行（放置初始化代码）\n2. $onChanges(changesObj)  -- 组件数据变化时执行，并可获取变更对象\n3. $doCheck() -- 执行变更检测时执行\n4. $onDestroy() -- 组件释放时执行（放置清理代码）\n5. $postLink() -- 类似后连接函数 （一般放置dom操作，因为此时组件已经渲染好）\n\n实例：\n\n```javascript\n((angular, window) => {\n  class AlertComponent {\n    constructor() {\n    }\n\n    $onInit() {\n      console.log('init');\n    }\n\n    $onChanges(changesObj)\n      console.log('change', changesObj);\n    }\n\n    $doCheck() {\n      console.log('check');\n    }\n\n    $onDestroy() {\n      console.log('destroy');\n    }\n\n    $postLink() {\n      console.log('post link');\n    }\n  }\n\n  AlertComponent.$inject = []; // 配置依赖项\n\n  angular.module('components').component('jAlert', {\n    templateUrl: 'components/alert/alert.html',\n    // scope绑定语法，< 单向绑定（变量），@ 单向绑定（纯字符串）， = 双向绑定，& 事件绑定\n    bindings: {\n      menuData: '<'\n    },\n    controller: AlertComponent,\n    controllerAs: '$ctrl',\n    require: '',\n    transclude: false\n  });\n\n})(window.angular, window);\n```\n\n在页面使用该指令后，可以在控制台看出如下输出：\n\n```\ninit\ncheck\npost link\nN个check（脏检查）\n```\n\n在切换路由，或者其他会删掉该组件的操作时，会看出控制台输出 ``destroy``。\n\n如果中途有数据变化，控制台还会输出 ``change``。 \n\n这就是整个组件的生命周期。\n\n## 3、属性绑定\n\n在 ``directive`` 中，我们要获取数据，一般会采用 ``$scope`` 传参，或者通过link函数来捕获参数。\n\n在新的组件申明中，我们只需要通过 ``bindings`` 就可以实现复杂的参数绑定。\n\n简单思考下，我们可能需要哪些绑定呢？\n\n1. 双向绑定 （双向）\n2. 单向绑定变量 （从外到内）\n3. 单向绑定属性（字符串）（从外到内）\n4. 输出绑定 （从内到外）\n\n在组件的 ``bindings`` 属性中，我们也刚好有四种语法，来一一对应这四种绑定。\n\n具体写法如下： \n\n```javascript\nbindings: {\n  model: '=',  // 双向绑定\n  title: '@',  // 单项绑定字符串（直接用组件上的属性值）\n  key: '<',  // 单项绑定变量，取到属性值，然后返回$scope[属性值]\n  onClick: '&'  // 输出绑定，执行外部函数\n}\n```\n\n假设组件标签为 ``<j-test>``，那么用法如下：\n\n```javascript\n$scope = {\n  model: '1',\n  key: 'abc',\n  onClick: () => {\n\n  }\n};\n```\n\n```html\n<j-test model=\"model\" key=\"key\" title=\"Title\" on-click=\"onClick()\"></j-test>\n```\n\n此时，我们在组件中，就能获取到对应的值： \n\n```javascript\n{\n  model: 1, // 从scope中取\n  key: 'abc', // 从scope中取\n  title: 'Title', // 直接用string\n  onClick: fn // 执行该onClick会触发外部函数$scope.onClick\n}\n```\n\n**注意：关于输出函数传递参数，需要有特定的写法（一定要注意！！！）**\n\n*在组件中的写法*\n\n在组件中，要给该函数传参，必须使用：\n\n```javascript\nthis.onClick({\n  param1: 'xxx',\n  param2: 'BBB'\n});\n```\n\n的写法，并建议参数名使用 ``$`` 开头，如：``$event``。\n\n*在组件绑定中的写法*\n\n```html\n<j-test model=\"model\" key=\"key\" title=\"Title\" on-click=\"onClick(param1, param2)\"></j-test>\n```\n\n注意onClick的写法，里面的参数名称，必须和组件中参数对象中的key匹配。\n\n## 4、给组件设定外部HTML\n\n在使用组件过程中，我相信很容易遇到需要使用外部html的组件，如 ``Tabs, Panel`` 等，那我们给组件内部传入自定义的HTML呢？\n\n这个时候，我们可以使用 ``ng-transclude``\n\n### 4.1、传递单个HTML片段\n\n首先，主要在注册组件时，开启 ``transclude``（设置transclude为true），然后我们就可以在组件html中，设定占位符，有如下两种方式：\n\n```html\n<!-- 占位符1 -->\n<div ng-transclude></div>\n\n<!-- 占位符2 -->\n<ng-transclude></ng-transclude>\n```\n\n然后在使用组件的地方，就可以直接把要使用的HTML放在组件标记中，如：\n\n```html\n<j-test>\n  <span>我会被传递到主键内部</span>\n</j-test>\n```\n\n### 4.2、传递多个HTML片段\n\n以上，我们知道了如何传递单个HTML片段，但传递多个HTML片段也是非常有必要的，如 ``Dialog``组件，\n我们很可能会传递 ``dialog-header``, ``dialog-body`` 等等，那此时又应如何呢？\n\n这个场景，我们可以借助 ``ng-transclude`` 的 ``slot`` 功能实现，\n\n首先，是占位符的变化，如下：\n\n```html\n<!-- 占位符1 -->\n<div ng-transclude=\"header\"></div>\n<div ng-transclude=\"body\"></div>\n\n<!-- 占位符2 -->\n<ng-transclude ng-transclude-slot=\"header\"></ng-transclude>\n<ng-transclude ng-transclude-slot=\"body\"></ng-transclude>\n```\n\n其次是组件配置的变化，因为有多个 ``transclude``，那么仅仅设置为 ``true``，就不太能满足需求了。\n需要修改如下：\n\n```javascript\ntransclude: {\n  header: '?panelHeader', // panelHeader表示内部标签，?表示是可选的\n  body: 'panelBody' // 没有问号，表示该节点必选\n}\n```\n\n接下来，就应该是调用时的改变，调用如下：\n\n```html\n<j-panel>\n  <panel-header>\n    我是Panel Header（可选）\n  </panel-header>\n  <panel-body>\n    我是Panel Body（必须）\n  </panel-body>\n</j-panel>\n```\n\n## 5、组件 ``require``\n\n同 ``Directive`` 一样，组件也可以相互依赖，只需要在注册组件时，设置require属性即可，写法如下：\n\n```javascript\nrequire: {\n  componentCtrl: '^parentComponent'\n}\n```\n\n## 6、小结\n\n新增的 ``angular.component`` 就是这么一个东西，比起 ``directive`` 更加纯粹，更加强大，更加易用。\n建议在后续使用中，多多尝试该方式。"
  },
  {
    "path": "AngularJS相关/Angular再回首(2)-那些容易忽略的Component细节.md",
    "content": "---\ntitle: Angular再回首(2)-那些容易忽略的Component细节\ndate: 2017/02/21 14:47:10\n---\n\n## 0、前言\n\n在 ``Angular 1.5.x`` 中，增加的组件方法，相当实用和易用。但也有许多小细节问题值得注意，\n以下为本人在组件实践过程中遇到的问题，或者是需要注意的小细节。\n\n## 1、问题/小细节（需要注意的点）\n\n### 1.1、如何判断是否添加了可选的 ``transclude`` 元素？\n\n在很多时候，我们会给一个组件设定多个 ``transclude``，可能其中有一部分是可选的，那如何判断可选的 ``transclude`` 被用户设置了值呢？\n\n此时，我们可以依靠 ``$transclude`` 来进行判断：\n\n```javascript\nclass XXXComponent{\n  constructor($transclude){\n    this.$transclude = $transclude;\n  }\n\n  $onInit(){\n    // 判断transclude是否存在\n    let transcludeName = 'xxx';\n    let hasXXX = this.$transclude.isSlotFilled(transcludeName);\n  }\n}\n\nXXXComponent.$inject = ['$transclude'];\n```\n\n### 1.2、如何监控绑定属性的变更？\n\n属性绑定，分为一次性绑定(@)（也算是单向绑定），单向绑定(<)，双向绑定(=)。\n\n**# 监控单向绑定属性**\n\n对于单向绑定的属性，可以通过生命周期钩子 ``$onChanges(changesObj)`` 来进行监控。\n\n```javascript\nclass XXXController{\n  $onChanges(changesObj){\n    console.log(changesObj);\n  }\n}\n```\n\n其中参数 ``changesObj`` 是所有变更属性的一个汇总，数据结构如下：\n\n```json\nchangesObj = {\n  key1: { // 有变更的绑定属性\n    currentValue: any // 当前值 （变化后的值）\n    previousValue: any // 上一次的值 （变化前的值）\n    isFirstChange(): fn // 方法，用于判断是否是第一次变更。 \n  }\n}\n```\n\n**注意：``$onChanges`` 无法监控双向绑定属性，切记！**\n\n**# 监控双向绑定**\n\n由于 ``$onChanges`` 无法监控双向绑定属性，那么我们就必须另外想办法来进行监控，可以有以下几种方案：\n\n*方案一：利用 ``$interval``*\n\n既然是双向绑定，那么肯定变化是直接生效的，关键就在于我们无法监视到，这个时候我们可以利用 ``$interval`` 来实现定时监控。\n\n```javascript\nclass XXXController{\n  constructor($interval){\n    this.$interval = $interval;\n    this.init();\n  }\n  init(){\n    let previousValue = null;\n    this.$interval(() => {\n      if(previousValue !== this.value){\n        previousValue = this.value;\n        console.log('value changed');\n      }\n    }, 200);\n  }\n}\nXXXController.$inject = ['$interval'];\n\nangular.module('xxx').component('xxx', {\n  bindings: {\n    value: '='\n  },\n  controller: XXXController,\n  controllerAs: 'vm'\n});\n\n```\n\n优点：\n1. 易于理解\n\n缺点：\n1. 浪费资源\n2. 需要自己书写逻辑\n\n推荐指数： ☆\n\n*方案二：利用 ``$scope.$watch(keyString)``*\n\n组件也有独立的 ``$scope``，那么借助 ``$scope.$watch`` 也可以实现监听属性变化，代码如下：\n\n```javascript\nclass XXXController{\n  constructor($scope){\n    this.$scope = $scope;\n    this.init();\n  }\n  init(){\n    this.$scope.$watch('vm.value', (newVal, oldVal) => {\n      console.log('value changed);\n    });\n  }\n}\nXXXController.$inject = ['$scope'];\n\nangular.module('xxx').component('xxx', {\n  bindings: {\n    value: '=' // 双向绑定属性\n  },\n  controller: XXXController,\n  controllerAs: 'vm'\n});\n```\n\n优点：\n1. 使用简单\n\n缺点：\n1. 字符串形式的 ``$watch``,依赖 ``controllerAs``,不易理解 \n2. 实质仍然是定时器，只不过是使用的 ``angular`` 自身的 ``$diget`` 循环\n\n推荐指数： ☆☆\n\n*方案三：利用 ``$scope.$watch(fn)``*\n\n``$scope.$watch`` 也接受函数类型的参数，相对于字符串形式，没有 ``controllerAs`` 的相关性，而且更灵活，代码如下：\n\n```javascript\nclass XXXController{\n  constructor($scope){\n    this.$scope = $scope;\n    this.init();\n  }\n  init(){\n    this.$scope.$watch(() => this.value, (newVal, oldVal) => {\n      console.log('value changed);\n    });\n  }\n}\nXXXController.$inject = ['$scope'];\n\nangular.module('xxx').component('xxx', {\n  bindings: {\n    value: '=' // 双向绑定属性\n  },\n  controller: XXXController,\n  controllerAs: 'vm'\n});\n```\n\n优点：\n1. 使用简单\n\n缺点：\n1. 实质仍然是定时器，只不过是使用的 ``angular`` 自身的 ``$diget`` 循环\n\n推荐指数： ☆☆☆☆\n\n*方案四：利用 ``getter & setter``*\n\n因为我们使用了 ``ES6 Class``，那么 ``ES6`` 的 ``getter setter`` 特性，我们也是能够使用的，方式如下：\n\n```javascript\nclass XXXController{\n\n  set value(val){\n    this._value = val;\n    console.log('value changed');\n  }\n  get value(){\n    return this._value;\n  }\n}\nXXXController.$inject = [];\n\nangular.module('xxx').component('xxx', {\n  bindings: {\n    value: '=' // 双向绑定属性\n  },\n  controller: XXXController,\n  controllerAs: 'vm'\n});\n```\n优点：\n1. 没有额外的开销，性能高\n\n缺点：\n1. 使用相对较为复杂\n\n推荐指数： ☆☆☆☆"
  },
  {
    "path": "AngularJS相关/Angular再回首(3)-我们来实现一个组件.md",
    "content": "---\ntitle: Angular再回首(3)-我们来实现一个组件\ndate: 2017/02/21 14:47:10\n---\n\n## 0、前言\n\n前两文写了 ``Component`` 的一些方面，但没有一个比较线性的串联关系，本文，就来从一个实例出发，来尝试概括一个组件的方方面面。\n\n## 1、\n\n## 2、组件实现\n\n### 2.1、先整一个组件\n\n```javascript\nangular.module('app', [])\n  .component('finalComponent', {});\n```\n\n这个组件啥都不干，就提供了一个新的标签，显得毫无意义，但是我们可以从这里看到如何定义一个组件。\n\n**注意：组件名称，请使用小驼峰命名法，在HTML中，请使用连字符+小写字母，这种实现是为了处理js和html大小写敏感的差异(js区分大小写，html不区分)**\n\n**注意2：如果在组件标签中，嵌入有效的标签，是会显示出来的，如下：**\n\n```html\n<final-component>\n  <h1>Hello</h1>\n</final-component>\n```\n\n会显示出大号的 “Hello”。\n\n### 2.2、带模板的组件\n\n接着，来实现一个有意义的组件，比如我要渲染一个特定的字符串，代码如下：\n\n```javascript\nangular.module('app', [])\n  .component('finalComponent', {\n    template: '<h1>Hello World.</h1>'\n  });\n```\n现在我们再使用：`<final-component>ABC</final-component>`，则会显示 \"Hello World\" 内容了。\n\n**注意：当组件指定了模板属性后，其内部的标签，将不会生效（transclude除外，）**\n\n### 2.3、复杂模板的组件\n\n以上，我们已经实现了带模板的组件，可是我们的模板可能会比较复杂，这个时候直接写 `template` 就不太好用了,此时，我们会考虑把模板拆分到一个独立的 `.html` 文件中，代码如下：\n\n```html\n<!-- 组件模板内容(文件名为:template.html) -->\n<h1>Hello World.</h1>\n```\n\n然后，使用 `templateUrl` 属性进行关联\n\n```javascript\nangular.module('app', [])\n  .component('finalComponent', {\n    templateUrl: '/app/template.html'\n  });\n```\n\n该代码可以达到 2.2 同样的效果，只是把模板内容拆分到独立文件中了。\n\n**注意：模板路径可以是相对路径，也可以是绝对路径，需要注意路径的写法，否则会出现找不到模板**\n\n**注意2：如果使用 `gulp` 构建，可以考虑使用 `gulp-angular-embed-templates` 将独立的模板文件，打包到组件中。**\n\n### 2.4、组件属性绑定\n\n之前实现的组件，感觉太死板了，我想改下文字，都不好实现（你非要用js强制操作dom，我拿你也没办法，不过后果自负），这个时候，我们迫切的需要能给组件传递参数。\n\n`Angular` 组件中，有多个参数传递方式，如下：\n\n* @ 单向绑定字符串（原值绑定） - 传什么就是什么，不做任何处理\n* < 单向绑定变量（取scope的值绑定） - 传的值会先用 `$scope` 转换，把结果传递给组件\n* = 双向绑定 - 组件内外变化都会通知另一方\n\n#### 2.4.1 直接传递字符串参数\n\n使用 `@` 进行单向字符串绑定\n\n```javascript\nangular.module('app', [])\n  .component('finalComponent', {\n    templateUrl: '/app/template.html'\n    bindings: {\n      name: '@'\n    }\n  });\n```\n\n```html\n<!-- 模板内容 -->\n<h1>Hello {{$ctrl.name}}.</h1>\n\n<!-- 使用组件 -->\n<final-component name=\"Jay\"></final-component>\n```\n\n此时，将会显示“Hello Jay”，可以看到，设定的参数值会原样显示了。\n\n**注意：在模板中，要使用变量，需要加$ctrl前缀，先这样用着，后面会提到**\n\n#### 2.4.2 使用单向绑定变量\n\n```javascript\nclass TestController{\n  constructor(){\n    this.name = 'Jay'\n  }\n}\nTestController.$inject = []; // 依赖\nangular.module('app', [])\n  .component('finalComponent', {\n    templateUrl: '/app/template.html'\n    bindings: {\n      name: '<'\n    }\n  })\n  .controller('TestController', TestController);\n```\n\n```html\n<!-- 模板内容 -->\n<h1>Hello {{$ctrl.name}}.</h1>\n\n<!-- 使用组件 -->\n<div ng-controller=\"TestController as t\">\n  <final-component name=\"t.name\"></final-component>\n</div>\n```\n\n此时，也将会显示“Hello Jay”，可以看到，此时 `t.name` 会拿到 `$scope` 中进行解析。\n\n**注意：推荐使用 `controller as` 写法**\n\n#### 2.4.3 双向绑定\n\n```javascript\nclass TestController{\n  constructor(){\n    this.name = 'Jay'\n  }\n}\nTestController.$inject = []; // 依赖\nangular.module('app', [])\n  .component('finalComponent', {\n    templateUrl: '/app/template.html'\n    bindings: {\n      name: '='\n    }\n  })\n  .controller('TestController', TestController);\n```\n\n```html\n<!-- 模板内容 -->\n<h1>\n  <h1>Hello {{$ctrl.name}}.</h1>\n  <input ng-model=\"$ctrl.name\"></input>\n</h1>\n\n<!-- 使用组件 -->\n<div ng-controller=\"TestController as t\">\n  <final-component name=\"t.name\"></final-component>\n  <h3>{{t.name}}</h3>\n</div>\n```\n此时，在文本框输入值之后，可以看到组件内外都会及时变更。\n\n"
  },
  {
    "path": "AngularJS相关/Angular开发Tips.md",
    "content": "---\ntitle: Angular开发Tips\ndate: 2017/02/21 14:47:10\n---\n\n1、在使用$routeProvider的时候，需要让模块依赖ngRoute，否则会提示找不到服务，示例：\n\n\tangular.module('module1', ['ngRoute'])\n\t.config(['$routeProvider', function($routeProvider){\n\t  //do something...\n\t}]);\n\n2、在页面中需要绑定有风险的html的时候，可以使用 `ng-bind-html=\"html\"（version>=1.3）`,如果遇到错误，控制器中可以使用`html = $sce.trustHtml(unsafeHtml)`。\n\n3、 如何动态的向页面添加带指令的HTML？通入如下代码：\n\n\t$compile(html)($scope);\n\n4、如果阻止事件冒泡？示例如下：\n\t\n\t//方式一，利用一个自定义指令实现\n\t.directive('stopEventPropagation', function(){\n\t  return {\n\t    restrict: 'A',\n\t    link: function(scope, iElement, iAttrs){\n\t      //通过获取事件对象，来阻止调用\n\t      iElement.bind('click', function(e){\n\t        e.stopPropagation();\n\t      });\n\t    }\n\t  }\n\t});\n\t\n\t<a stop-event-propagation ng-click=\"doSomething();\">Click me</a>\n\t\n\t//方式二，直接引用$event对象\n\t\n\t<a ng-click=\"doSomething(); $event.stopPropagation();\">Click me</a>\n\n5、关于$route和$location的事件顺序，如下：\n\n\t$routeChangeStart -> $locationChangeStart -> $locationChangeSuccess -> $routeChangeSuccess\n\n6、有关select标签的使用，当options的来源是ajax时，那么如果指定选中项呢？如下：\n\n\t<select ng-options=\"sysOptions\" ng-model=\"selectSystem\"></select>\n\t//如上HTML代码，如果sysOptions来自ajax请求，而selectSystem又不是的话，往往会选中一个空值。\n\t//可以使用如下方式避免：\n\n```javascript\n\t.controller('TestCtrl', ['$scope', '$http', function($scope, $http){\n\t  $http.get(url).success(function(data){\n\t    $scope.sysOptions = data;\n\t    //在异步回调函数中，对ng-model赋值。\n\t    $scope.selectSystem = 'Test';\n\t  });\n\t}]);\n```\n\n7、在编写指令时，属性的匹配大小写需要注意：如果在html中使用 `showName=\"xx\"`,那么在指令的iAttrs中，应该使用 `showname` 获取。如果要在指令中使用showName获取的话，那么必须在html中使用 `show-name=\"xx\"`。\n\n8、要生成安全链接时，需要修改配置，代码如下：\n\n```javascript\n需要将如下代码： ng-href=\"{{true: 'javascript:void(0);' : 'url'}}\" \n生成为： href=\"javascript:void(0);\"\n```\n\n```javascript\n.config(['$compileProvider', function($compileProvider){\n    $compileProvider.aHrefSanitizationWhitelist(/^\\s*(https?|ftp|mailto|file|javascript):/)\n}]);\n```\n\n9、在ng-click等ng事件中，如果拿到事件源对象？如下：\n\n```javascript\n<a ng-click=\"click($event);\" />\n\n$scope.click = function($event){\n  var target = $event.target;\n};\n//注意，如果使用ng-click=\"click($event.target)\"，将会导致angular解析错误。\n```\t\n\n10、判断angular的模块是否存在，可以使用如下代码：\n\n\tvar isAngularModuleExists = function(moduleName){\n\t  try{\n\t    angular.module(moduleName)\n\t  }catch{\n\t    return false\n\t  }\n\t  return true;\n\t};\n\n11、在使用coffee编写使用provider方式编写服务时，当心写在最后的this.$get，coffee会将最后一句编译为return this.$get，而这刚好不符合provider的要求，所以应该在末尾手动加上return或者放置一个undefined在最后，放置编译出return this.$get这样的代码。\n\n12、如果要动态控制是否启用非空验证，可以使用ng-required=\"true|false\"指令。\n\n13、当心ng-if指令，在使用ng-if指令时，会创建独立的作用域，如果要在$scope监视ng-if包含的变量，那么是无法成功的。如果一定要监视，可以考虑使用ng-show。\n\n14、注意.value()与.constant的区别，前者只能注入和用于服务或者控制器中，后则可以被注入到配置(.config(['xx']))中。\n"
  },
  {
    "path": "AngularJS相关/Angular：指令、Controller数据共享.md",
    "content": "---\ntitle: Angular：指令、Controller数据共享\ndate: 2015/3/13 11:24:39\n---\n\n## 1、Directive与Controller数据共享\n\n在指令中，不仅仅需要指令配置信息，很多时候也需要获取$scope的相关数据。那么，如何在指令中拿到$scope的数据呢？\n\n### 1.1、Directive和Controller使用同一个scope\n\n\t<!doctype html>\n\t<html>\n\t  <head>\n\t    <meta charset=\"utf-8\" />\n\t    <title>Angular Demo</title>\n\t  </head>\n\t  <body>\n\t    <div ng-controller=\"DemoCtrl\">\n\t      <d1></d1>\n\t    </div>\n\t\n\t    <!-- 脚本区域 -->\n\t    <script src=\"https://ajax.googleapis.com/ajax/libs/angularjs/1.3.15/angular.min.js\"></script>\n\t    <script>\n\t      angular.module('app', [])\n\t      .directive('d1', [function(){\n\t        return {\n\t          restrict: 'E',\n\t          scope: false, //defualt value is false\n\t          template: '<h1>Hi,{{name}}</h1>',\n\t          link: function(scope, iElement, iAttrs){\n\t            console.log('directive scope id = ' + scope.$id);\n\t          }\n\t        }\n\t      }])\n\t      .controller('DemoCtrl', ['$scope', function($scope){\n\t        console.log('controller scope id = ' + $scope.$id);\n\t        $scope.name = 'Jay';\n\t      }]);\n\t\n\t      //可以采用如此方式启动angular扫描，或者直接使用ng-app=\"app\"\n\t      angular.bootstrap(document.body, ['app']);\n\t    </script>\n\t    <!-- 脚本区域 End -->\n\t  </body>\n\t</html>\n\n执行以上代码，页面显示Hi Jay，并在控制台打印\n\n\tcontroller scope id = 2\n\tdirective scope id = 2\n\n在指令中，默认会直接使用上级的scope，从控制台来看，先执行controller的scope，再执行directive的scope。因为id一致，所以是同一个scope。既然是同一个scope，那么共享数据自然就不是问题了。该方式，适合业务性质的directive，如果是公共的directive，不建议使用此方式，可能会导致scope杂乱。\n\n### 1.2、在指令作用域中使用@，将当前属性作为字符串传递\n\n\t<!doctype html>\n\t<html>\n\t  <head>\n\t    <meta charset=\"utf-8\" />\n\t    <title>Angular Demo</title>\n\t  </head>\n\t  <body>\n\t    <div ng-controller=\"DemoCtrl\">\n\t      <d1 name=\"{{key}}\"></d1>\n\t    </div>\n\t\n\t    <!-- 脚本区域 -->\n\t    <script src=\"https://ajax.googleapis.com/ajax/libs/angularjs/1.3.15/angular.min.js\"></script>\n\t    <script>\n\t      angular.module('app', [])\n\t      .directive('d1', [function(){\n\t        return {\n\t          restrict: 'E',\n\t          scope: {\n\t            name: '@'\n\t          },\n\t          template: '<h1>Hi,{{name}}</h1>',\n\t          link: function(scope, iElement, iAttrs){\n\t            console.log('directive scope id = ' + scope.$id);\n\t          }\n\t        }\n\t      }])\n\t      .controller('DemoCtrl', ['$scope', function($scope){\n\t        console.log('controller scope id = ' + $scope.$id);\n\t        $scope.key = 'Jay';\n\t\n\t      }]);\n\t\n\t      //可以采用如此方式启动angular扫描，或者直接使用ng-app=\"app\"\n\t      angular.bootstrap(document.body, ['app']);\n\t    </script>\n\t    <!-- 脚本区域 End -->\n\t  </body>\n\t</html>\n\n以上代码，主要修改了指令的scope，从输出来看，指令和controller各自是自己独有的作用域。\n\n``scope = {name: '@'}``，等价于\n\n\tlink:function(scope, iElement, iAttrs){\n\t    scope.name = iAttrs.name;\n\t}\n\nController中的key的变化，会即时影响到Directive的变化，但是Directive的变化并不会反向影响到Controller，结果近似于单向绑定。\n\n### 1.3、在指令的作用域中使用=，进行数据的双向绑定\n\n\t<!doctype html>\n\t<html>\n\t  <head>\n\t    <meta charset=\"utf-8\" />\n\t    <title>Angular Demo</title>\n\t  </head>\n\t  <body>\n\t    <div ng-controller=\"DemoCtrl\">\n\t      key = {{key}}\n\t      <d1 name=\"key\"></d1>\n\t    </div>\n\t\n\t    <!-- 脚本区域 -->\n\t    <script src=\"https://ajax.googleapis.com/ajax/libs/angularjs/1.3.15/angular.min.js\"></script>\n\t    <script>\n\t      angular.module('app', [])\n\t      .directive('d1', [function(){\n\t        return {\n\t          restrict: 'E',\n\t          scope: {\n\t            name: '='\n\t          },\n\t          template: '<h1>Hi,{{name}}</h1><input type=\"text\" ng-model=\"name\" />',\n\t          link: function(scope, iElement, iAttrs){\n\t            console.log('directive scope id = ' + scope.$id);\n\t          }\n\t        }\n\t      }])\n\t      .controller('DemoCtrl', ['$scope', function($scope){\n\t        console.log('controller scope id = ' + $scope.$id);\n\t        $scope.key = 'Jay';\n\t\n\t      }]);\n\t\n\t      //可以采用如此方式启动angular扫描，或者直接使用ng-app=\"app\"\n\t      angular.bootstrap(document.body, ['app']);\n\t    </script>\n\t    <!-- 脚本区域 End -->\n\t  </body>\n\t</html>\n\n以上代码的变化在于，使用了scope: {name: '='}，该代码将父作用域的属性和指令的属性进行双向绑定。所以指令中文本框的值的变化，将会同步影响controller中key的变化。\n\n**注意：在使用指令的时候，html代码，并不是和示例1.1一致了，如果是双向绑定，那么应该使用<d1 name=\"key\" /&gt;，而不是<d1 name=\"{{key}}\"&gt;。**\n\n### 1.4、在Directive中调用Controller的方法\n\n\t<!doctype html>\n\t<html>\n\t  <head>\n\t    <meta charset=\"utf-8\" />\n\t    <title>Angular Demo</title>\n\t  </head>\n\t  <body ng-app=\"app\">\n\t    <div ng-controller=\"DemoCtrl\">\n\t      key = {{key}}\n\t      <d1 name=\"key\" show-name=\"show(key)\"></d1>\n\t    </div>\n\t\n\t    <!-- 脚本区域 -->\n\t    <script src=\"https://ajax.googleapis.com/ajax/libs/angularjs/1.3.15/angular.min.js\"></script>\n\t    <script>\n\t      angular.module('app', [])\n\t      .directive('d1', [function(){\n\t        return {\n\t          restrict: 'E',\n\t          scope: {\n\t            name: '=',\n\t            showName: '&'\n\t          },\n\t          template: '<h1>Hi,{{name}}</h1><input type=\"text\" ng-model=\"name\" />' \n\t          + '<button ng-click=\"showName(name)\">Show</button>',\n\t          link: function(scope, iElement, iAttrs){\n\t            console.log('directive scope id = ' + scope.$id);\n\t          }\n\t        }\n\t      }])\n\t      .controller('DemoCtrl', ['$scope', function($scope){\n\t        console.log('controller scope id = ' + $scope.$id);\n\t        $scope.key = 'Jay';\n\t        $scope.show = function(name){\n\t            alert(name);\n\t        };\n\t      }]);\n\t    </script>\n\t    <!-- 脚本区域 End -->\n\t  </body>\n\t</html>\n\n点击指令生成的按钮，会执行controller的show方法，利用在scope: {showName: '&'}，可以将父级作用域的方法绑定到指令中。\n\n**注意，一定要注意属性命令，在html中书写showName，那么在iAttrs中对应showname，只有在html中书写show-name,在会在iAttrs中对应showName。**\n\n## 2、在controller中，拿到directive的作用域\n\n### 2.1、拿到scope的元素，调用isolateScope获取scope\n\n\t<!doctype html>\n\t<html>\n\t  <head>\n\t    <meta charset=\"utf-8\" />\n\t    <title>Angular Demo</title>\n\t  </head>\n\t  <body ng-app=\"app\">\n\t    <div ng-controller=\"DemoCtrl\">\n\t      key = {{key}}\n\t      <button ng-click=\"click()\">Click</button>\n\t      <hr />\n\t      <d1 id=\"d1\" name=\"key\" show-name=\"show(key)\"></d1>\n\t    </div>\n\t\n\t    <!-- 脚本区域 -->\n\t    <script src=\"//ajax.aspnetcdn.com/ajax/jQuery/jquery-2.1.3.min.js\"></script>\n\t    <script src=\"https://ajax.googleapis.com/ajax/libs/angularjs/1.3.15/angular.min.js\"></script>\n\t    <script>\n\t      angular.module('app', [])\n\t      .directive('d1', [function(){\n\t        return {\n\t          restrict: 'E',\n\t          scope: {}, //等价于 scope: true\n\t          template: '<h1>Hi,{{name}}',\n\t          link: function(scope, iElement, iAttrs){\n\t            scope.name = 'directive name';\n\t            console.log('directive scope id = ' + scope.$id);\n\t          }\n\t        }\n\t      }])\n\t      .controller('DemoCtrl', ['$scope', function($scope){\n\t        console.log('controller scope id = ' + $scope.$id);\n\t        $scope.click = function(){\n\t          var dirScope = $('#d1').isolateScope();\n\t          alert(dirScope.name);\n\t        }\n\t      }]);\n\t    </script>\n\t    <!-- 脚本区域 End -->\n\t  </body>\n\t</html>\n\n此代码中，利用$('#d1').isolateScope，拿到了该指令的scope，所以可以随时方式，该方式在多种指令中也有效。\n\n**如果判断应该用isolateScope()还是scope()获取作用域？一个最简单的方式，用F12查看源码，找到该元素，然后查看class是ng-isolate-scope还是ng-scope**\n\n## 3、 指令之间相互获取数据\n\n### 3.1、通过directive依赖来共享数据\n\n\t<script>\n\t      angular.module('app', [])\n\t      .directive('d1', [function(){\n\t        return {\n\t          restrict: 'E',\n\t          require: '^ngModel',\n\t          scope: {}, //等价于 scope: true\n\t          template: false,\n\t          link: function(scope, iElement, iAttrs, ngModelCtrl){\n\t            \n\t          }\n\t        }\n\t      }])\n\t      .controller('DemoCtrl', ['$scope', function($scope){\n\t        \n\t      }]);\n\t</script>\n\n### 3.2、通过如2.1的方式获取数据\n\n## 4、 其他Hacky的方式\n\n1. 通过``$parent``访问父级作用域\n2. 通过``$$prevSibling``访问该作用域的上一个兄弟作用域\n3. 通过``$$nextSibling``访问该作用域的下一个兄弟作用域\n4. 通过``$$childHead``访问儿子作用域的第一个\n5. 通过``$$childTail``访问儿子作用域的最后一个\n\n## 5、参考资料\n\n1. [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/)\n\n2. [directive和controller如何通信](http://www.cnblogs.com/bigdataZJ/p/AngularJS1.html)\n\n"
  },
  {
    "path": "AngularJS相关/[20140917]Angular：如何编写一个指令.md",
    "content": "---\ntitle: Angular：如何编写一个指令\ndate: 2015/3/13 11:24:39\n---\n\n## Angular是什么？\nAngularJS是一个用JavaScript编写的客户端MVC框架，它运行于Web浏览器，能够极大的帮助我们（开发者）编写模块化，单页面，Ajax风格的Web Applications。\n\nPS：**AngularJS适合开发CRUD的SPA**\n\n## Angular Directive是什么？\nAngular Directive是构建在DOM元素（属性、标签名、注释和CSS类）上的标记，告诉AngularJS的HTML编译器($compile) 附加指定的行为到元素或者甚至变换这个元素和它的子集。\n\nPS：**通过扩展HTML标签的方式提供可复用的web组件**\n\nPS2：**指令的作用：提供语义化标签**\n\n## 完整的Directive参数\n\tvar directiveModule=angular.module('Newkit.negHotkeys');\n\tdirectiveModule.directive('negHotkeys',function(injectables){\n\t\tvar directiveDefineObject={\n\t\t\trestrict:(string), \n\t\t\tpriority:(number),\n\t\t\ttemplate:(string),\n\t\t\ttemplateUrl:(string),\n\t\t\treplace:(bool),\n\t\t\ttransclude:(bool),\n\t\t\tscope:(bool or object),\n\t\t\tcontroller:(function),\n\t\t\trequire:(string),\n\t\t\tlink:(function)\n\t\t\tcompile:(function)\n\t\t};\n\t\treturn directiveDefineObject;\n\t});\n##### 参数说明\n- restrict：(string)指令的使用方式，可选值：元素[E]、属性[A]、样式类[C]、注释[M]，并且可以采用组合的方式使用，示例：'AE'\n- priority：(number)优先级，描述了多个指令时，指令的执行顺序。数字越大，优先级越高，默认值0。\n- template：(string)文本模板\n- templateUrl：(string)模板文件地址，如果设置了该属性，那么将会忽略template的配置。\n- replace：(bool)指示是否替换元素，如果设置为true,则替换，否则（设置为false或不设置）追加到元素内部\n- transclude：(bool)是否将指令的子节点移动到一个新模板内部，如果在模板中指定了ng-transclude，那么会将元素原本的内容移动到新的模板内部，具体看示例二\n- scope：(bool or object)设置作用域，如果设置为false[默认值]，则使用现有的作用域；如果设置为true，则创建一个新的作用域。设置为object时，设定作用域绑定策略\n- controller：创建一个控制器，它会暴露一个API，实现在多个指令之间进行通信\n- require：设置依赖的指令。不设置，则无依赖，示例：'?\\^testDirective'，其中，?表示该指令可选，^表示需要遍历DOM树查找指令\n- link：链接函数，function(scope,iElement,iAttrs){}，其中的i表示实例，所以在link中接收的是实例元素和实例元素属性\n- compile：编译函数，function(tElement,tAttrs,transclude){}，其中t表示模板，所以在compile中使用的是模板元素。在编译过程中，可以返回preLink(链接前)，postLink(链接后)函数，compile函数只会调用一次，而link函数的调用次数等于things中的元素个数，所以多余共同的东西，那么最好放在compile函数中实现（出于效率考虑） **注：设置了compile属性之后，指令将忽略link属性，同时compile函数的返回值将作为link函数使用**\n\n## Angular Directive 示例\n### 示例一(简单指令)\n\n```javascript\n<!--demo1指令定义-->\nangular.module('app').directive('demo1',function(){\n  return {\n    restrict:'AE',/*标签或者属性*/\n    template:'<div>Hello</div>',\n    replace:true\n  }\n});\n\n<!--使用-->\n<html ng-app=\"app\">\n  <head>...</head>\n  <body>\t\n    <demo1></demo1>\n    <div data-demo1></div>\n  </body>\n</html>\n<!--结果(指令将满足条件的元素替换为了新的内容)-->\n<body>\n    <div>Hello World!</div>\n    <div demo1=\"\">Hello World!</div>\n</body>\n```\n\n\n### 操作步骤分析\n1. 定义一个模块app，并创建了一个指令demo1。\n2. 设定该指令可采用元素的标签和属性申明，并设置了一个文本模板，同时设置了replace=true。\n3. 在html中，采用标签如*&lt;demo1></demo1&gt;*和属性*&lt;div demo1></div&gt;*来实现调用\n\n\n### 示例二（变换）\n\n```javascript\n<!--demo2指令定义-->\nangular.module('app.directive.demo2',[]).directive('demo2',function(){\n    return {\n        restrict:'E',\n        template:'<div>This is Demo2<div ng-transclude></div></div>',\n        transclude:true\n    }\n});\n\n<!--使用-->\n<demo2>\n    <span>原始的内容，</span><br/>\n    <span>还会在这里。</span>\n</demo2>\n<demo2></demo2>\n\n<!--页面生成的HTML-->\n  <demo2>\n  <div>This is Demo2\n    <div ng-transclude=\"\">\n          <span class=\"ng-scope\">原始的内容，</span><br class=\"ng-scope\">\n          <span class=\"ng-scope\">还会在这里。</span>\n      </div>\n  </div>\n</demo2>\n<demo2>\n  <div>This is Demo2\n    <div ng-transclude=\"\"></div>\n  </div>\n</demo2>\n```\n\n#### 分析\n1. 通过在指令中设置transclude=true，同时在template中包含*&lt;div ng-transclude>*，实现了将元素内部元素移动到了ng-transclude元素内部，并创建了新的作用域\n\n### 示例三（link与compile）\n```javascript\n\t/*指令*/\n\tangular.module('app.directive.demo3',[]).directive('demo3Link',function(){\n    return {\n        restrict:'E',\n        template:'<div>This is Demo3Link</div>',\n        link:function(scope,iElement,iAttrs){\n            iElement.html('<div>good link</div>');\n        }\n    }\n\t}).directive('demo3Compile',function(){\n        return {\n            restrict:'E',\n            template:'<div>This is Demo3Compile</div>',\n            compile:function(tElement,tAttrs,transclude){\n                tElement.html('<div>test demo3 compile</div>');\n                return function(scope,iElement,iAttrs){\n                    //iElement.html('<div>good compile</div>');\n                };\n            }\n        }\n    });\n\n\t/*使用*/\n\t<demo3-link></demo3-link>\n    <demo3-link></demo3-link>\n    <demo3-compile></demo3-compile>\n\n\t/*页面生成的HTML*/\n    <demo3-link><div>good link</div></demo3-link>\n    <demo3-link><div>good link</div></demo3-link>\n    <demo3-compile><div>test demo3 compile</div></demo3-compile>\n```\n\n#### 分析\ncompile用于在编译期处理模板内容，并能设置preLink和postLink函数，此时将不能设置link函数，代码如下：\n\n```\n\tcompile:function(tElement,tAttrs,transclude){\n\t    tElement.html('<div>test demo3 compile</div>');\n\t    return {\n\t        pre:function preLink(scope,iElement,iAttrs){\n\t            console.log('preLink');\n\t        },\n\t        post:function postLink(scope,iElement,iAttrs){\n\t            console.log('postLink');\n\t        }\n\t    };\n\t}\n```\nlink用于对替换后的元素进行操作，如果参数是iElement。\n\n\n### 示例四（简单加法计算器）\n```\n/*代码在这里*/\nangular.module('app.directive.demo4',[]).directive('demo4',function(){\n    return {\n        restrict:'E',\n        template:'<fieldset><legend>计算两个数之和</legend>' +\n            '<div><input type=\"text\" ng-model=\"num1\">+<input type=\"text\" ng-model=\"num2\">=<span>{{total}}</span></div>' +\n            '</fieldset>',\n        replace:true,\n        link:function(scope,iElement,iAttrs){\n            scope.num1=0;\n            scope.num2=0;\n            scope.total=0;\n            scope.$watch('num1+num2',function(to,from){\n                scope.total=+scope.num1+(+scope.num2)\n            })\n        }\n    }\n});\n\n/*HTML在这里*/\n<demo4></demo4>、\n\n/*效果请自行测试*/\n```\n\n#### 分析\n可以利用指令完成特定的功能了。\n\n\n### 示例五（negHotkeys指令代码）\n\n[代码在这里](http://trgit/backend_framework/web_platform/blob/master/src/framework/js/directives/custom/negHotKeys.coffee)\n\n## 总结\n1. 指令依附于模块\n2. 一个模块可以有多个指令，但是需要采用示例三的写法\n3. 指令可以语义化标签，实现html组件化\n4. 其他..."
  },
  {
    "path": "AngularJS相关/用AngularJS开发Web应用程序.md",
    "content": "---\ntitle: 用AngularJS开发Web应用程序\ndate: 2015/3/13 11:24:39\n---\n\n##章节一：Angular 禅道##\n###本章生词###\n\tserve = 提供\n\ttake a brief = 先简要的\n\tintroduction = 介绍\n\tconcept = 概念\n\ta lot of = 许多\n\tmaterial = 材料\n\tcover = 概括\n\tpainless = 无痛的\n\tplenty = 丰富、大量\n\tunique = 独特的\n\tdoubt = 疑问\n\tshape = 塑造\n\texplain = 解释\n\texpect = 预计\n\tget familiar with = 熟悉\n\tbecome aware = 察觉\n\tsophisticated = 复杂\n\tdependency injection = 依赖注入nuance\n\tnuance = 细微之处\n\tgeneral = 一般\n\tpurpose = 目的\n\tshines = 耀眼\n\trecent = 最近\n\taddition = 此外\n\tmostly = 主要的\n\tdue = 由于\n\tinnovative = 创新\n\tyet = 但\n\tattract = 吸引\n\tease = 缓解\n\tsolid = 扎实\n\tengineering = 工程\n\tpractice = 实践\n\tindeed = 的确\n\trespects = 方面\n\texplicit = 明确的\n\tcapable = 能\n\tfigure out = 弄清楚\n\tinteresting = 有趣的\n\tinterpret = 解析\n\tmistaken = 错误，谬\n\tseveral = 几个，数个\n\ttypically = 通常\n\ttreasure = 宝藏\n\ttestability = 可测试性\n\tbuilt-in support 内置支持\n\tthoroughly = 彻底的\n\trelatively = 比较的\n\tactor = 演员\n\tpersonal = 个人的\n\tturned out = 横空出世\n\n---\n这个章节介绍了AngularJS，包括框架和它背后的项目。首先，我们先简要的了解项目本身：谁become aware驱动了它，在哪儿可以找到源代码和文档，如何寻求帮助等等。\n\n这个章节的大部分是介绍AngularJS框架，它的核心概念和编码模式。包含有许多概括（总结）性的材料，使得学习进程快速无障碍，同时也有丰富的代码示例。\n\nAngularJS是一个独特的框架，毫无疑问的引领一个Web开发潮流。这也是为什么章节的最后部分解释了是什么让AngularJS如此特别，它和其它外部框架之间的差异和我们能在未来如何设想它。\n\n这个章节包含了以下几个主题：\n\n1. 如何用AngularJS书写一个简单的Hello World 程序。在做这个的过程中，你将了解到在哪儿找到框架源代码、文档以及社区。\n2. 熟悉AngularJS应用程序的基本构造块：Templates、Directives、Scopes和Controllers。\n3. 察觉AngularJS复杂的依赖注入系统以及它所有的细微之处。\n4. 理解AngularJS与其他框架或库（特别是jQuery）之间的差异，是什么使得它如此特别。\n\n###AngularJS简介###\nAngularJS是一个用JavaScript编写的客户端MVC框架，它运行于Web浏览器，能够极大的帮助我们（开发者）编写模块化，单页面，Ajax风格的Web Applications。它是一个平常的框架，不过如果用于编写CRUD类型的web app，那么它将非常耀眼。\n\n###熟悉框架###\nAngularJS是最近的客户端mvc框架的例外，但是它吸引了许多注意力，主要是由于它创新的模板系统，减轻了开发，同时有很扎实的工程实践。的确，它的模板系统独特于许多方面：\n\n1. 使用HTML作为模板语言\n2. 不要求明确的DOM刷新，AngularJS 能跟踪用户操作、浏览器事件和模型变化，来选择何时和那个模板将被刷新\n3. 它有非常有趣的和可扩展的组件子系统，它能教会浏览器如何解析新的HTML标签和属性\n\n模板子系统可能是AngularJS中最常见的部分，但是不要错误的认为AngularJS是单页Web程序所需要的包含数个工具和常用服务的完整框架包。\n\nAngularJS同样有一些隐藏的宝藏，依赖注入（DI=dependency injection）和可测试特性的强烈关注。DI的内置支持能够非常容易的访问从一个极小的、彻底的可测试服务创建的web app。\n\n###项目发展路线###\nAngularJS是客户端MVC框架中比较新的成员；它的1.0版本发布于2012年6月。实际上，这个框架作为谷歌雇员Misko Hevery的个人项目开始于2009年。最初的idea是如此的好，在写作本文的同时，这个项目已经被Google正式支持，并且有Google的完整团队全职维护这个框架。\n\nAngularJS是托管在[GitHub](https://github.com/angular/angular.js)上的，基于MIT协议的开源项目\n\n###社区###"
  },
  {
    "path": "AngularJS相关/详解angular之$q.md",
    "content": "---\ntitle: 详解angular之$q\ndate: 2017/02/21 14:47:10\n---\n\n## 0、什么的Promise\n\nPromise（承诺）是用于改善异步编程体验的一种编程模型，它提供了一些列的API的方法论，让你能更优雅的解决异步编程中出现的一些问题。\n\n## 1、Promise的核心竞争力\n\n在处理有依赖性的回调的时候，我们的代码是这样写的：\n\n\tstep1(function (value1) {\n\t    step2(value1, function(value2) {\n\t        step3(value2, function(value3) {\n\t            step4(value3, function(value4) {\n\t                // Do something with value4\n\t            });\n\t        });\n\t    });\n\t});\n\n这就是我们所谓的回调地狱。\n\n如果用Promise的方式来实现，是怎么样呢？\n\n\tstep1().then(step2).then(step3).then(step4)\n\n代码更简单逻辑也清晰，异步的回调嵌套变成了同步写法，孰优孰劣相信大家都一目了然。\n\n## 2、Angular服务$q\n\n在angular中，基于nodejs中流行的Q提供了一个简化版本的Q,对外的话提供一个service $q。\n\n以下列举出angular中的$q提供的API\n\n#### 1、Promise.then() 将回调变成链式调用，then可以接两个参数，successCallback, errorCallback,示例如下：\n\n\tvar deferred = $q.defer();\n\tvar promise = deferred.promise;\n\tpromise.then(successCallback, errorCallback);\n\n#### 2、Promise.catch 捕获Promise异常，Promise.catch(errorCallback)等价于Promise.then(null, errorCallback)\n\n#### 3、Promise.finally(callback, notifyCallback) promise结束后要做的事情和接收通知信息\n\n#### 4、Deferred.resolve(val) 通知promise请求处理完毕，并将处理结果传给回调函数（successCallback），示例如下：\n\n\tvar deferred = $q.defer();\n\tsetTimeout(function(){\n\t\tdeferred.resolve('abc'); //会将abc传递给successCallback\n\t}, 1000);\n\tvar promise = deferred.promise;\n\tpromise.then(successCallback, errorCallback);\n\n#### 5、Deferred.reject(msg) 通知promise请求出现异常，将异常信息传给回调函数（errorCallback），示例如下：\n\n\tvar deferred = $q.defer();\n\tsetTimeout(function(){\n\t\tdeferred.reject('abc'); //会将abc传递给errorCallback\n\t}, 1000);\n\tvar promise = deferred.promise;\n\tpromise.then(successCallback, errorCallback);\n\n#### 6、Deferred.notify(value) 内部执行有变化时，对外发起通知。将会在Promise.finally中捕获到\n\n\tvar deferred = $q.defer();\n\tsetTimeout(function(){\n\t\tdeferred.reject('abc'); //会将abc传递给errorCallback\n\t}, 1000);\n\tvar promise = deferred.promise;\n\tpromise.then(successCallback, errorCallback);\n\n#### 7、$q.when(val/fn) 将任意对象/函数包装成promise，返回包装好的promise。\n\n#### 8、$q.all(promises).then() 当所有的promise都成功解析后，流程才继续往下走。示例如下:\n\n\t$q.all($http.get('xxx'), $http.post('xxx',{}))\n\t.then(successCallback, errorCallback);\n\n## 3、$q的使用\n\n常规使用\n\n\t//定义开关变量\n\tvar canSuccess = false;\n\t//定义一个Promise\n\tvar buildPromise = ()=>{\n\t  var deferred = $q.defer();\n\t  setTimeout(()=>{\n\t    if(canSuccess){\n\t      deferred.resolve('promise执行成功！')\n\t    }else{\n\t      deferred.reject('promise执行失败！')\n\t    }\n\t  },5000);\n\t  return deferred.promise;\n\t};\n\t\n\t//使用它\n\tvar promise = buildPromise();\n\tpromise.then(()=>{\n\t  console.log('执行成功啦！');\n\t}, ()=>{\n\t  console.log('执行失败了！');\n\t})\n\n使用$q.all\n\n\tvar p1 = $http.get('xxxx');\n\tvar p2 = $http.get('xxxx2');\n\t$q.all(p1, p2).then(() =>{\n\t  console.log('两次请求都成功了！');\n\t});\n\n## 4、$q源码分解\n\n\t//Deferred定义\n\tfunction Deferred() {\n\t  this.promise = new Promise();\n\t  //Necessary to support unbound execution :/\n\t  this.resolve = simpleBind(this, this.resolve);\n\t  this.reject = simpleBind(this, this.reject);\n\t  this.notify = simpleBind(this, this.notify);\n\t}\n\n\t//函数柯里化\n\tfunction simpleBind(context, fn) {\n\t  return function(value) {\n\t    fn.call(context, value);\n\t  };\n\t}\n\n通过这种方式，就能将resolve，reject和promise关联起来了。既然我们最终要返回promise，那我们来看已看Promise的实现：\n\n\tfunction Promise() {\n\t  this.$$state = { status: 0 };\n\t}\n\t\n\textend(Promise.prototype, {\n\t  then: function(onFulfilled, onRejected, progressBack) {\n\t    if (isUndefined(onFulfilled) && isUndefined(onRejected) && isUndefined(progressBack)) {\n\t      return this;\n\t    }\n\t    var result = new Deferred();\n\t\n\t    this.$$state.pending = this.$$state.pending || [];\n\t    this.$$state.pending.push([result, onFulfilled, onRejected, progressBack]);\n\t    if (this.$$state.status > 0) scheduleProcessQueue(this.$$state);\n\t\n\t    return result.promise;\n\t  },\n\t\n\t  \"catch\": function(callback) {\n\t    return this.then(null, callback);\n\t  },\n\t\n\t  \"finally\": function(callback, progressBack) {\n\t    return this.then(function(value) {\n\t      return handleCallback(value, true, callback);\n\t    }, function(error) {\n\t      return handleCallback(error, false, callback);\n\t    }, progressBack);\n\t  }\n\t});\n\n从这里很明显可以看出，catch就是一个语法糖，调用的还是then。finally也是一个语法糖，就是不关成功，还是失败，都会调用callback。那这个时候，我们主要关注的方法就放到then这个方法的实现上。\n\n为了实现链式调用，在then方法内部，又实例化了Deferred对象，并返回Defferrd.promise。\n\n接下来就来看处理过程：\n\n\tthis.$$state.pending = this.$$state.pending || [];\n\tthis.$$state.pending.push([result, onFulfilled, onRejected, progressBack]);\n\tif (this.$$state.status > 0) scheduleProcessQueue(this.$$state);\n\n\textend(Deferred.prototype, {\n\t  resolve: function(val) {\n\t    if (this.promise.$$state.status) return;\n\t    if (val === this.promise) {\n\t      this.$$reject($qMinErr(\n\t        'qcycle',\n\t        \"Expected promise to be resolved with value other than itself '{0}'\",\n\t        val));\n\t    } else {\n\t      this.$$resolve(val);\n\t    }\n\t\n\t  },\n\t\n\t  $$resolve: function(val) {\n\t    var then, fns;\n\t\n\t    fns = callOnce(this, this.$$resolve, this.$$reject);\n\t    try {\n\t      if ((isObject(val) || isFunction(val))) then = val && val.then;\n\t      if (isFunction(then)) {\n\t        this.promise.$$state.status = -1;\n\t        then.call(val, fns[0], fns[1], this.notify);\n\t      } else {\n\t        this.promise.$$state.value = val;\n\t        this.promise.$$state.status = 1;\n\t        scheduleProcessQueue(this.promise.$$state);\n\t      }\n\t    } catch (e) {\n\t      fns[1](e);\n\t      exceptionHandler(e);\n\t    }\n\t  },\n\t\n\t  reject: function(reason) {\n\t    if (this.promise.$$state.status) return;\n\t    this.$$reject(reason);\n\t  },\n\t\n\t  $$reject: function(reason) {\n\t    this.promise.$$state.value = reason;\n\t    this.promise.$$state.status = 2;\n\t    scheduleProcessQueue(this.promise.$$state);\n\t  },\n\t\n\t  notify: function(progress) {\n\t    var callbacks = this.promise.$$state.pending;\n\t\n\t    if ((this.promise.$$state.status <= 0) && callbacks && callbacks.length) {\n\t      nextTick(function() {\n\t        var callback, result;\n\t        for (var i = 0, ii = callbacks.length; i < ii; i++) {\n\t          result = callbacks[i][0];\n\t          callback = callbacks[i][3];\n\t          try {\n\t            result.notify(isFunction(callback) ? callback(progress) : progress);\n\t          } catch (e) {\n\t            exceptionHandler(e);\n\t          }\n\t        }\n\t      });\n\t    }\n\t  }\n\t});\n\n在调用then的时候，就将锅中回调写到$$state的pending数组中，让defferred.resolve的时候就会调用Deferred的内部方法，调用我们传递的回调函数。\n\n**源码分解实在是说不明白，后期再发一篇如何实现一个简易的Promise，希望能更简洁易懂**\n\n## 5、 了解更多\n\n[JavaScript Promise迷你书（中文版）](http://liubin.github.io/promises-book/)\n\n[Angular $q api](https://docs.angularjs.org/api/ng/service/$q)"
  },
  {
    "path": "Angular系列/01_Angular2初体验.md",
    "content": "---\ntitle: 01_Angular2初体验\ndate: 2017/02/21 14:47:10\n---\n\n## 0、关于Angular2\n\nAngualr2是前端最流行的MV*框架AngularJS的革命性更新版本，官网：[https://angular.io/](https://angular.io/)，号称一个框架统一移动版和桌面。\n\n## 1、背景\n\n将AngularJS升级为Angular2，是大势所趋。在之前，我们就必须要对Angular2有足够的了解。所以这一系列文章，希望从各个点将angular2分而破之。\n\n另外，由于Angular2当前处于Beta阶段，所以代码的时效性不高。所以每篇都会注明相关版本。\n\nAngular2推荐的开发语言是TypeScript [http://www.typescriptlang.org/](http://www.typescriptlang.org/)，所以我们这一系列文章也使用TypeScript开发（实际是使用JavaScript，半天没弄成功，丧气ing...）。\n\n不要害怕TypeScript，因为TypeScript是ES6的超集，我们完全可以使用ES6的方式来编写TypeScript代码。对我们来说，仅仅是文件名后缀变化了。\n\n## 2、Angular2 Hello-World\n\nAngular2并不仅仅只有一个JS文件，要想成功运行Angular2，需要包含如下内容：\n\n1. systemjs --模块加载器\n2. Rxjs   --对Js的扩展，至今不知道它是做什么的\n3. angular2\n\n如果要支持IE，那么还需要\n\n1. es6-shim\n2. systemjs 中的system-polyfills.js文件\n3. angular2中的shims_for_IE.js文件\n\n接着就直接创建项目吧，结构如下：\n\n```\n<root folder>\n  components/\n    hello_world.html\n    hello_world.ts\n  bootstrap.ts\n  index.html\n  package.json\n```\n\n首先第一步，我们要通过npm安装我们的依赖项：\n``npm install angular2 rxjs systemjs es6-shim typescript --save``\n\n接着，实现我们的``index.html``内容：\n\n```html\n<!DOCTYPE html>\n<html lang=\"en\">\n\n<head>\n  <meta charset=\"UTF-8\">\n  <title>Angular2 Hello World</title>\n</head>\n\n<body>\n  \n  <!-- Angular2组件标记 -->\n  <hello_world></hello_world>\n  \n  <!-- IE required polyfills -->\n  <script src=\"node_modules/es6-shim/es6-shim.js\"></script>\n  <script src=\"node_modules/systemjs/dist/system-polyfills.js\"></script>\n  <script src=\"node_modules/angular2/es6/dev/src/testing/shims_for_IE.js\"></script>\n\n  <!-- Compile TypeScript -->\n  <script src=\"node_modules/typescript/lib/typescript.js\"></script>\n\n  <!-- Angular2 required -->\n  <script src=\"node_modules/systemjs/dist/system.js\"></script>\n  <script src=\"node_modules/angular2/bundles/angular2-polyfills.js\"></script>\n  <script src=\"node_modules/rxjs/bundles/Rx.js\"></script>\n  <script src=\"node_modules/angular2/bundles/angular2.js\"></script>\n\n  <!-- startup app -->\n  <script>\n    System.config({\n      transpiler: 'typescript',\n      typescriptOptions: { emitDecoratorMetadata: true },\n      packages: {'components': {defaultExtension: 'ts'}} //配置components目录下的请求，默认格式为ts\n    });\n    //注意：此处import的时候，必须要指明后缀，因为我们是把bootstrap.ts放在index平级的，在System的config中没有配置默认扩展名\n    System.import('bootstrap.ts').then(null, console.error.bind(console));\n  </script>\n</body>\n\n</html>\n```\n\n然后是我们的``hello_world.html``和``hello_world.ts``，内容如下：\n\n```html\n<h1>Hello Angular2</h1>\n\nMy name is: <input type=\"text\" [(ngModel)]=\"username\">\n<br>\nAngular2: <span *ngIf=\"username\">Hello, {{username}}</span>\n```\n\n```javascript\nimport {Component} from 'angular2/core';\n\n@Component({\n  selector: 'hello_world', //此处指明了组件的标记，我们就可以使用<hello_world></hello_world>来使用这个组件了。\n  templateUrl: 'components/hello_world.html'\n})\n//export的意思是导出这个组件，在使用的地方，就可以使用import {xx} from 'xxx'来获取到了。\nexport class HelloWorldComponent{\n  constructor(){\n    \n  }\n}\n```\n\n最后，是我们的``bootstrap.ts``入口JS：\n\n```javascript\nimport {bootstrap} from 'angular2/platform/browser';\n\nimport {HelloWorldComponent} from 'components/hello_world';\nbootstrap(HelloWorldComponent);\n```\n\n通过``anywhere``启动静态服务器，就可以看到我们的页面了。\n\n**But，理想很丰满，现实很骨感，为嘛不兼容IE11？？？**\n\n错误提示如下：\n\n```\n\"'Symbol' is undefined\"\n```\n\n**坑你没商量*！最终发现是Rx的版本必须用angular2提供的那个版本**\n\n地址是：[https://code.angularjs.org/2.0.0-beta.12/Rx.js](https://code.angularjs.org/2.0.0-beta.12/Rx.js)\n\n所以把Rx.js文件替换下，就可以在IE11中跑起来了。\n\n**另外，经测试，Angular2可兼容IE9及以上版本。**\n\n## 3、结尾\n\n[Demo源码](https://github.com/hstarorg/HstarDemoProject/tree/master/angular2_demo/04)"
  },
  {
    "path": "Angular系列/02_Angular2组件生命周期.md",
    "content": "---\ntitle: 02_Angular2组件生命周期\ndate: 2017/02/21 14:47:10\n---\n\n## 0、Angular2 组件\n\nAngular1并不是围绕组件的概念来实现的。所以，我们需要controller、$scope，同时也需要封装自定义指令。\n\n在Angular2中，把之前的这些东西都丢弃了，使用了一种更面向对象的组件模型。\n\n一个组件控制着我们称之为View的显示部分。组件同时也是自描述的。\n\n**在Angular2中，指令也是存在的，组件只是指令的一种。**\n\n## 1、定义一个组件\n\n最基本的组件只需要提供一个selector和template就足够了。代码如下：\n\n```javascript\nimport {Component} from 'angular2/core';\n\n@Component({\n  selector: 'basic-info',\n  template: '<h1>Basic Info</h1>'\n})\n\nexport class AboutComponent{\n  constructor() {\n\t}\n}\n``` \n\n要实现输入和输出呢？\n\n```javascript\nimport {Component, Input, Output, EventEmitter} from 'angular2/core';\n\n@Component({\n  selector: 'basic-info',\n  template: `\n  <h1>Basic Info, {{abc}}</h1>\n  <input type=\"text\" [(ngModel)]=\"abc\">\n  `\n})\n\nexport class BasicInfo{\n  @Input('test') set value(val){\n    this.abc = val;\n    this.callback.next([val]);\n  }\n  @Output('callback') callback = new EventEmitter();\n  constructor(){\n    this.abc = 'aaaa';\n  }\n}\n```\n\n如何使用？\n\n```html\n<input type=\"text\" [(ngModel)] = \"value\">\n<basic-info [test]=\"value\" (callback)=\"innerCallback($event)\"></basic-info>\n```\n\n## 2、组件生命周期\n\nAngular2会管理组件的整个生命周期，包括组件的创建、渲染、子组件的创建和渲染、数据绑定属性变化时的校验、从DOM移除之前的销毁等等。\n\n那如果我们想在某个状态时，进行一些操作应该怎么办呢？Angular2提供了组件生命周期的钩子，供我们在这些时间点添加自定义的操作。\n\n在``angular2/core``中提供了多个Lifecycle Hook接口，我们可以实现一个或多个接口，来设置自定义操作。每一个接口，都会有一个钩子方法，钩子方法的名称是接口的名称加上前缀ng。如OnInit的钩子方法如下：\n\n```javascript\nimport {Component, Input, Output, EventEmitter} from 'angular2/core';\n\n@Component({\n  selector: 'basic-info',\n  template: `\n  <h1>Basic Info, {{abc}}</h1>\n  <input type=\"text\" [(ngModel)]=\"abc\">\n  `\n})\n\nexport class BasicInfo{\n  @Input('test') set value(val){\n    this.abc = val;\n    this.callback.next([val]);\n  }\n  @Output('callback') callback = new EventEmitter();\n  constructor(){\n    this.abc = 'aaaa';\n  }\n  // 组件Init时创建\n  ngOnInit(){\n    console.log('basic info init.');\n  }\n}\n```\n\n**生命周期钩子（组件和指令都有的）**\n\n1. ngOnInit //组件初始化，在Angular初始化数据绑定输入属性之后\n2. ngOnChanges // \n3. ngDoCheck\n4. ngOnDestroy\n\n**生命周期钩子（组件特有的）**\n\n1. ngAfterContentInit // Angular将外部内容放入视图后\n2. ngAfterContentChecked // 在Angular检测放到视图内的外部内容的绑定后\n3. ngAfterViewInit // Angular创建视图之后\n4. ngAfterViewChecked //Angular检测了组件视图的绑定之后\n\n**执行顺序**\n\n1. ngOnChanges //绑定属性变化时\n2. ngOnInit //在第一次ngOnChanges之后，初始化时\n3. ngDoCheck //每次Angular变化检测时\n4. ngAfterContentInit //组件内容初始化之后\n5. ngAfterContentChecked //组件内容变化后\n6. ngAfterViewInit //初始化组件视图和子视图之后\n7. ngAfterViewChecked //在数组视图和子视图检查之后\n8. ngOnDestroy\n\n我们将组件设定上钩子函数如下：\n\n```javascript\nimport {Component, Input, Output, EventEmitter} from 'angular2/core';\n\n@Component({\n  selector: 'basic-info',\n  template: `\n  <h1>Basic Info, {{abc}}</h1>\n  <input type=\"text\" [(ngModel)]=\"abc\">\n  `\n})\n\nexport class BasicInfo{\n  @Input('test') set value(val){\n    this.abc = val;\n    this.callback.next([val]);\n  }\n  @Output('callback') callback = new EventEmitter();\n  constructor(){\n    this.abc = 'aaaa';\n  }\n  ngOnInit(){\n    console.log('basic info init.');\n  }\n  ngDoCheck(){\n    console.log('basic info do check.');\n  }\n  ngOnChanges(){\n    console.log('basic info changes.');\n  }\n  ngOnDestroy(){\n    console.log('basic info destroy');\n  }\n  ngAfterContentInit(){\n    console.log('basic info after content init');\n  }\n  ngAfterContentChecked(){\n    console.log('basic info after content checked');\n  }\n  ngAfterViewInit(){\n    console.log('basic info after view init');\n  }\n  ngAfterViewChecked(){\n    console.log('basic info after view checked');\n  }\n}\n```\n\n控制台打印的结果是：\n\n```html\nbasic info changes.\ntest.component.js:23 basic info init.\ntest.component.js:26 basic info do check.\ntest.component.js:35 basic info after content init\ntest.component.js:38 basic info after content checked\ntest.component.js:41 basic info after view init\ntest.component.js:44 basic info after view checked\ntest.component.js:26 basic info do check.\ntest.component.js:38 basic info after content checked\ntest.component.js:44 basic info after view checked\ntest.component.js:26 basic info do check.\ntest.component.js:38 basic info after content checked\ntest.component.js:44 basic info after view checked\ntest.component.js:26 basic info do check.\ntest.component.js:38 basic info after content checked\ntest.component.js:44 basic info after view checked\n```"
  },
  {
    "path": "Angular系列/03_Angular2的那些Decorator.md",
    "content": "---\ntitle: 03_Angular2的那些Decorator\ndate: 2017/02/21 14:47:10\n---\n\n## 0、Decorator\n\n``Decorator`` 是ECMAScript中建议的标准，使得我们可以在设计时对类和属性进行注解和修改。\n\n## 1、Angular2的Decorator\n\n在Angular2的早期版本（使用AtScript）中，我们是使用Annotation（注解），它以一个声明的方式将元数据添加到代码中。\n\n在后来迁移到TypeScript的时候，我们可以使用 Decorator 和 Annotation 。作为使用者来说，使用 Decorator 和 Annotation 几乎是一样的，唯一的区别是我们没有去控制 Annotation 如何将元数据添加到我们的代码中，而 Decorator 是对 这些 Annotation 的最终实现。\n\n从长远看，我们更应该多关注 Decorator ，因为它才是真正的标准建议。\n\n## 2、Angular2的那些Decorator\n\n### 2.1、In angular2/core\n\n#### 2.1.1、Component\n\n``Component`` 用于声明可重用的UI构建模块（组件），每个 Angular component都要求有一个 ``@Component`` 注解，它指定了组件何时被实例化，哪些属性和 hostListeners 被绑定。\n\n当组件实现（implements）了一些生命周期钩子（lifecycle-hooks），那么将在特定的时间点访问这些钩子的回调函数。\n\n**如何使用**\n\n```javascript\n@Component({\n  selector: 'demo', // 配置选择器\n  inputs: [],\n  outputs: [],\n  properties: [],\n  events: [],\n  host: {},\n  providers: [], // 设定所依赖的Providers（ng1中的service，provider，factory）\n  exportAs: '',\n  moduleId: '',  //设定模块ID\n  viewProviders: [],\n  queries: {},\n  //changeDetection\n  templateUrl : '', // 指定模板文件URL，和template冲突\n  template: 'Hello {{name}}!', //指定模板内容，和templateUrl冲突\n  styleUrls: [], // 设定组件依赖的样式表文件\n  styles : [],  //设定组件依赖的样式\n  directives: [], //设定所依赖的Directives（ng1中的directives）\n  pipes: [] //设定所依赖的Pipes（ng1中的filter）\n  //encapsulation\n})\nexport class Demo {\n  private name: string = 'World';\n}\n```\n\n**注：从继承关系来看，Component extends Directive。**\n\n#### 2.1.2、Directive\n\n``Directive`` 允许你在DOM元素上附加行为。如果指令带有内嵌视图，那么就成为了组件。\n\n指令同样也有生命周期钩子。使用方式和 Component 雷同。\n\n指令允许多种注入方式来实例化：\n\n1、无注入 -- 该指令没有外部依赖\n\n```javascript\n// 空构造，无注入\n@Directive({ selector: '[my-directive]' })\nclass MyDirective {\n  constructor() {\n  }\n}\n```\n\n2、组件级别的注入 -- 该指令依赖一些外部服务\n\n```javascript\nimport {User} from 'xxx';\n\n@Directive({ selector: '[my-directive]' })\nclass MyDirective {\n  constructor(user: User) { //依赖外部服务\n  }\n}\n```\n\n3、注入当前元素的其它指令 --该指令依赖当前元素上的其他指令，搭配其他指令一起使用\n\n```javascript\nimport {User} from 'xxx';\n\n@Directive({ selector: '[my-directive]' })\nclass MyDirective {\n  constructor(depDirective: DepDirective) { //依赖当前元素上的其他指令\n  }\n}\n```\n\n```html\n<div my-directive dep-directive>\n  \n</div>\n```\n\n4、注入当前元素、父元素、更上层的父元素上的指令 --该指令依赖上层元素的指令\n\n```javascript\nimport {User} from 'xxx';\n\n@Directive({ selector: '[my-directive]' })\nclass MyDirective {\n  //要使用 @Host()\n  constructor(@Host() depDirective: DepDirective) { //可以依赖父辈元素上的指令\n  }\n}\n```\n\n```html\n<div dep-directive>\n  <div my-directive></div>\n</div>\n```\n\n5、注入直接子集集合元素的的指令 --该指令依赖直接子元素的指令\n\n```javascript\nimport {User} from 'xxx';\n\n@Directive({ selector: '[my-directive]' })\nclass MyDirective {\n  //使用 @Query<Type> ，依赖直接子元素上的指令\n  constructor(@Query(DepDirective) depDirective: QueryList<DepDirective>) {\n  }\n}\n```\n\n```html\n<div my-directive>\n  <p dep-directive></p>\n  <p dep-directive></p>\n</div>\n```\n\n6、注入后代集合元素的指令 --该指令依赖后代元素的指令\n\n```javascript\nimport {User} from 'xxx';\n\n@Directive({ selector: '[my-directive]' })\nclass MyDirective {\n  //使用 @Query<Type> ，依赖直接子元素上的指令\n  constructor(@Query(DepDirective, {descendants: true}) depDirective: QueryList<DepDirective>) {\n  }\n}\n```\n\n```html\n<div my-directive>\n  <div>\n    <p dep-directive></p>\n    <p dep-directive></p>\n  </div>\n</div>\n```\n\n7、可选注入 --该指令的依赖是可选的。\n\n```\n@Directive({ selector: '[my-directive]' })\nclass MyDirective {\n  // 使用 @Optional 标记，依赖是可选的。 \n  constructor(@Optional() depDirective:DepDirective) {\n  }\n}\n```\n\n**注：以上多种注入方式也适用于 Component 。**\n\n#### 2.1.3、Injectable\n\n``Injectable`` 允许使用注入。在编写组件/指令时，如果有注入，那么就需要将指令/组件标记为可注入的。\n\n#### 2.1.4、Pipe\n\n``Pipe`` 允许我们定义管道方法，实现ng1中filter类似的功能。\n\n如何编写一个Pipe？\n\n```javascript\n@Pipe({name: 'lowercase'})\nclass Lowercase {\n  transform(v: string, args: any[]) { return v.toLowerCase(); }\n}\n```\n\n### 2.2、In angular2/router\n\n#### 2.2.1、CanActivate\n\n``CanActivate`` 允许我们在使用路由时，检查组件的权限，来确定是否可以使用。\n\n```javascript\n@Component({selector: 'control-panel-cmp', template: `<div>Settings: ...</div>`})\n@CanActivate(checkIfWeHavePermission)\nclass ControlPanelCmp {\n}\n```\n\n#### 2.2.2、RouteConfig\n\n``RouteConfig`` 用于我们配置路由。\n\n使用如下：\n\n```\n@Component({\n  selector: 'dojo-app',\n  moduleId: module.id,\n  templateUrl: 'app.html',\n  styleUrls: ['app.css'],\n  directives: [ROUTER_DIRECTIVES, HeaderComponent]\n})\n\n@RouteConfig([\n  {path: '/', name: 'Home', component: HomeComponent},\n  {path: '/about', name: 'About', component: AboutComponent}\n])\n\nexport class AppComponent{\n  constructor() {\n\t}\n} \n```\n\n## 3、参考\n\n1. [https://angular.io/docs/ts/latest/api/index.html#!?apiType=Decorator](https://angular.io/docs/ts/latest/api/index.html#!?apiType=Decorator)\n2. [https://angular.io/docs/ts/latest/api/index.html#!?apiFilter=metadata](https://angular.io/docs/ts/latest/api/index.html#!?apiFilter=metadata)"
  },
  {
    "path": "Angular系列/04_Angular2指令简析.md",
    "content": "---\ntitle: 04_Angular2指令简析\ndate: 2017/02/21 14:47:10\n---\n\n## 0、Angular2指令\n\n在Angular1中，就已经有了指令的概念。Angular1中的指令用于实现可复用UI部件，也用于操作dom元素。\n\n那么在Angular2中的指令是一样的东西么？\n\nAngular2中有组件的概念，指令这个东西就变得更加纯粹。\n\nAngular2的指令有三种：\n\n- 组件 \n- 属性指令\n- 结构指令\n\n组件是有模板的指令，是指令的中一个另类，因为它使用@Component来装饰，而不是@Directive。\n\n属性指令用于改变现有元素的展现和行为，使用的时候它们看起来像是正常的HTML属性，所以称之为属性指令。如ngModel指令。\n\n结构指令通过添加、删除和替换DOM树中的元素来改变布局，由于可以更改DOM结构，所以称之为结构指令。如ngIf，ngSwitch。\n\n由于Angular2的API好不够稳定，书写该文时，采用的是Angular2 rc1（@angular rc.1）版本，其他版本请自行测试。\n\n## 1、属性指令\n\n接着，我们就一步步来实现一个属性指令 dynamicColor 。\n\n首先，我们需要创建一个ts文件，然后把指令的骨架搭建起来。\n\n```typescript\nimport {Directive} from '@angular/core';\n\n@Directive({\n  selector: '[dynamicColor]'\n})\n\nexport class DynamicColorDirective{\n  constructor(){\n    \n  }\n}\n```\n\n以上代码中，我们创建了一个dynamicColor指令。\n\n接下来，我们来实现具体的功能，可以设置元素的背景色和前景色，并能实现事件通知。\n\n要实现动态背景色和前景色，那我们需要额外附加两个属性bgColor和color。\n\n要想在指令中获取这两个属性值，那么我们可以通过@Input方式或者是inputs属性，代码如下：\n\n```typescript\nimport {Directive, Input} from '@angular/core';\n\n@Directive({\n  selector: '[dynamicColor]'\n})\n\nexport class DynamicColorDirective{\n  \n  @Input()\n  private bgColor: string;\n  \n  @Input()\n  private color: string;\n  \n  constructor(){\n    \n  }\n}\n```\n\n或者是：\n\n```typescript\nimport {Directive, Input} from '@angular/core';\n\n@Directive({\n  selector: '[dynamicColor]',\n  //注意，在之前的版本中，使用properties属性，而且，当前还可以使用。\n  inputs: [\n    'bgColor: bgColor', //字符串以冒号隔开，前者是DynamicColorDirective的属性，后者的html元素的属性\n    'color: color'\n  ]\n})\n\nexport class DynamicColorDirective{\n  \n  // @Input()\n  private bgColor: string;\n  \n  // @Input()\n  private color: string;\n  \n  constructor(){\n    \n  }\n}\n```\n\n那么html中又应该如何传递值给指令呢？\n\n```html\n<div class=\"test\" dynamicColor [bgColor]=\"testBgColor\" [color]=\"testColor\">\n  Hi!\n</div>\n```\n\n**注意：在html元素的属性上，我们可以有两种写法。一种是直接书写属性，此时会把属性值原样传递给指令。第二种是使用[属性]，此时属性值应该是表达式（可以使用变量，判断等语句），传递给指令的是表达式的结果。**\n\n我们又如何在后端查看这两个值呢？\n\n直接在constructor中console？明确的说是不行的，因为constructor的代码会先于绑定执行。\n\n这个时候，我们就需要借助指令的生命周期钩子。\n\n指令的生命周期钩子有如下几个：\n\n1. ngOnInit --初始化时\n2. ngOnChanges -- 属性绑定之时（会有一次inputs属性绑定先于初始化）\n3. ngDoCheck -- 执行属性检查时\n4. ngOnDestroy -- 指令释放时\n\n了解了生命周期钩子，我们就可以通过ngOnInit来查看绑定好的属性值了。\n\n```typescript\nimport {Directive, Input, OnInit} from '@angular/core';\n\n@Directive({\n  selector: '[dynamicColor]',\n  inputs: [\n    'bgColor: bgColor', //字符串以冒号隔开，前者是DynamicColorDirective的属性，后者的html元素的属性\n    'color: color'\n  ]\n})\n\nexport class DynamicColorDirective implements OnInit{\n  \n  // @Input()\n  private bgColor: string;\n  \n  // @Input()\n  private color: string;\n  \n  constructor(){\n    \n  }\n  ngOnInit(){\n    console.log('bgColor', this.bgColor);\n    console.log('color', this.color);\n  }\n}\n```\n\n接下来，我们需要设置元素的background color和color样式，那么我们必须要拿到这个而元素的引用, 并在初始化之后进行绑定。\n\n```typescript\nimport {Directive, Input, OnInit, ElementRef} from '@angular/core';\n\n@Directive({\n  selector: '[dynamicColor]',\n  inputs: [\n    'bgColor: bgColor', //字符串以冒号隔开，前者是DynamicColorDirective的属性，后者的html元素的属性\n    'color: color'\n  ]\n})\n\nexport class DynamicColorDirective implements OnInit{\n  \n  private nativeElement: any;\n  \n  // @Input()\n  private bgColor: string;\n  \n  // @Input()\n  private color: string;\n  \n  constructor(el: ElementRef){\n    this.nativeElement = el.nativeElement;\n  }\n  \n  private _setElementStyle(): void{\n    this.nativeElement.style.backgroundColor = this.bgColor;\n    this.nativeElement.style.color = this.color;\n  }\n  \n  ngOnInit(){\n    console.log('bgColor', this.bgColor);\n    console.log('color', this.color);\n    this._setElementStyle();\n  }\n}\n```\n\n当从元素上绑定的属性变化时，又应该从哪里获取到变更呢？这就需要借助生命周期里面的OnChanges函数，代码如下：\n\n```typescript\nimport {Directive, Input, ElementRef, OnInit, OnChanges} from '@angular/core';\n\n@Directive({\n  selector: '[dynamicColor]',\n  inputs: [\n    'bgColor: bgColor', //字符串以冒号隔开，前者是DynamicColorDirective的属性，后者的html元素的属性\n    'color: color'\n  ]\n})\n\nexport class DynamicColorDirective implements OnInit, OnChanges{\n  \n  private nativeElement: any;\n  \n  // @Input()\n  private bgColor: string;\n  \n  // @Input()\n  private color: string;\n  \n  constructor(el: ElementRef){\n    this.nativeElement = el.nativeElement;\n  }\n  \n  private _setElementStyle(): void{\n    this.nativeElement.style.backgroundColor = this.bgColor;\n    this.nativeElement.style.color = this.color;\n  }\n  \n  ngOnInit(){\n    console.log('bgColor', this.bgColor);\n    console.log('color', this.color);\n    this._setElementStyle();\n  }\n  \n  ngOnChanges(){\n    console.log('bgColor-change', this.bgColor);\n    console.log('color-change', this.color);\n    this._setElementStyle();\n  }\n}\n```\n\n由于每次变化都会触发OnChanges，那么为了提高性能，我们可以在这里加入一个节流函数。\n\n```typescript\nprivate _setElementStyle(): void {\n  clearTimeout(this.timeoutId);\n  this.timeoutId = setTimeout(() => {\n    this.nativeElement.style.backgroundColor = this.bgColor;\n    this.nativeElement.style.color = this.color;\n  }, 500);\n}\n```\n\n有了节流函数，我们就不太确定到底执行了几次更新操作了。这个时候，我们可以加入事件通知。这就涉及到指令的@Output了。\n\n```typescript\n@Output()\nprivate updated: EventEmitter<any> = new EventEmitter(); \n```\n\n也等同于：\n\n```typescript\noutputs: [\n  'updated: updated'\n]\nprivate updated: EventEmitter<any> = new EventEmitter(); \n```\n\n**注意：在之前的版本中，也可以用events属性来替代outputs，现在也还可以使用**\n\n要对外发出通知，只需要使用如下代码：\n\n```typescript\nthis.updated.emit('updated');\nthis.updated.next('updated2');\n```\n\nHTML标签使用时，代码如下：\n\n```html\n<div class=\"test\" dynamicColor [bgColor]=\"testBgColor\" [color]=\"testColor\" (updated)=\"notify($event)\">\n  Hi!\n</div>\n```\n\n至此，我们这个指令就已经完成了，所有代码如下：\n\n```typescript\n//dynamicColor.directive.ts\n\nimport {Directive, Input, Output, ElementRef, EventEmitter, OnInit, OnChanges} from '@angular/core';\n\n@Directive({\n  selector: '[dynamicColor]',\n  inputs: [\n    'bgColor: bgColor', //字符串以冒号隔开，前者是DynamicColorDirective的属性，后者的html元素的属性\n    'color: color'\n  ],\n  outputs: [\n    'updated: updated'\n  ]\n})\n\nexport class DynamicColorDirective implements OnInit, OnChanges {\n\n  private nativeElement: any;\n\n  private timeoutId: any;\n\n  // @Input()\n  private bgColor: string;\n\n  // @Input()\n  private color: string;\n  \n  // @Output()\n  private updated: EventEmitter<any> = new EventEmitter(); \n\n  constructor(el: ElementRef) {\n    this.nativeElement = el.nativeElement;\n  }\n\n  private _setElementStyle(): void {\n    clearTimeout(this.timeoutId); //先清除已有的timeout\n    //保证只执行最后一次。\n    this.timeoutId = setTimeout(() => {\n      this.nativeElement.style.backgroundColor = this.bgColor;\n      this.nativeElement.style.color = this.color;\n      this.updated.emit('updated');\n      this.updated.next('updated2');\n    }, 500);\n  }\n\n  ngOnInit() {\n    console.log('bgColor', this.bgColor);\n    console.log('color', this.color);\n    this._setElementStyle();\n  }\n\n  ngOnChanges() {\n    console.log('bgColor-change', this.bgColor);\n    console.log('color-change', this.color);\n    this._setElementStyle();\n  }\n}\n```\n```html\n//test.html\n\n<h1>Dynamic Color Directive</h1>\n<input type=\"text\" [(ngModel)]=\"testBgColor\">\n<div class=\"test\" dynamicColor [bgColor]=\"testBgColor\" [color]=\"testColor\" (updated)=\"notify($event)\">\n  Hi!\n</div>\n```\n\n```typescript\n//test.component.ts\n\nexport class TestComponent{\n  \n  private testBgColor: string = 'blue';\n  \n  private testColor: string = 'red';\n  \n  constructor(){\n    \n  }\n  \n  private test(data){\n    console.log('data', 'my', data);\n  }\n  \n  private notify(data){\n    console.log('notify = ', data);\n  }\n  \n}\n```\n*思考一下？我们还有没有更简单的方式实现以上的效果呢？*\n\n```typescript\nimport {Directive, EventEmitter} from '@angular/core';\n\n@Directive({\n  selector: '[dynamicColor]',\n  inputs: [\n    'bgColor: bgColor', //字符串以冒号隔开，前者是DynamicColorDirective的属性，后者的html元素的属性\n    'color: color'\n  ],\n  outputs: [\n    'updated: updated'\n  ],\n  host: {\n    '[style.backgroundColor]': 'bgColor',\n    '[style.color]': 'color'\n  }\n})\n\nexport class DynamicColorDirective {\n\n  private bgColor: string;\n\n  private color: string;\n  \n  private updated: EventEmitter<any> = new EventEmitter(); \n\n  constructor() {\n  }\n}\n```\n通过host直接在元素上添加绑定。\n\n## 2、结构指令\n\n结构指令帮助我们修改dom结构，我们就简单实现一个templateInclude指令。\n\n```typescript\nimport {Directive, Input, Output, ElementRef, EventEmitter, OnChanges} from '@angular/core';\nimport {Http} from '@angular/http';\n\n@Directive({\n  selector: '[templateInclude]'\n})\n\nexport class TemplateIncludeDirective implements OnChanges {\n\n  private nativeElement: any;\n\n  @Input('templateInclude')\n  private templateUrl: string;\n\n  @Output()\n  private loaded: EventEmitter<any> = new EventEmitter();\n\n  constructor(el: ElementRef, private http: Http) {\n    this.nativeElement = el.nativeElement;\n  }\n\n  private _setTemplate() {\n    this.http.get(this.templateUrl)\n      .subscribe(res => {\n        this.nativeElement.innerHTML = res.text();\n        this.loaded.next(`${this.templateUrl} loaded`);\n      });\n  }\n\n  ngOnChanges() {\n    console.log(this.templateUrl);\n    this._setTemplate();\n  }\n}\n```\n\n## 3、总结\n\n**1、指令的元数据有很多属性可以使用**\n\n```typescript\n\nclass DirectiveMetadata {\n  selector : string //指令使用的标记（选择器）\n  inputs : string[] //输入参数绑定\n  properties : string[] //属性绑定（过期，请使用inputs）\n  outputs : string[] //输出参数绑定\n  events : string[] //事件绑定（过期，请使用outputs）\n  host : {[key: string]: string} //宿主元素属性设置\n  providers : any[] //服务绑定\n  bindings : any[] //服务绑定（过期，请使用providers）\n  exportAs : string //导出名称\n  queries : {[key: string]: any} //用于指令依赖关系\n}\n```\n\n**2、在使用指令（不仅仅是指令）进行绑定的时候，[]表示输入属性，()表示输出属性和事件**\n\n**3、尽量使用统一的做法，用装饰器优于在属性上做绑定**\n\n**4、在编写指令（不仅限于指令）时，将class中内容按照特定顺序进行排列，推荐顺序如下（个人建议，仅供参考）：**\n\n1. 私有变量\n2. 共有变量\n3. @Input变量\n4. @Output变量\n5. 构造函数\n6. 私有方法（建议下划线开头）\n7. 公有方法\n8. 生命周期钩子方法\n"
  },
  {
    "path": "Angular系列/05_Angular2组件简析.md",
    "content": "---\ntitle: 05_Angular2组件简析\ndate: 2017/02/21 14:47:10\n---\n\n## 0、Angular2组件\n\n**注：由于Angular2的API好不够稳定，书写该文时，采用的是Angular2 rc1（@angular rc.1）版本，其他版本请自行测试。**\n\n在上篇中，我们已经讲到了指令，这篇呢，我们一起来看看Angular2组件是怎么一回事。\n\n首先，组件也是指令，组件是一种有模板（内嵌视图）的特殊指令。\n\n从元数据[指令源代码](https://github.com/angular/angular/blob/2.0.0-rc.1/modules/%40angular/core/src/metadata/directives.ts)中也可以看出组件与指令的关系：\n\n```typescript\nexport class ComponentMetadata extends DirectiveMetadata {\n  \n}\n```\n\n相比 ``Directive``, ``Component`` 新增了一些属性，如下：\n\n```typescript\n{\n  changeDetection : ChangeDetectionStrategy; 定义变化检测类型\n  viewProviders: any[]; 用于在组件中注入特定的class。一般是实体类\n  moduleId: string; 定义主键的ID\n  templateUrl: string; 如ng1，外部模板地址\n  template: string; 如ng1，内嵌模板内容\n  styleUrls: string[]; 外部样式表文件\n  styles: string[]; 内嵌样式\n  directives: Array<Type | any[]>; 使用到的指令\n  pipes: Array<Type | any[]>; 使用到的管道\n  encapsulation: ViewEncapsulation  封装视图的类型\n}\n```\n\n## 1、组件生命周期\n\n既然组件也是指令，那么指令所拥有的四大阶段组件也同样拥有。\n\n而且，由于组件带有视图，还多了几个和视图相关的生命周期阶段。如下：\n\n1. ngAfterContentInit 组件内容渲染到页面之后触发\n2. ngAfterContentChecked 检查组件内容绑定数据后触发\n3. ngAfterViewInit 创建组件视图之后触发\n4. ngAfterViewChecked 检查组件视图绑定数据后触发\n\n它们的执行顺序也和以上顺序一致。\n\n## 2、整一个组件试试？\n\n接下来，我们就简单实现一个组件 ``TodoList`` 来实验一下以上的知识点。\n\n首先，搭建好一个简单的架子，如下：\n\n```typescript\nimport {Component} from '@angular/core';\n\n@Component({\n  selector: 'todo-list'\n})\n\nexport class TodoListComponent{\n  constructor(){\n    \n  }\n}\n```\n\n使用组件装饰器 ``Component`` 来定义一个组件，注意其中 ``selector`` 属性和 ``Directive`` 中的写法不一样了。\n\n组件必须以标签的方式存在，所以 selector 属性值仅仅只需要写标签名就可以了，不再需要其他特别的符号了。 \n\n组件和指令最大的差别就在于模板，所以我们接下来添加上模板代码：\n\n```typescript\nimport {Component} from '@angular/core';\n\n@Component({\n  selector: 'todo-list',\n  template: `\n<div class=\"todo-list\">\n  <h1>Todo List</li>\n  <ul>\n    <li></li>\n  </ul>\n</div> \n  `\n})\n\nexport class TodoListComponent{\n  constructor(){\n    \n  }\n}\n```\n\n如上，一个简单的模板就搞好了。这里需要注意 ``templateUrl`` 和 ``template`` 是互斥的两个属性。一般来说只选择一个赋值，如果两者都存在，那么会采用 ``tempalte`` 的值。\n\n模板有了，我们就来点业务逻辑：\n\n先假设Todo有三个状态：\n\n```typescript\nenum TodoStatus {\n  Open,\n  Processing,\n  Closed\n}\n```\n\n在来定义Todo的实体类：\n\n```typescript\nclass Todo {\n\n  private name: string;\n\n  private description: string;\n\n  private status: TodoStatus;\n\n  constructor(name: string, status: TodoStatus, description?: string) {\n    this.name = name;\n    this.status = status;\n    this.description = description || '';\n  }\n}\n```\n\n接着来实现一个组件：\n\n```typescript\n@Component({\n  selector: 'todo-list',\n  template: require('./todo-list.component.html')\n})\nexport class TodoList {\n\n  private todos: Array<Todo>;\n\n  private todo: { name: string, desc: string } = { name: '', desc: '' };\n\n  constructor() {\n    this.todos = [];\n  }\n\n  addTodo() {\n    this.todos.push(new Todo(this.todo.name, TodoStatus.Open, this.todo.desc));\n  }\n}\n```\n\n这个时候，HTML页面内容如下：\n\n```html\n<div *ngIf=\"todos.length === 0\">No todos.</div>\n<ul class=\"todo-list\">\n  <li *ngFor=\"let todo of todos\">\n    {{todo.name}} - {{todo.description}}\n  </li>\n</ul>\n<hr>\n<div class=\"todo-edit\">\n  Name: <input type=\"text\" [(ngModel)]=\"todo.name\">\n  <br>\n  Description: <br>\n  <textarea name=\"\" id=\"\" cols=\"30\" rows=\"3\" [(ngModel)]=\"todo.desc\"></textarea>\n  <br>\n  <button (click)=\"addTodo()\">Add Todo</button>\n</div>\n```\n\n功能做好了，我们得给它来点样式美化。\n\n此时我们仅仅需要实现一点样式：\n\n```css\n.todo-list{\n  margin: 0;\n  padding: 0;\n}\n.todo-list li{\n  list-style: none;\n  border: 1px solid red;\n}\n```\n\n然后在组件装饰器中申明就可以了：\n\n```typescript\n@Component({\n  selector: 'todo-list',\n  template: require('./todo-list.component.html'),\n  styles : [require('./todo-list.component.css')]\n})\n```\n\n至此，一个简单的可以添加todo的todo-list就已经完成了。\n\n**注意：注入@Input，@Output之类的和Directive都是一样的，此处就不再演示了。**"
  },
  {
    "path": "Angular系列/06_Angular2管道（Pipe）简析.md",
    "content": "---\ntitle: 06_Angular2管道（Pipe）简析\ndate: 2017/02/21 14:47:10\n---\n\n## 0、Angular2 Pipe\n\n**注：由于Angular2的API好不够稳定，书写该文时，采用的是Angular2 rc1（@angular rc.1）版本，其他版本请自行测试。**\n\n对于 ``Pipe``，其实我们并不陌生。在angular1中，它被称之为 ``filter``。\n\n``Pipe``用于对数据进行格式化处理，就好比管道，一个进一头出，中间过程就是管道的处理逻辑。\n\nAngular2中的 ``Pipe`` 本质上是包含特定方法的类。\n\n## 1、编写一个简单Pipe\n\n``Pipe`` 相对于指令和组件来说，非常简单，我们仅仅需要编写一个类：\n\n```typescript\nclass EmptyToZero{\n  \n}\n```\n\n实现特定的方法：\n\n```typescript\nexprt class EmptyToZero{\n  transform(v: any, args: any[]){\n    if(v === undefined || v === null || v === ''){\n      return 0;\n    }\n    return v;\n  }\n}\n```\n\n使用 ``Pipe`` 装饰，请设定名称:\n\n```typescript\nimport {Pipe} from '@angular/core';\n\n@Pipe({ name: 'empty2zero' })\nexport class EmptyToZero {\n  transform(v: any, args: any[]) {\n    if (v === undefined || v === null || v === '') {\n      return 0;\n    }\n    return v;\n  }\n}\n```\n\n如何使用？\n\n```html\n<span>{{ value | empty2zero}}</span>\n```\n\n当然前提是要在Component中申明要使用的Pipe：\n\n```typescript\n@Component({\n  selector: 'todo-list',\n  template: require('./todo-list.component.html'),\n  styles : [require('./todo-list.component.css')],\n  pipes:[EmptyToZero]\n})\n```\n\n## 2、有状态的Pipe\n\n在使用 ``Pipe`` 装饰器的时候，我们可以提供两个参数：\n\n```typescript\n@Pipe({\n  name: 'empty2zero', //string类型，必填项，指定pipe的名称\n  pure: true //boolean类型，可选项，默认为true，设定为true时，表示无状态管道。无论是输入或者是什么参数的改变都会触发重新计算结果。\n})\n```\n\n有状态的 ``Pipe`` 可以收到一个Promise对象或者检测输入和自动订阅输入，最终返回一个可触发的值。\n\n要使用有状态的管道，必须将pure属性设置为false。\n\n比较典型的有状态异步管道如下：\n\n```typescript\nimport {Pipe} from 'angular2/core';\n@Pipe({\n    name: 'fetch',\n    pure: false\n})\nexport class FetchJsonPipe {\n    private fetchedValue:any;\n    private fetchPromise:Promise<any>;\n    transform(value:string, args:string[]):any {\n    if (!this.fetchPromise) {\n         this.fetchPromise = window.fetch(value)\n        .then((result:any) => result.json())\n        .then((json:any) => this.fetchedValue = json);\n    }\n    return this.fetchedValue;\n }\n}\n```\n\n\n"
  },
  {
    "path": "Angular系列/07_Angular2使用路由.md",
    "content": "---\ntitle: 07_Angular2使用路由\ndate: 2017/02/21 14:47:10\n---\n\n## 0、关于路由\n\n此处所说的路由是指URL路由（也许叫URL Rewrite）。其实是把网址（URL）映射到相关Controller、Component的这个功能。\n\nAngular2的路由其实也就是URL路由，在Angular2中，有两个模块提供了路由功能，``@angular/router-deprecated`` 和 ``@angular/router``。\n\n``@angular/router-deprecated`` 从名称也可以看出，它是一个过时的模块（beta版本中它的名字是 ``angular2/router``，在rc版本被更名）。但疑惑的是，它一直存在于 ``@angular`` 包中。在这里，我主要使用 ``@angular/router`` 来实现路由功能。\n\n## 1、使用Angular2路由\n\n建议在根组件中配置路由。要使用路由，必须先依赖 ``ROUTER_PROVIDERS``；如果要使用路由指令，必须先依赖 ``ROUTER_DIRECTIVES``。\n\n大概结构如下：\n\n```typescript\n //bootstrap.ts\nimport {provide} from '@angular/core';\nimport {bootstrap} from '@angular/platform-browser-dynamic';\nimport {LocationStrategy, HashLocationStrategy} from '@angular/common';\n\nimport {AppComponent} from './app/app.component';\n\n\nbootstrap(AppComponent, [\n  provide(LocationStrategy, { useClass: HashLocationStrategy })\n]);\n```\n\n```typescript\n//app.component.ts\nimport {Component, provide} from '@angular/core';\nimport {Routes, ROUTER_DIRECTIVES, ROUTER_PROVIDERS} from '@angular/router';\n\nimport {HomeComponent} from './../home/home.component';\nimport {AboutComponent} from './../about/about.component';\n\n@Component({\n  selector: 'demo-app',\n  template: `\n<h3>Angular2 Router Test</h3>\n<a [routerLink]=\"['/home']\">Home</a>\n<a [routerLink]=\"['/about']\">About</a> \n<router-outlet></router-outlet>\n  `,\n  directives: [ROUTER_DIRECTIVES],\n  providers: [ROUTER_PROVIDERS]\n})\n\n@Routes([\n  { path: '/home', component: HomeComponent},\n  { path: '/about', component: AboutComponent }\n])\n\nexport class AppComponent {\n  constructor() {\n    console.log('app init');\n  }\n}\n```\n\n通过 ``@Routes``，我们可以配置路由对应的组件。当然，要求这些组件必须已经存在。在配置路由节点的时候，我们仅仅需要提供 ``path`` 和 ``component`` 参数。\n\n在模板中使用 ``[routerLink]`` 可以配置连接，它的值是一个数组，第一个元素是要导向的url，第二个参数是路由参数。\n\n除了使用 ``[routerLink]`` 实现路由跳转，还可以使用特定服务来跳转，代码如下：\n\n```typescript\nthis.router.navigate(['/about']);\nthis.router.navigateByUrl('/about');\n```\n\n当URL比较复杂，如/home/test/index时，使用navigate方式如下：\n\n```typescript\nthis.router.navigate(['home', 'test' ,'index']);\n```\n\n## 2、路由参数\n\n有时候，我们需要给路由传递一些参数，这个时候就需要在配置路由的时候，指定参数。使用 ``[routerLink]`` 的方式如下:\n\n```html\n<a [routerLink]=\"['/about', {id: 1}]\">About</a> \n```\n\n那么如何获取这个参数呢？就需要在 ``AboutComponent`` 组件中通过 ``RouteSegment`` 来获取，代码如下：\n\n```typescript\nexport class AboutComponent {  \n  constructor(private routeSegment: RouteSegment) {\n    console.log('about init', 'params:', routeSegment.parameters);\n  }\n}\n```\n\n## 3、嵌套路由（子路由）\n\n一般来说，我们写一个中大型的应用程序，一个一级路由根本就不够使用。这个时候，就可以使用嵌套路由来把应用程序拆分成很多小的模块。\n\n在这种情况下，就需要我们的子组件也需要使用 ``@Routes`` 来申明自己的路由体系。\n\n```typescript\n//about.component.ts\n\nimport {Component} from '@angular/core';\nimport {ROUTER_DIRECTIVES, Routes, RouteSegment} from '@angular/router';\n\nimport {AboutUserComponent} from './about-user.component';\nimport {AboutMeComponent} from './about-me.component';\n\n@Component({\n  selector: 'demo-about',\n  template: `\n  <h1>About</h1>\n  <a [routerLink]=\"['/home']\">Go to Home</a>\n  <a [routerLink]=\"['./user', id]\">Go to About User</a>\n  <a [routerLink]=\"['./me']\">Go to About Me</a>\n  <router-outlet></router-outlet>\n  `,\n  directives: [ROUTER_DIRECTIVES]\n})\n\n@Routes([\n  { path: '/', component: AboutUserComponent },\n  { path: '/user/:id', component: AboutUserComponent },\n  { path: '/me', component: AboutMeComponent }\n])\n\nexport class AboutComponent {\n  \n  private id: number = 1;\n  \n  constructor(private routeSegment: RouteSegment) {\n    console.log('about init', 'params:', routeSegment.parameters);\n  }\n  \n}\n```\n\n在这段代码里，我们需要注意，``[routerLink]``的值，有些是 ``['/home']``, 也有 ``['./me]`` 这种，它们有什么区别呢？\n\n其实直接 '/home' 是指从根路径开始计算，也就是跳转到父路由，真实路径就是 /home。如果是使用的 './me' 这种形式，那么是相对路径，所以点击这个链接，跳转到的实际上是 /about/me 这个地址。\n\n## 4、生命周期钩子（拦截器）\n\n在路由中，我们可以通过实现 ``CanDeactivate`` 来控制路由是否可以被解除；还可以通过实现 ``OnActivate`` 来控制路由激活后的操作。\n\n当我们在 ``HomeComponent`` 中编写如下语句时：\n\n```typescript\nrouterCanDeactivate(curTree: RouteTree, futureTree: RouteTree) {\n  console.log('abc');\n  return new Promise((resolve, reject) => {\n    resolve(false);\n  });\n}\n```\n\n当进入Home页之后，我们就已经无法跳出了。\n\n当我们在 ``HomeComponent`` 中编写如下语句时：\n\n```typescript\nrouterOnActivate(currSegment: RouteSegment, prev: RouteSegment, currTree: RouteTree, prevTree: RouteTree){\n  console.log('succeed');\n}\n```\n\n每一次跳转到Home组件，我们都能在控制台看到输出 succeed 。 \n\n**很遗憾的是，暂时没有发现有全局的路由钩子，这也就意味着，我们没法在一个地方控制所有的路由是否允许被解除。**\n\n\n## 5、后记\n\n以上就是Angular2路由的简单使用了。相关Demo，请点击 [这里](https://github.com/hstarorg/HstarDemoProject/tree/master/angular2_demo)\n\n在Angular2中，还有一个路由是 ``@angular/router-deprecated``，它是之前的路由方式，由于已经被标注为过期，这里就不在说明，如果想了解一下，可以查看 [Demo](https://github.com/hstarorg/HstarDemoProject/tree/master/angular2_demo/angular2-router-deprecated-test)\n\n至于更复杂的动态路由，动态加载组件等等，未完待续...\n\n\n\n"
  },
  {
    "path": "Angular系列/08_Angular2动态加载组件.md",
    "content": "---\ntitle: 08_Angular2动态加载组件\ndate: 2017/02/21 14:47:10\n---\n\n## 0、为什么需要动态加载\n\n在一个比较大的应用程序中，我们不可能将所有的业务逻辑一次性加载出来，比较浪费资源，因为单个用户一般用不到所有的功能，这个时候，就需要部分组件动态加载了。\n\n## 1、Angular2如何动态加载组件\n\n在Angular2，有一个服务是 ``DynamicComponentLoader``，我们就可以通过它来进行动态加载组件。\n\n首先要使用它的话，我们必须要在providers中指定它。\n\n另外，它还有一些必须的依赖，是 ``injector``，当引入了这些元素之后，我们就可以实现一个加载组件的方法：\n\n```javascript\nclass AppComponent {\n  constructor(dynamicComponentLoader, viewContainerRef, injector) {\n    this.dynamicComponentLoader = dynamicComponentLoader;\n    this.viewContainerRef = viewContainerRef;\n    this.injector = injector;\n  }\n\n  //动态加载组件的方法，需要传入一个组件\n  loadComponent(component) {\n    this.viewContainerRef.clear();\n    this.dynamicComponentLoader.loadAsRoot(component, '#component-container', this.injector)\n      .then((componentRef) => {\n        //必须要这样来编写，否则会导致双向绑定表达式获取不到值。\n        componentRef.changeDetectorRef.detectChanges();\n        componentRef.onDestroy(() => {\n          componentRef.changeDetectorRef.detach();\n        });\n        return componentRef;\n      });\n  }\n}  \n```\n\n在以上代码中，核心就是 ``dynamicComponentLoader`` 的 ``loadAsRoot`` 方法，这个方法里面有个参数是 ``'#component-container'``，实际上指定将动态加载的组件放置的容器，\n\n```html\n<div id=\"content\">\n  <child id=\"component-container\"></child>\n</div>\n```\n\n通过这样的方式，我们就能够动态的把组件加载到页面上了。\n\n**注：``dynamicComponentLoader`` 还有一个加载方法是 ``loadNextToLocation(component, viewContainerRef)``，只需要提供要动态加载的组件和一个容器引用，就可以将组件加载到容器中了。**\n\n"
  },
  {
    "path": "Angular系列/09_Angular2使用ui-router-ng2.md",
    "content": "---\ntitle: 09_Angular2使用ui-router-ng2\ndate: 2017/02/21 14:47:10\n---\n\n## 0、导言\n\nAngular2的路由组件从beta到rc经历了多次变更，知道rc.4都没有完全稳定下来。其次它的功能也并不强大，全局钩子，动态加载，状态控制都不支持。\n\n如果是使用Angular1，那这个时候我们一般会选择 ``ui-router`` 这一个强大的基于状态的路由。\n\n其实，``ui-router`` 也提供了一个Angular2的版本，那就是 ``ui-router-ng2``。\n\n我们就来简单的尝试下它的使用，和利用它来实现动态加载一批组件（结合webpack）。\n\n## 1、引入 ``ui-router-ng2``\n\n要使用 ``ui-router-ng2``，我们必须要先通过 ``npm install ui-router-ng2`` 来安装该包。\n\n安装成功之后，我们需要实现一个 ``UIRouterConfig`` 的实例，代码如下：\n\n```typescript\nimport { Injectable } from '@angular/core';\nimport { UIRouter, UIRouterConfig } from 'ui-router-ng2';\n\nimport {AboutComponent} from './about.component';\n\n@Injectable()\nexport class AppRouterConfig implements UIRouterConfig {\n\n  constructor() {\n\n  }\n\n  configure(uiRouter: UIRouter) {\n    uiRouter.stateRegistry.register({\n      name: 'about',\n      component: AboutComponent,\n      url: '/about'\n    });\n  }\n}\n```\n\n一般做法，我们需要在 ``configure`` 方法中，注册路由状态对象。\n\n当实现了 ``UIRouterConfig`` 之后，我们就可以在应用启动时来使用它了，具体代码如下：\n\n```typescript\nimport { enableProdMode, provide, PLATFORM_DIRECTIVES } from '@angular/core';\nimport { APP_BASE_HREF, LocationStrategy, HashLocationStrategy, PlatformLocation } from '@angular/common';\nimport { BrowserPlatformLocation } from '@angular/platform-browser';\nimport { bootstrap } from '@angular/platform-browser-dynamic';\nimport { UIROUTER_PROVIDERS, UIRouterConfig, UIROUTER_DIRECTIVES } from 'ui-router-ng2';\n\nimport { RootComponent } from './../shell';\nimport { AppRouterConfig } from './routes';\n\nenableProdMode();\n\nbootstrap(RootComponent, [\n  provide(APP_BASE_HREF, { useValue: '/' }),\n  provide(LocationStrategy, { useClass: HashLocationStrategy }),\n  provide(PlatformLocation, { useClass: BrowserPlatformLocation }),\n  ...UIROUTER_PROVIDERS,\n  provide(UIRouterConfig, { useClass: AppRouterConfig }),\n  provide(PLATFORM_DIRECTIVES, { useValue: UIROUTER_DIRECTIVES, multi: true })\n])\n  .then(x => {\n    console.log('app started...');\n  })\n  .catch(error => console.log(error));\n```\n\n其中最关键是和路由相关的代码是：\n\n```typescript\n...UIROUTER_PROVIDERS, //依赖路由提供者\nprovide(UIRouterConfig, { useClass: AppRouterConfig }), //指定RouterConfig\nprovide(PLATFORM_DIRECTIVES, { useValue: UIROUTER_DIRECTIVES, multi: true }) //依赖路由指令\n```\n\n在经过这些步骤之后，我们的项目就已经可以使用 ``ui-router-ng2`` 来进行路由管理了。\n\n## 2、路由钩子\n\n``ui-router-ng2`` 提供了很强大的路由钩子函数，可以让我们很方便的对路由的各个阶段进行控制。\n\n使用方式如下：\n\n```typescript\nimport {Injectable, Inject} from '@angular/core';\nimport {UIRouter, UIRouterConfig} from 'ui-router-ng2';\n\nimport {AboutComponent} from './about.component';\n\n@Injectable()\nexport class AppRouterConfig implements UIRouterConfig {\n  constructor() {\n\n  }\n\n  configure(uiRouter: UIRouter) {\n    uiRouter.stateRegistry.register({\n      name: 'about',\n      component: AboutComponent,\n      url: '/about'\n    });\n\n    // 以下t均为Transition实例\n\n    uiRouter.transitionService.onBefore({}, t => {\n      console.log('onBefore', t); //路由跳转之前\n    });\n    uiRouter.transitionService.onStart({}, t => {\n      console.log('onStart', t); //路由跳转开始\n    });\n    uiRouter.transitionService.onExit({}, t => {\n      console.log('onExit', t); //路由跳出时\n    });\n    uiRouter.transitionService.onRetain({}, t => {\n      console.log('onRetain', t); //...\n    });\n\n    uiRouter.transitionService.onEnter({}, t => {\n      console.log('onEnter', t); //路由进入时\n    });\n\n    uiRouter.transitionService.onFinish({}, t => {\n      console.log('onFinish', t); //路由跳转完成\n    });\n    uiRouter.transitionService.onSuccess({}, t => {\n      console.log('onSuccess', t); //路由跳转成功\n    });\n    uiRouter.transitionService.onError({}, t => {\n      console.log('onError', t); //路由跳转出错\n    });\n  }\n}\n```\n\n通过以上的各个阶段，我们可以灵活控制跳转是否继续，每个钩子函数都接受 ``boolean`` 和 ``Promise<boolean>``来让我们确定是否跳转。\n\n## 3、自定义回调处理invalidState\n\n通过以上的方式，我们实现了状态路由，也实现了路由跳转的控制。接下来，我们另辟蹊径来实现动态加载。\n\n由于当我们请求不合法的state时，uiRouter都会执行到 ``invalidCallbacks`` 这个函数，我这里就通过它加载动态模块。\n\n实现代码如下：\n\n```typescript\nconfigure(uiRouter: UIRouter) {\n  ... //省略不相关代码\n  uiRouter.stateProvider.invalidCallbacks = [($from$, $to$) => {\n    return new Promise((resolve, reject) => {\n      let toStateName = $to$.name();\n      let moduleName = this._getModuleName(toStateName);\n      if (!moduleName) {\n        return;\n      }\n      this.moduleLoader.load(moduleName).then(_ => {\n        let state = uiRouter.stateService.target(toStateName);\n        resolve(state);\n      });\n    });\n  }];\n}\n```\n\n接着，再来看看moduleLoader的代码：\n\n```typescript\nimport { Injectable, Inject } from '@angular/core';\nimport {Http} from '@angular/http';\n\nimport {UIRouter} from 'ui-router-ng2';\n\n@Injectable()\nexport class ModuleLoader {\n\n  private uiRouter: UIRouter;\n  private loadedModules: Set<string>;\n\n  constructor(private http: Http) {\n    this.loadedModules = new Set<string>();\n  }\n\n  setRouter(uiRouter) {\n    this.uiRouter = uiRouter;\n  }\n\n  load(moduleName): Promise<any> {\n    if (this.loadedModules.has(moduleName)) {\n      return Promise.resolve();\n    }\n    return new Promise((resolve, reject) => {\n      this.http.get(`../dist/assets/js/${moduleName}.js`)\n        .toPromise()\n        .then(res => {\n          let mod = eval(res.text());\n          mod.MODULE_STATES.forEach(state => {\n            this.uiRouter.stateRegistry.register(state);\n          });\n          this.loadedModules.add(moduleName);\n          resolve();\n        }).catch(err => reject(err));\n    });\n  }\n}\n```\n\n通过请求指定的文件，然后利用eval执行出具体的state数组，通过register方法，动态注入到我们的ui-router中。\n\n**注意invalidCallbacks回调中的参数($from$, $to$)，必须使用这两个名字，通过分析源代码发现它是用参数名做了匹配的，如果换成其他名称，会提示注入错误。**\n\n**invalidCallbacks回调中的逻辑非常关键，演示了如何获取原始要跳转的state，也演示了如何恢复继续跳转。**\n\n## 4、更多待探索\n\n当前对 ``ui-router-ng2`` 的探索还不够多，另外它本身也还在beta版本，该处理方式应该还有优化空间。"
  },
  {
    "path": "Angular系列/Angular2踩坑大全.md",
    "content": "---\ntitle: Angular2踩坑大全\ndate: 2017/02/21 14:47:10\n---\n\n## Angular2的那些坑\n\n1、同样的代码，引用Rxjs库的版本不对，就会导致在IE11下无法运行。(特定版本下重现)\n正确的版本：[https://code.angularjs.org/2.0.0-beta.12/Rx.js](https://code.angularjs.org/2.0.0-beta.12/Rx.js)\n\n2、在使用TypeScript编写Angular2代码时，一定要将注意 ``tsconfig.json``，其中 ``experimentalDecorators`` 和 ``emitDecoratorMetadata`` 必须要设置为true，否则无法使用依赖注入。\n\n3、&lt;router-outlet> 不能放在带有 *ngIf的容器内，否则会出现初始化时无法找到。"
  },
  {
    "path": "Angular系列/利用Angular实现多团队模块化SPA开发框架.md",
    "content": "---\ntitle: 利用Angular实现多团队模块化SPA开发框架\ndate: 2017-11-23 11:11:11\n---\n\n# 0、前言\n\n当一个公司有多个开发团队时，我们可能会遇到这样一些问题：\n\n1. 技术选项杂乱，大家各玩各\n2. 业务重复度高，各种通用api，登录注销，权限管理都需要重复实现（甚至一个团队都需要重复实现）\n3. 业务壁垒，业务之间的互通变得比较麻烦\n4. 部署方式复杂，多个域名（或IP地址）访问，给用户造成较大的记忆难度\n5. 多套系统，风格难以统一\n6. 等等...\n\n当然，解决方式有不少。以下就来讲解下我们这边的一种解决方案。\n\n# 1、思路\n\n**Angualr**\n\n`Angular`（注：非AngularJS） 是流行的前端 `MVVM` 框架之一，配合 `TypeScript`，非常适合用来做后台管理系统。由于我们曾今的一套 `Angularjs` 开发框架，我们继续选择 `Angular` 来进行实现，并尽可能的兼容 `AngularJS` 的模块。\n\n**SPA**\n\n选 `SPA` 还是多页？多余 `Mvvm` 来说，多页并不是标配。而且多页开发中，我们势必会关注更多的内容，包括通用header，footer，而不仅仅是页面的核心内容。\n\n**模块化**\n\n为什么要模块化呢？当有多个团队开发时（或者项目较大时），我们希望各个团队开发出来的东西都是 `模块`（不仅限于JS模块），这样可以让我们独立发布、更新、删除模块，也能让我们的关注点集中在特定模块下，提高开发效率和可维护性。\n\n**平台化**\n\n我们需要有一个运行平台（Website站点），允许在里面运行指定的模块。这样就可以实现单一入口，也容易实现通用逻辑，模块共享机制等等。\n\n**兼容 AngularJS 模块**\n\n在考虑将框架切换到 `Angular` 时，我们无可避免的会遇到如何兼容当前已有模块的问题。大致可选的方案如下：\n\n1. 参考 `AngualrJS -> Angular` 官方升级指南，一步步将模块切换为 `Angular` 的实现。（工作量大，需要开发团队调整很多东西）\n2. `iframe嵌入`，会有一定的体验差异，但对开发团队来说，基本无缝升级，也不需要做什么改动。（无疑，我们选择了这套方案）\n\n**模块打包**\n\n我们需要将单个的模块打包为资源包，进行更新。这样才能做到模块独立发布，及时生效。\n\n**CSS冲突**\n\n在大型 `SPA` 中，CSS冲突是很大的一个问题。我们期望通过技术手段，能够根据当前使用的模块，加载和卸载CSS。\n\n**跨页面共享数据**\n\n由于涉及到iframe兼容旧有模块，我们无可避免，需要考虑跨窗口的页面共享。\n\n**公共模块**\n\n当一个团队的模块较多时，就会有一些公共的东西被抽取出来，这个过程，框架是无法知道的，所以这个时候，我们就需要考虑支持公共模块。（模块之间也有依赖关系）\n\n# 3、实现\n\n基于以上的一些思考，我们首先需要实现一个基础的平台网站，这个没什么难度，直接用 `Angular` 实现即可。有了这一套东西，我们的登录注销，基本的菜单权限管理，也就实现了。\n\n在这个基础之上，我们也能实现公共服务、公共组件了（封装一系列常用的玩意）。\n\n## 如何模块化？如何打包？\n\n**注意：此模块并非Angular本身的模块。** 我们通过约定，在 `modules/` 下的每一个目录都是一个业务模块。一个业务模块一般会包含，静态资源、CSS以及JS。根据这个思路，我们的打包策略就是：遍历 `modules/` 的所有目录，对每一个目录进行单独打包（webpack多entry打包+CSS抽取），另外使用 `gulp` 来处理相关的静态资源（在我看来，gulp才是构建工具，webpack是打包工具，所以混合使用，物尽其用）。\n\n一般来说，`webpack` 会把所有相关依赖打包在一起，A、B 模块都依赖了 `@angular/core` 识别会重复打包，而且框架中，也已经打包了 `@angular` 相关组件。这个时候，常规的打包配置就不太合适了。那该如何做呢？\n\n考虑到 `Angular` 也提供了 `CDN` 版本，所以我们将 `Angular` 的组件通过文件合并，作为全局全量访问，如 `ng.core`、`ng.common` 等。\n\n既然这样，那我们打包的时候，就可以利用 `webpack` 的 `externals` 功能，把相关依赖替换为全局变量。\n\n```js\nexternals: [{\n  'rxjs': 'Rx',\n  '@angular/common': 'ng.common',\n  '@angular/compiler': 'ng.compiler',\n  '@angular/core': 'ng.core',\n  '@angular/http': 'ng.http',\n  '@angular/platform-browser': 'ng.platformBrowser',\n  '@angular/platform-browser-dynamic': 'ng.platformBrowserDynamic',\n  '@angular/router': 'ng.router',\n  '@angular/forms': 'ng.forms',\n  '@angular/animations': 'ng.animations'\n}\n```\n\n这样处理之后，我们打包后的文件，也就不会有 `Angular` 框架代码了。\n\n**注：这个对引入资源的方式也有一定要求，就不能直接引入内层资源了。**\n\n## 如何动态加载模块\n\n打包完成之后，这个时候就要考虑平台如何加载这些模块了（发布过程就不说了，放到指定位置即可）。\n\n什么时候决定加载模块呢？其实是访问特定路由的时候，所以我们的顶级路由，会使用Promise方法来实现，如下：\n\n```js\nconst loadModule = (moduleName) => {\n  return () => {\n    return ModuleLoaderService.load(moduleName);\n  };\n};\n\nconst dynamicRoutes = [];\n\nmodules.forEach(item => {\n  dynamicRoutes.push({\n    path: item.path,\n    canActivate: [AuthGuard],\n    canActivateChild: [AuthGuard],\n    loadChildren: loadModule(item.module)\n  });\n});\nconst appRoutes: Routes = [{\n  path: 'login', component: LoginComponent\n}, {\n  path: 'logout', component: LogoutComponent\n}, {\n  path: '', component: LayoutComponent, canActivate: [AuthGuard],\n  children: [\n    { path: '', component: HomeComponent },\n    ...dynamicRoutes,\n    { path: '**', component: NotFoundComponent },\n  ]\n}];\n\n```\n\n我们把每个模块，按照 `umd` 的格式进行打包。然后再需要使用该模块的时候，使用动态构建 `script` 来运行脚本。\n\n```js\nload(moduleName, isDepModule = false): Promise<any> {\n  let module = window['xxx'][moduleName];\n  if (module) {\n    return Promise.resolve(module);\n  }\n  return new Promise((resolve, reject) => {\n    let path = `${root}${moduleName}/app.js?rnd=${Math.random()}`;\n    this._loadCss(moduleName);\n    this.http.get(path)\n      .toPromise()\n      .then(res => {\n        let code = res.text();\n        this._DomEval(code);\n        return window['xxx'][moduleName];\n      })\n      .then(mod => {\n        window['xxx'][moduleName] = mod;\n        let AppModule = mod.AppModule;\n        // route change will call useModuleStyles function.\n        // this.useModuleStyles(moduleName, isDepModule);\n        resolve(AppModule);\n      })\n      .catch(err => {\n        console.error('Load module failed: ', err);\n        resolve(EmptyModule);\n      });\n  });\n}\n\n// 取自jQuery\n_DomEval(code, doc?) {\n  doc = doc || document;\n  let script = doc.createElement('script');\n  script.text = code;\n  doc.head.appendChild(script).parentNode.removeChild(script);\n}\n```\n\nCSS的动态加载相对比较简单，代码如下：\n\n```js\n_loadCss(moduleName: string): void {\n  let cssPath = `${root}${moduleName}/app.css?rnd=${Math.random()}`;\n  let link = document.createElement('link');\n  link.setAttribute('rel', 'stylesheet');\n  link.setAttribute('href', cssPath);\n  link.setAttribute('class', `xxx-module-style ${moduleName}`);\n  document.querySelector('head').appendChild(link);\n}\n```\n\n为了能够在模块切换时卸载，还需要提供一个方法，供路由切换时使用：\n\n```js\nuseModuleStyles(moduleName: string): void {\n  let xxxModuleStyles = [].slice.apply(document.querySelectorAll('.xxx-module-style'));\n  let moduleDeps = this._getModuleAndDeps(moduleName);\n  moduleDeps.push(moduleName);\n  xxxModuleStyles.forEach(link => {\n    let disabled = true;\n    for (let i = moduleDeps.length - 1; i >= 0; i--) {\n      if (link.className.indexOf(moduleDeps[i]) >= 0) {\n        disabled = false;\n        moduleDeps.splice(i, 1);\n        break;\n      }\n    }\n    link.disabled = disabled;\n  });\n}\n```\n\n## 公共模块依赖\n\n为了处理模块依赖，我们可以借鉴 AMD规范 以及使用 `requirejs` 作为加载器。当前在我的实现里，是自定义了一套加载器，后期应该会切换到 AMD 规范上去。\n\n## 如何兼容 `AngularJS` 模块？\n\n为了兼容 `AngularJS` 的模块，我们引入了 iframe， iframe会先加载一套曾今的 `AngularJS` 宿主，然后再这个宿主中，运行 `AngularJS` 模块。为了实现通信，我们需要两套平台程序中，都引入一个基于 `postMessage` 实现的跨窗口通信库（因为默认跨域，所以用postMessage实现），有了它之后，我们就可以很方便的两边通信了。\n\n## AOT编译\n\n按照 `Angular` 官方的 `Aot` 编译流程即可。\n\n## 多Tab页\n\n在后台系统中，多Tab页是比较常用了。但是多Tab页，在单页中使用，会有一定的性能风险，这个依据实际的情况，进行使用。实现多Tab页的核心就是如何动态加载组件以及如何获取到要加载的组件。\n\n多Tab页面，实际就是一个 `Tabset` 组件，只是在 `tab-item` 的实现稍显特别一些，相关动态加载的源码：\n\n```js\n@ViewChild('dynamicComponentContainer', { read: ViewContainerRef }) dynamicComponentContainer: ViewContainerRef;\n\nconstructor(\n  private elementRef: ElementRef,\n  private renderer: Renderer2,\n  private tabset: TabsetComponent,\n  private resolver: ComponentFactoryResolver,\n  private parentContexts: ChildrenOutletContexts\n) {\n}\n\npublic destroy() {\n  let el = this.elementRef.nativeElement as HTMLElement;\n  // tslint:disable-next-line:no-unused-expression\n  el.parentNode && (el.parentNode.removeChild(el));\n}\n\nprivate loadComponent(component: any) {\n  let context = this.parentContexts.getContext(PRIMARY_OUTLET);\n  let injector = ReflectiveInjector.fromResolvedProviders([], this.dynamicComponentContainer.injector);\n  const resolver = context.resolver || this.resolver;\n  let factory = resolver.resolveComponentFactory(component);\n  //   let componentIns = factory.create(injector);\n  //   this.dynamicComponentContainer.insert(componentIns.hostView);\n  this.dynamicComponentContainer.createComponent(factory);\n}\n```\n\n**注意：要考虑组件卸载方法，如 destroy()**\n\n为了获取到当前要渲染的组件，我们可以借用路由来抓取：\n\n```js\nthis.router.events.subscribe(evt => {\n  if (evt instanceof NavigationEnd) {\n    let pageComponent;\n    let pageName;\n    try {\n      let nextRoute = this.route.children[0].children[0];\n      pageName = this.location.path();\n      pageComponent = nextRoute.component;\n    } catch (e) {\n      pageName = '$$notfound';\n      pageComponent = NotFoundComponent;\n    }\n    let idx = this.pageList.length + 1;\n    if (!this.pageList.find(x => x.name === pageName)) {\n      this.pageList.push({\n        header: `页面${idx}`,\n        comp: pageComponent,\n        name: pageName,\n        closable: true\n      });\n    }\n    setTimeout(() => {\n      this.selectedPage = pageName;\n    });\n  }\n});\n```\n\n# 3、总结\n\n以上就是大概的实现思路以及部分相关的细节。其他细节就需要根据实际的情况，进行酌情处理。\n\n该思路并不仅限于 `Angular` 框架，使用 `Vue、React` 也可以做到类似的效果。同时，这套东西也比较适合中小企业的后台平台（不一定非要多团队，一个团队按模块开发也是不错的）。\n\n如需要了解更多细节，可以参考：[ngx-modular-platform](https://github.com/hstarorg/ngx-modular-platform)，能给个 `star` 就更好了。\n\n在此抛砖引玉，希望能集思广益，提炼出更好的方案。欢迎讨论和 `提Issue`, `发PR`。\n"
  },
  {
    "path": "Angular系列/跟我学Angular2（1-初体验）.md",
    "content": "---\ntitle: 跟我学Angular2（1-初体验）\ndate: 2017/02/21 14:47:10\n---\n\n## 0、导言\n\nAngular1作为最流行的前端MV*框架，给前端开发带来了极大的便利性。但是，仍然有许多不好的地方已经很难再改变了。Angular团队根据WEB发展的趋势和Angular1中积累的经验来开发了一个全新的Angular，也就是Angular2。\n\n## 1、优势\n\nAngular2做了很激进的变化，带来的成果也是显而易见的。\n\n1. 极大的提高了性能\n2. 更强大的模块化\n3. 改进的依赖注入\n4. 对Web Component友好\n5. 原生移动支持 - iOS 和 Android\n6. 服务端渲染，搜索引擎优化\n\n## 2、工具链\n\n由于Angular2面向未来，使用了太多还不被当前主流浏览器支持的技术，跑起来还真不是一个容易的事情，所以我们需要一个工具链：\n\n![http://www.hubwiz.com/course/5599d367a164dd0d75929c76/img/toolchain.jpg](http://www.hubwiz.com/course/5599d367a164dd0d75929c76/img/toolchain.jpg)\n\nsystemjs - 通用模块加载器，支持AMD、CommonJS、ES6等各种格式的JS模块加载 \n\nes6-module-loader - ES6模块加载器，systemjs会自动加载这个模块 \n\ntraceur - ES6转码器，将ES6代码转换为当前浏览器支持的ES5代码。systemjs会自动加载 这个模块。\n\n## 3、Angular2 Hello world\n\n### Step1、下载angular2\n\n[https://angular.io/](https://angular.io/)是angular2的官网，我们需要通过npm进行下载angular2： npm install angular2 [https://www.npmjs.com/package/angular2](https://www.npmjs.com/package/angular2)。\n\n### Step2、引入angular2\n\n\t<!DOCTYPE html>\n\t<html lang=\"en\">\n\t\n\t<head>\n\t  <meta charset=\"UTF-8\">\n\t  <title></title>\n\t  <script src=\"../node_modules/angular2/bundles/angular2.sfx.dev.js\"></script>\n\t</head>\n\t\n\t<body>\n\t</body>\n\t\n\t</html>\n\n### Step3、 Hello angular\n\t\n\t<body>\n\t  <app></app>\n\t  <script>\n\t  var App = ng.Component({\n\t    selector: 'app',\n\t    template: '<h1>Hello {{name}}.</h1>'\n\t  }).Class({\n\t    constructor: function() {\n\t      this.name = 'Angular2';\n\t    }\n\t  });\n\t  ng.bootstrap(App);\n\t  </script>\n\t</body>\n\n\t\n\t\n\t\n\t\n\t\n"
  },
  {
    "path": "CSS3学习之路/CSS3入门之文本与字体.md",
    "content": "---\ntitle: CSS3入门之文本与字体\ndate: 2017/02/21 14:47:10\n---\n\n## 1、CSS3文本效果\n\n### 1.1、text-shadow文本阴影\n\n语法：``text-shadow: h-shadow v-shadow blur color;``(<水平阴影>，<垂直阴影>，[模糊距离]，[阴影颜色])\n\n示例：\n\n\t<h1 style=\"text-shadow: 5px 5px 2px green;\">我是文本阴影</h1>\n\t<h1 style=\"text-shadow: 0 0 5px blue;\">我是文本阴影</h1>\n\t<h1 style=\"text-shadow: 2px 2px 4px #000000;color: white;\">我是文本阴影</h1>\n\n<h1 style=\"text-shadow: 5px 5px 2px green;\">我是文本阴影</h1>\n\n<h1 style=\"text-shadow: 0 0 5px blue;\">我是文本阴影</h1>\n\n<h1 style=\"text-shadow: 2px 2px 4px #000000;color: white;\">我是文本阴影</h1>\n\n**该属性兼容IE10+以及所有现代浏览器**\n\n### 1.2、word-break文本换行\n\n语法： ``word-break: normal|break-all|keep-all;``\n\nnormal:默认换行；break-all:允许在单词内换行；keep-all:只能在半角空格或连字符处换行\n\n示例：\n\n\t<div style=\"width:100px;word-break:break-all;\">Nice to meet you. good mor-ning.</div>\n\t<div style=\"width:100px;word-break:keep-all;\">Nice to meet you. good mor-ning.</div>\n\n<div style=\"width:100px;word-break:break-all;\">Nice to meet you. good mor-ning.</div>\n<div style=\"width:100px;word-break:keep-all;\">Nice to meet you. good mor-ning.</div>\n\n### 1.3、text-overflow修剪文本\n\n语法：``text-overflow: clip|ellipsis|string;``\n\n示例：\n\n\t<div style=\"width: 100px; overflow:hidden; white-space:nowrap;text-overflow: clip;\">Nice to meet you. good mor-ning.</div>\n\t<div style=\"width: 100px; overflow:hidden; white-space:nowrap;text-overflow: ellipsis;\">Nice to meet you. good mor-ning.</div>\n\n<div style=\"width: 100px; overflow:hidden; white-space:nowrap;text-overflow: clip;\">Nice to meet you. good mor-ning.</div>\n<div style=\"width: 100px; overflow:hidden; white-space:nowrap;text-overflow: ellipsis;\">Nice to meet you. good mor-ning.</div>\n\n**注意：使用text-overflow的时候，需要与overflow:hidden;white-space:nowrap;协同使用**\n\n## 2、CSS3字体\n\n在CSS3之前，必须使用已经在用户计算机上安装好的字体，给Web设计带来很大的局限性。现在，通过CSS3,Web设计师可以使用他们喜欢的任意字体。\n\n### 2.1、@font-face引入网络字体\n\nFirefox、Chrome、Safari 以及 Opera 支持 .ttf (True Type Fonts) 和 .otf (OpenType Fonts) 类型的字体。\n\nInternet Explorer 9+ 支持新的 @font-face 规则，但是仅支持 .eot 类型的字体 (Embedded OpenType)。\n\n不兼容IE8，IE8-。\n\n示例：\n\n\t<style>\n\t\t@font-face {\n\t\t\tfont-family: SentyPaperCut;\n\t\t\tsrc:url(http://hstarcdn.github.io/fonts/SentyPaperCut.ttf);\n\t\t}\n\t\t@font-face {\n\t\t\tfont-family:SentyCreamPuff;\n\t\t\tsrc:url(http://hstarcdn.github.io/fonts/SentyCreamPuff.otf);\n\t\t}\n\t\t.font1,.font2{\n\t\t  font-size: 50px;\n\t\t}\n\t\t.font1{\n\t\t  color: red;\n\t\t  font-family: SentyTEA-Platinum;\n\t\t}\n\t\t.font2{\n\t\t  color: blue;\n\t\t  font-family: SentyCreamPuff;\n\t\t}\n\t</style>\n\n\t<span class=\"font1\">\n\t  自定义字体演示\n\t</span>\n\t<span class=\"font2\">\n\t  自定义字体演示\n\t</span>\n\n<style>\n\t@font-face {\n\t\tfont-family: SentyTEA-Platinum;\n\t\tsrc:url(http://hstarcdn.github.io/fonts/SentyTEA-Platinum.ttf);\n\t}\n\t@font-face {\n\t\tfont-family:SentyCreamPuff;\n\t\tsrc:url(http://hstarcdn.github.io/fonts/SentyCreamPuff.otf);\n\t}\n\t.font1,.font2{\n\t  font-size: 50px;\n\t}\n\t.font1{\n\t  color: red;\n\t  font-family: SentyTEA-Platinum;\n\t}\n\t.font2{\n\t  color: blue;\n\t  font-family: SentyCreamPuff;\n\t}\n</style>\n\n<span class=\"font1\">\n  自定义字体演示\n</span>\n<span class=\"font2\">\n  自定义字体演示\n</span>\n\n除此之外，在@font-face中，还可以设置多种字体描述符，如：\n\n<table class=\"dataintable\">\n<tbody><tr>\n<th style=\"width:20%;\">描述符</th>\n<th style=\"width:25%;\">值</th>\n<th>描述</th>\n</tr>\n\n<tr>\n<td>font-family</td>\n<td><i>name</i></td>\n<td>必需。规定字体的名称。</td>\n</tr>\n\n<tr>\n<td>src</td>\n<td><i>URL</i></td>\n<td>必需。定义字体文件的 URL。</td>\n</tr>\n\n<tr>\n<td>font-stretch</td>\n<td>\n\t<ul>\n\t<li>normal</li>\n\t<li>condensed</li>\n\t<li>ultra-condensed</li>\n\t<li>extra-condensed</li>\n\t<li>semi-condensed</li>\n\t<li>expanded</li>\n\t<li>semi-expanded</li>\n\t<li>extra-expanded</li>\n\t<li>ultra-expanded</li>\n\t</ul>\n</td>\n<td>可选。定义如何拉伸字体。默认是 \"normal\"。</td>\n</tr>\n\n<tr>\n<td>font-style</td>\n<td>\n\t<ul>\n\t<li>ormal</li>\n\t<li>italic</li>\n\t<li>oblique</li>\n\t</ul>\n</td>\n<td>可选。定义字体的样式。默认是 \"normal\"。</td>\n</tr>\n\n<tr>\n<td>font-weight</td>\n<td>\n\t<ul>\n\t<li>normal</li>\n\t<li>bold</li>\n\t<li>100</li>\n\t<li>200</li>\n\t<li>300</li>\n\t<li>400</li>\n\t<li>500</li>\n\t<li>600</li>\n\t<li>700</li>\n\t<li>800</li>\n\t<li>900</li>\n\t</ul>\n</td>\n<td>可选。定义字体的粗细。默认是 \"normal\"。</td>\n</tr>\n\n<tr>\n<td>unicode-range</td>\n<td><i>unicode-range</i></td>\n<td>可选。定义字体支持的 UNICODE 字符范围。默认是 \"U+0-10FFFF\"。</td>\n</tr>\n</tbody></table>"
  },
  {
    "path": "CSS3学习之路/CSS3入门之转换.md",
    "content": "---\ntitle: CSS3入门之转换\ndate: 2017/02/21 14:47:10\n---\n\n## 1、CSS3 转换\n\n### 1.1、转换是什么，能实现哪些效果？\n\n转换是使元素改变形状、尺寸和位置的一种效果，主要能实现的效果如下：\n\n1. 移动\n2. 缩放\n3. 转动\n4. 拉长\n5. 拉伸\n\n### 1.2、浏览器兼容\n\nCSS3的转换属性为 ``transform`` ，IE10+,Firefox,Chrome,Opera,Safari等现代浏览器支持transform属性，IE9需要-ms-前缀。\n\n## 2、 2D 转换\n\n准备工作：\n\n\t<style>\n\t  .container{\n\t    position:relative;border:1px solid red; width: 100px; height: 100px;\n\t  }\n\t  .container>div{\n\t    width: 50px; height: 50px; background: gray;\n\t  }\n\t</style>\n<style>\n  .container{\n    position:relative;border:1px solid red; width: 100px; height: 100px;\n  }\n  .container div{\n    width: 50px; height: 50px; background: gray;\n  }\n</style>\n\n### 2.1、translate() -- 移动\n\ntranslate(/\\*x坐标移动位移\\*/ left, /\\*y坐标移动位移\\*/ top)\n\n\t<h3>右移20px</h3>\n    <div class=\"container\">\n      <div style=\"transform: translate(20px);\"></div>\n    </div>\n    <h3>下移20px</h3>\n    <div class=\"container\">\n      <div style=\"transform: translate(0px,20px);\"></div>\n    </div>\n    <h3>左移20px，下移20px</h3>\n    <div class=\"container\">\n      <div style=\"transform: translate(-20px,20px);\"></div>\n    </div>\n\n<h3>右移20px</h3>\n<div class=\"container\">\n  <div style=\"transform: translate(20px);\"></div>\n</div>\n<h3>下移20px</h3>\n<div class=\"container\">\n  <div style=\"transform: translate(0px,20px);\"></div>\n</div>\n<h3>左移20px，下移20px</h3>\n<div class=\"container\">\n  <div style=\"transform: translate(-20px,20px);\"></div>\n</div>\n\n### 2.2、rotate() -- 旋转\n\nrotate(/\\*旋转角度\\*/ deg)\n\n\t<h3>旋转135度</h3>\n\t<div class=\"container\">\n\t  <div style=\"transform: rotate(135deg);\"></div>\n\t</div>\n\n<h3>旋转135度</h3>\n<div class=\"container\">\n  <div style=\"transform: rotate(135deg);\"></div>\n</div>\n\n### 2.3、scale() -- 缩放\n\nscale(/\\*宽度缩放比例\\*/ widthScale, /\\*高度缩放比例\\*/ heightScale)\n\n\t<h3>缩放到0.5倍</h3>\n\t<div class=\"container\">\n\t  <div style=\"transform: scale(0.5, 0.5);\"></div>\n\t</div>\n\t<h3>宽度缩放到1.5倍，高度缩放到0.25倍</h3>\n\t<div class=\"container\">\n\t  <div style=\"transform: scale(1.5, 0.25);\"></div>\n\t</div>\n\n<h3>缩放到0.5倍</h3>\n<div class=\"container\">\n  <div style=\"transform: scale(0.5, 0.5);\"></div>\n</div>\n<h3>宽度缩放到1.5倍，高度缩放到0.25倍</h3>\n<div class=\"container\">\n  <div style=\"transform: scale(1.5, 0.25);\"></div>\n</div>\n\n### 2.4、skew() -- 倾斜\n\nskew(/\\*X轴倾斜角度\\*/ xDeg, /\\*Y轴倾斜角度\\*/ yDeg)\n\n\t<h3>X轴翻转30度</h3>\n\t<div class=\"container\">\n\t  <div style=\"transform: skew(30deg);\"></div>\n\t</div>\n\t<h3>X轴翻转30度，Y轴翻转10度</h3>\n\t<div class=\"container\">\n\t  <div style=\"transform: skew(30deg, 10deg)\"></div>\n\t</div>\n\n<h3>X轴翻转30度</h3>\n<div class=\"container\">\n  <div style=\"transform: skew(30deg);\"></div>\n</div>\n<h3>X轴翻转30度，Y轴翻转10度</h3>\n<div class=\"container\">\n  <div style=\"transform: skew(30deg, 10deg)\"></div>\n</div>\n\n### 2.5、matrix() --矩阵\n\n\t<h3>旋转30度</h3>\n\t<div class=\"container\">\n\t  <div style=\"transform: matrix(0.866,0.5,-0.5,0.866,0,0)\"></div>\n\t</div>\n\n<h3>旋转30度</h3>\n<div class=\"container\">\n  <div style=\"transform: matrix(0.866,0.5,-0.5,0.866,0,0)\"></div>\n</div>\n\n\n### 2.6 Transform方法\n\n<table class=\"dataintable\">\n<tbody><tr>\n<th style=\"width:25%;\">函数</th>\n<th>描述</th>\n</tr>\n\n<tr>\n<td>matrix(<i>n</i>,<i>n</i>,<i>n</i>,<i>n</i>,<i>n</i>,<i>n</i>)</td>\n<td>定义 2D 转换，使用六个值的矩阵。</td>\n</tr>\n\n<tr>\n<td>translate(<i>x</i>,<i>y</i>)</td>\n<td>定义 2D 转换，沿着 X 和 Y 轴移动元素。</td>\n</tr>\n\n<tr>\n<td>translateX(<i>n</i>)</td>\n<td>定义 2D 转换，沿着 X 轴移动元素。</td>\n</tr>\n\n<tr>\n<td>translateY(<i>n</i>)</td>\n<td>定义 2D 转换，沿着 Y 轴移动元素。</td>\n</tr>\n\n<tr>\n<td>scale(<i>x</i>,<i>y</i>)</td>\n<td>定义 2D 缩放转换，改变元素的宽度和高度。</td>\n</tr>\n\n<tr>\n<td>scaleX(<i>n</i>)</td>\n<td>定义 2D 缩放转换，改变元素的宽度。</td>\n</tr>\n\n<tr>\n<td>scaleY(<i>n</i>)</td>\n<td>定义 2D 缩放转换，改变元素的高度。</td>\n</tr>\n\n<tr>\n<td>rotate(<i>angle</i>)</td>\n<td>定义 2D 旋转，在参数中规定角度。</td>\n</tr>\n\n<tr>\n<td>skew(<i>x-angle</i>,<i>y-angle</i>)</td>\n<td>定义 2D 倾斜转换，沿着 X 和 Y 轴。</td>\n</tr>\n\n<tr>\n<td>skewX(<i>angle</i>)</td>\n<td>定义 2D 倾斜转换，沿着 X 轴。</td>\n</tr>\n\n<tr>\n<td>skewY(<i>angle</i>)</td>\n<td>定义 2D 倾斜转换，沿着 Y 轴。</td>\n</tr>\n</tbody></table>\n\n\n## 3、3D 转换\n\n### 3.1、rotateX、rotateY\n\n\t<div class=\"container\">\n\t  <div style=\"transform: rotateY(0deg)\" id=\"fun2\"></div>\n\t</div>\n\t<script>  \n\tfunction fun2 (element) {\n\t  var i = 0;\n\t  var interval = setInterval(function(){\n\t    element.style.transform = 'rotateY(' + i + 'deg)';\n\t    i++;\n\t  }, 5);\n\t}\n\tfun2(document.getElementById('fun2'));\n\t</script>\n<div class=\"container\">\n  <div style=\"transform: rotateY(0deg)\" id=\"fun2\"></div>\n</div>\n<script>  \nfunction fun2 (element) {\n  var i = 0;\n  var interval = setInterval(function(){\n    element.style.transform = 'rotateY(' + i + 'deg)';\n    i++;\n  }, 5);\n}\nfun2(document.getElementById('fun2'));\n</script>\n\n### 3.2、Transform方法\n\n<table class=\"dataintable\">\n<tbody><tr>\n<th style=\"width:25%;\">函数</th>\n<th>描述</th>\n</tr>\n\n<tr>\n<td>matrix3d(<i>n</i>,<i>n</i>,<i>n</i>,<i>n</i>,<i>n</i>,<i>n</i>,<br><i>n</i>,<i>n</i>,<i>n</i>,<i>n</i>,<i>n</i>,<i>n</i>,<i>n</i>,<i>n</i>,<i>n</i>,<i>n</i>)</td>\n<td>定义 3D 转换，使用 16 个值的 4x4 矩阵。</td>\n</tr>\n\n<tr>\n<td>translate3d(<i>x</i>,<i>y</i>,<i>z</i>)</td>\n<td>定义 3D 转化。</td>\n</tr>\n\n<tr>\n<td>translateX(<i>x</i>)</td>\n<td>定义 3D 转化，仅使用用于 X 轴的值。</td>\n</tr>\n\n<tr>\n<td>translateY(<i>y</i>)</td>\n<td>定义 3D 转化，仅使用用于 Y 轴的值。</td>\n</tr>\n\n<tr>\n<td>translateZ(<i>z</i>)</td>\n<td>定义 3D 转化，仅使用用于 Z 轴的值。</td>\n</tr>\n\n<tr>\n<td>scale3d(<i>x</i>,<i>y</i>,<i>z</i>)</td>\n<td>定义 3D 缩放转换。</td>\n</tr>\n\n<tr>\n<td>scaleX(<i>x</i>)</td>\n<td>定义 3D 缩放转换，通过给定一个 X 轴的值。</td>\n</tr>\n\n<tr>\n<td>scaleY(<i>y</i>)</td>\n<td>定义 3D 缩放转换，通过给定一个 Y 轴的值。</td>\n</tr>\n\n<tr>\n<td>scaleZ(<i>z</i>)</td>\n<td>定义 3D 缩放转换，通过给定一个 Z 轴的值。</td>\n</tr>\n\n<tr>\n<td>rotate3d(<i>x</i>,<i>y</i>,<i>z</i>,<i>angle</i>)</td>\n<td>定义 3D 旋转。</td>\n</tr>\n\n<tr>\n<td>rotateX(<i>angle</i>)</td>\n<td>定义沿 X 轴的 3D 旋转。</td>\n</tr>\n\n<tr>\n<td>rotateY(<i>angle</i>)</td>\n<td>定义沿 Y 轴的 3D 旋转。</td>\n</tr>\n\n<tr>\n<td>rotateZ(<i>angle</i>)</td>\n<td>定义沿 Z 轴的 3D 旋转。</td>\n</tr>\n\n<tr>\n<td>perspective(<i>n</i>)</td>\n<td>定义 3D 转换元素的透视视图。</td>\n</tr>\n</tbody></table>"
  },
  {
    "path": "CSS3学习之路/CSS3入门之边框与背景.md",
    "content": "---\ntitle: CSS3入门之边框与背景\ndate: 2017/02/21 14:47:10\n---\n\n## 1、前言\n\nCSS3作为CSS的最新版本，在展示效果上有非常大的提升，接下来，我们就一起领略一下CSS3的风采吧。\n\n## 2、CSS3边框\n\n```\n<style>\n    .td{\n      width: 200px;\n      height: 100px;\n      border: 1px solid black;\n      margin: 10px 0; \n    }\n</style>\n```\n\n### 2.1、border-radius(用于设置圆角边框)\n\n在CSS2时代，要想实现圆角边框，是一件非常麻烦的事情。一种实现方式是使用一个背景图片，为了实现伸缩效果，还需要至少3张图片拼凑，相当麻烦。另外一种实现方式是使用多个div重叠来实现圆角。\n\n在CSS3中，有一个非常简单的属性，那就是border-radius。\n\n语法： ``border-radius: 1-4 length|% / 1-4 length|%;``\n\n```css\nborder-radius: 10px;\n//等价于\nborder-top-left-radius:10px;\nborder-top-right-radius:10px;\nborder-bottom-right-radius:10px;\nborder-bottom-left-radius:10px;\n```\n\n<div style=\"border-radius:10px;\">\n    演示圆角边框\n</div>\n\n<div style=\"border-radius:10px;\">\n    演示圆角边框\n</div>\n\n**兼容性说明：** IE9+，Chrome,FF,Safari,Oprea\n```\ndiv\n{\n  border:2px solid;\n  border-radius:25px;\n  -moz-border-radius:25px; /* Old Firefox */\n}\n```\n### 2.2、box-shadow(用于添加边框阴影)\n\n语法： ``box-shadow: h-shadow v-shadow blur spread color inset;``,其中h-shadow和v-shadow是必须设置，允许负值。【参数说明：水平阴影的位置，垂直阴影的位置，模糊距离，阴影的尺寸，阴影的颜色，外部引用(outset)改为内部阴影】\n\n```html\n<div class=\"td\" style=\"border-radius:10px; border: 1px solid red;\">\n    演示圆角边框\n</div>\n<div class=\"td\" style=\"box-shadow: 2px 2px red;\">\n\t简单阴影\n</div>\n<div class=\"td\" style=\"box-shadow: -2px -2px red;\">\n  简单阴影\n</div>\n<div class=\"td\" style=\"box-shadow: -2px -2px red;\">\n\t简单阴影\n</div>\n<div class=\"td\" style=\"box-shadow: -2px -2px 10px red;\">\n  带模糊效果的阴影\n</div>\n<div class=\"td\" style=\"box-shadow: -2px -2px 10px red;\">\n    带模糊效果的阴影\n</div>\n<div class=\"td\" style=\"box-shadow: 2px 2px 10px 10px red;\">\n  带模糊效果指定尺寸的阴影\n</div>\n<div class=\"td\" style=\"box-shadow: 2px 2px 10px 10px red;\">\n\t带模糊效果指定尺寸的阴影\n</div>\n<div class=\"td\" style=\"box-shadow: 2px 2px 10px 10px red inset;\">\n  内部阴影\n</div>\n<div class=\"td\" style=\"box-shadow: 2px 2px 10px 10px red inset;\">\n    内部阴影\n</div>\n```\n\n**兼容性说明：** IE9+，Chrome,FF,Safari,Oprea\n\n### 2.3、border-image(CSS3边框图片)\n\nborder-image是简写属性，全部是：\n\n```css\n\tborder-image-source  //背景图片源\n\tborder-image-slice  //图片边框内偏移\n\tborder-image-width  //图片边框的宽度\n\tborder-image-outset  //边框图像区域超出边框的量\n\tborder-image-repeat  //边框是否适应平铺(repeated)、铺满(rounded)、拉伸(stretched)\n```\n\n```html\n<div style=\"border-width:10px;border-image: url(http://www.w3school.com.cn/i/border.png) 10 10 round;\">\n  简单图片边框\n</div>\n<div style=\"border-width:10px;border-image: url(http://www.w3school.com.cn/i/border.png) 10 10 round\">\n\t简单图片边框\n</div>\n<div style=\"border-width:10px;border-image: url(http://www.w3school.com.cn/i/border.png) 10 10 50 round\">\n  完全设置的图片边框\n</div>\n<div style=\"border-width:10px;border-image: url(http://www.w3school.com.cn/i/border.png) 10 10 50 round\">\n完全设置的图片边框\n</div>\n```\n\n**兼容性说明：** Chrome,FF,Safari,Oprea\n```\ndiv\n{\n  border-image:url(border.png) 30 30 round;\n  -moz-border-image:url(border.png) 30 30 round; /* 老的 Firefox */\n  -webkit-border-image:url(border.png) 30 30 round; /* Safari 和 Chrome */\n  -o-border-image:url(border.png) 30 30 round; /* Opera */\n}\n```\n\n## 3、CSS3背景\n\n### 整体兼容性\n\n以下CSS背景的特性，全部支持IE9+,FF,Chrome,Safari,Oprea\n\n### 3.1、background-size(用于规定背景图片的尺寸)\n\n在以前的CSS中，背景图片的大小，是由图片本身的大小决定的。在CSS3中，有一个简单的CSS样式可以设置背景图片的大小，允许我们在不同的环境中重复使用背景图片。可以以像素或百分比规定尺寸。\n```html\n<div style=\"\n  background-image:url(http://www.w3school.com.cn/i/bg_flower_small.gif);\n  background-size: 50% 70%;\n  background-repeat:no-repeat;\">\n</div>\n\n<div style=\"background-image:url(http://www.w3school.com.cn/i/bg_flower_small.gif);background-size: 50% 70%;background-repeat:no-repeat;\">\n\t简单设置背景图大小\n</div>\n```\n\n### 3.2、background-origin(规定背景图片的定位区域)\n\n盒子模型示意图：\n<img src=\"http://www.w3school.com.cn/i/background-origin.gif\" alt=\"box\" />\n\nbackground-origin属性则可以设置背景图片放置于哪个区域上（content-box,padding-box,border-box）\n\n```html\n<div style=\"width:66px;height:125px;\nbackground-image:url(http://www.w3school.com.cn/i/bg_flower_small.gif);\nbackground-origin:content-box; \npadding: 20px;border:20px solid red;\"></div>\n\n<div style=\"width:66px;height:125px;\nbackground-image:url(http://www.w3school.com.cn/i/bg_flower_small.gif);\nbackground-origin:border-box; \npadding: 20px;border:20px solid red;\"></div>\n\n<div style=\"width:66px;height:125px;\nbackground-image:url(http://www.w3school.com.cn/i/bg_flower_small.gif);\nbackground-origin:padding-box;\npadding: 20px;border:20px solid red;\"></div>\n```\n\n### 3.3、多重背景\n\n可以针对标签设置多个背景，用法如下：\n\n```css\nbody\n{ \n  background-image:url(bg_flower.gif),url(bg_flower_2.gif);\n}\n```"
  },
  {
    "path": "Canvas学习札记/01_初识Canvas，绘制简单图形.md",
    "content": "---\ntitle: 01_初识Canvas，绘制简单图形\ndate: 2017/02/21 14:47:10\n---\n\n## 0、关于Canvas\n\n``<canvas>`` 是HTML5新增的一个标签，用于定义图形，比如图表和其他图像。\n\n``<canvas>`` 标签只是图形容器，必须要使用脚本来绘制图形。\n\n一句话概括就是：``<canvas>`` 是浏览器上的画图，允许你通过js自由作画。\n\n### Canvas和SVG与VML的不同\n\n``<canvas>`` 有一个基于JS的绘图API，它本身并不会绘制图形。SVG和VML都是用一个XML文档来描述图形。\n\n虽然它们在功能上基本相同，但是从表面上来看，它们非常不同。SVG和VML绘图易于编辑，只需要从描述中修改元素属性。而Canvas想移除元素，往往需要擦掉绘图重新绘制它。\n\n### Canvas兼容HTML5标准属性和事件\n\n``<canvas>`` 作为一个HTML的新标签，标准的HTML属性和事件它都支持。比如可以设置 ``title、style、class`` 等属性，也可以使用诸如 ``onclick`` 等事件。\n\n```html\n<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n  <meta charset=\"UTF-8\">\n  <title>Canvas Test</title>\n  <style>\n    body{\n      text-align: center;\n    }\n    .c1{\n      border: 1px solid red;\n      height: 600px;\n      width: 800px;\n    }\n  </style>\n</head>\n<body>\n  <canvas id=\"c1\" onclick=\"alert('abc')\" class=\"c1\"></canvas>\n</body>\n</html>\n```\n\n## 1、使用Canvas\n\n要使用``canvas``，首先，我们先得html中加入canvas标签。最好，再加上一个id属性（也可以不加，只是查找该元素要稍微麻烦点）。\n\n```html\n...\n<body>\n  <canvas id=\"c1\" onclick=\"alert('abc')\" class=\"c1\"></canvas>\n  <script>\n    var c1 =document.getElementById('c1');\n    // 如果不用id属性，我们可以用如下方式来获取canvas对象\n    //var c1 = document.getElementsByTagName('canvas')[0]; \n  </script>\n</body>\n...\n```\n\n在获取到 ``canvas`` 元素之后，我们需要通过 ``getContext(contextID)`` 方法获取到画布。\n\n当前 ``contextID``的值仅仅可以用'2d'，在未来，可能会允许传递'3d'，来进行三维绘图。\n\n```html\n...\n<body>\n  <canvas id=\"c1\" onclick=\"alert('abc')\" class=\"c1\"></canvas>\n  <script>\n    var c1 =document.getElementById('c1');\n    // 如果不用id属性，我们可以用如下方式来获取canvas对象\n    //var c1 = document.getElementsByTagName('canvas')[0];\n    var context = c1.getContext('2d');\n    console.log(context); //可以看到context是一个CanvasRenderingContext2D对象\n  </script>\n</body>\n...\n```\n\n``CanvasRenderingContext2D`` 对象实现了一个画布所使用的大多数方法。现在我们就需要对它来进行使用，将图像绘制到浏览器上。\n\n### 1.1、绘制矩形\n\n关于矩形的绘制，主要有三个方法：\n\n* fillRect(x, y, width, height) 用于填充矩形\n* strokeRect(x, y, width, height) 用于绘制矩形边框\n* clearRect(x, y, width, height) 用于清空矩形区域（设置矩形区域为空白）\n\n其中 ``x,y``表示从那个点开始绘制。``width,height`` 表示矩形的宽度和高度。\n\n要设置矩形的填充颜色，需要通过 ``fillStyle`` 来控制，支持 ``'red', '#fff', 'rgb(10,10,10)', 'rgba(10,10,10,10,0.5)'``等多种颜色属性。\n\n要设置矩形的边框颜色，需要通过 ``strokeStyle`` 来控制，属性值和 ``fillStyle`` 一致。\n\n\n```javascript\n...\n//绘制红色矩形\ncontext.fillStyle = 'red'; \ncontext.fillRect(10,10,100,100);\n\n//绘制蓝色矩形框\ncontext.strokeStyle = 'blue';\ncontext.strokeRect(150,150,100,100);\n\n//清空矩形区域（设置矩形区域为空白）\ncontext.clearRect(100,100,100,100);\n...\n```\n\n### 1.2、绘制线条\n\n我们可以通过 ``lineTo(x, y)`` 绘制直线。两点成直线，绘制直线需要两个点，所以我们需要先设置一个起点，一般来说，我们使用 ``moveTo(x, y)`` 设置笔触的位置。当然，你也可以用 ``lineTo(x, y)`` 来设置一个笔触点。\n\n在没有设置笔触的场景下，以下两段代码的效果完全一致：\n\n```javascript\n//画线，设置起点。\ncontext.moveTo(200, 200);\n//设置轨迹\ncontext.lineTo(500,500);\n//画线\ncontext.stroke();\n```\n\n```javascript\n//画线，设置起点。\ncontext.lineTo(200, 200);\n//设置轨迹\ncontext.lineTo(500,500);\n//画线\ncontext.stroke();\n```\n\n**一般来说，我们会在 ``canvas`` 初始化或者 ``beginPath()`` 调用后，通过 ``moveTo(x, y)`` 来设置一个初始笔触点。**\n\n要同时绘制多个线条，我们应该通过 ``beginPath()`` 来建立路径。\n\n```javascript\n//用线条绘制了一个矩形\ncontext.beginPath();\ncontext.moveTo(400, 400);\ncontext.lineTo(450, 400);\ncontext.lineTo(450, 450);\ncontext.lineTo(400, 450);\ncontext.closePath();\n//真实的绘图\ncontext.stroke();\n```\n\n看了以上的代码，可能会有一个疑惑，为什么仅仅三个线条就构成了一个矩形呢？\n\n原因在于当调用 ``closePath()`` 的时候，会把最后的笔触点和最开始的笔触点连接在一起，这个时候也就构成了第四条直线。\n\n **注意：当前路径为空，即调用beginPath()之后，或者canvas刚建的时候，第一条路径构造命令通常被视为是moveTo（），无论最后的是什么。出于这个原因，你几乎总是要在设置路径之后专门指定你的起始位置。**\n \n **闭合路径 ``closePath()``,不是必需的。这个方法会通过绘制一条从当前点到开始点的直线来闭合图形。如果图形是已经闭合了的，即当前点为开始点，该函数什么也不做。**\n \n **当你调用 ``fill()`` 函数时，所有没有闭合的形状都会自动闭合，所以你不需要调用 ``closePath()`` 函数。但是调用stroke()时不会自动闭合。**\n \n再来填充一个梯形玩玩：\n\n```javascript\ncontext.beginPath();\ncontext.moveTo(100, 400);\ncontext.lineTo(200, 400);\ncontext.lineTo(250, 500);\ncontext.lineTo(50, 500);\ncontext.fill();\n```\n\n### 1.3、绘制矩形线条\n\n矩形线条是一个比较常用的图形，所以提供了一个简单的方法来直接绘制：\n\n```javascript\n//绘制矩形线条\ncontext.beginPath();\ncontext.rect(700, 10, 50, 50);\ncontext.stroke();\ncontext.fillStyle = 'red';\ncontext.fill();\n```\n\n\n### 1.4、绘制圆弧\n\n绘制圆弧或者圆的时候，我们可以使用如下方法：\n\n* arc(x, y, radius, startAngle, endAngle, anticlockwise) 画一个以（x,y）为圆心的以radius为半径的圆弧（圆），从startAngle开始到endAngle结束，按照anticlockwise给定的方向（默认为顺时针）来生成。\n* arcTo(x1, y1, x2, y2, radius) 根据给定的控制点和半径画一段圆弧，再以直线连接两个控制点。\n\nanticlockwise为true则表示逆时针绘制。\n\n```javascript\n//绘制圆弧\ncontext.beginPath();\ncontext.arc(550, 150, 100, getRadian(90) , getRadian(360), false);\ncontext.stroke();\ncontext.beginPath();\ncontext.arc(550, 150, 100, 0, getRadian(90), false);\ncontext.fill();\n```\n\n以上代码，绘制了两个弧形，一个空心，一个实心。一般再绘制圆弧的时候就不要执行 ``moveTo(x, y)``，否则绘制终点会被连接到这个触点上。\n\n**注意：在arc函数中，``startAngle`` 和 ``endAngle`` 属性值都是弧度，而不是我们所熟知的角度。所以我们一个一个角度转换为弧度的函数，如下：**\n\n```javascript\nfunction getRadian(degrees/*角度值*/){\n  return (Math.PI / 180) * degrees;\n}\n```\n\n**arcTo没吃透，暂时描述不出来，先简单看看示例：**\n\n```javascript\n//绘制圆弧（必须要设定起始点）\ncontext.beginPath();\ncontext.fillRect(600, 400, 10, 10);\ncontext.fillRect(700, 500, 10, 10);\ncontext.fillStyle = 'blue';\ncontext.fillRect(700, 400, 10, 10);\n\ncontext.beginPath();\ncontext.moveTo(700, 400);\ncontext.arcTo(600, 600, 700, 700, 500);\ncontext.stroke();\n```\n\n\n## 2、其他\n\n### 2.1、canvas检查支持性\n\n如果仅仅需要在UI上体现，那么我们可以在 ``<canvas>`` 标签内部放置元素，如果浏览器不支持 ``<canvas>`` 标签，那么内部的元素就会被浏览器解析，而显示出来。\n\n```html\n<canvas id=\"stockGraph\" width=\"150\" height=\"150\">\n  <p>Canvas not be support.</p>\n</canvas>\n```\n\n除此之外，我们也可以用js的方式来检查。\n\n```javascript\nvar c1 = document.getElementById('c1');\n//如果canvas元素没有getContext方法，那么就证明浏览器不支持canvas。\nif(!c1.getContext){\n  console.log('Canvas not be support.')\n}\n```\n\n### 2.2、canvas的width和height属性\n\n``<canvas>`` 对象有两个比较特别的属性，``width、height``，这两者用于控制画布的大小，width的默认值是300，height的默认值为150。**当这两个属性值有变化时，在该画布上已经完成的任何绘图都会擦除掉。**\n\n```javascript\nvar c1 = document.getElementById('c1');\nconsole.log('default width:', c1.width, '; default height:', c1.height);\nc1.width = 500;\nc1.height = 600;\n```\n\n``<canvas>`` 的的height和width属性如果和用css设置的height和width样式不一致，那么就可能会产生扭曲。\n\n### 2.3、来个好玩的，画个桃心\n\n```javascript\nfunction drawHeart() {\n  context.fillStyle = 'purple';\n  //三次曲线\n  context.beginPath();\n  context.moveTo(75, 40);\n  context.bezierCurveTo(75, 37, 70, 25, 50, 25);\n  context.bezierCurveTo(20, 25, 20, 62.5, 20, 62.5);\n  context.bezierCurveTo(20, 80, 40, 102, 75, 120);\n  context.bezierCurveTo(110, 102, 130, 80, 130, 62.5);\n  context.bezierCurveTo(130, 62.5, 130, 25, 100, 25);\n  context.bezierCurveTo(85, 25, 75, 37, 75, 40);\n  context.fill();\n}\ndrawHeart();\n``` \n\n### 2.4 附上测试代码\n\n```html\n<!DOCTYPE html>\n<html lang=\"en\">\n\n<head>\n  <meta charset=\"UTF-8\">\n  <title>Canvas Test</title>\n  <style>\n    body {\n      text-align: center;\n    }\n    \n    .c1 {\n      border: 1px solid red;\n      height: 600px;\n      width: 800px;\n    }\n  </style>\n</head>\n\n<body>\n  <canvas id=\"c1\" onclick=\"alert('abc')\" class=\"c1\"></canvas>\n  <script src=\"main.js\"></script>\n</body>\n</html>\n```\n\n```javascript\nvar c1 = document.getElementById('c1');\n// 如果不用id属性，我们可以用如下方式来获取canvas对象\n//var c1 = document.getElementsByTagName('canvas')[0];\nc1.width = 800;\nc1.height = 600;\nvar context = c1.getContext('2d');\nconsole.log(context); //可以看到context是一个CanvasRenderingContext2D对象\n\nfunction getRadian(degrees/*角度值*/) {\n  return (Math.PI / 180) * degrees;\n}\n\n//绘制红色矩形\ncontext.fillStyle = 'red';\ncontext.fillRect(10, 10, 100, 100);\n\n//绘制蓝色矩形框\ncontext.strokeStyle = 'blue';\ncontext.strokeRect(150, 150, 100, 100);\n\n//清空矩形区域（设置矩形区域为空白）\ncontext.clearRect(100, 100, 100, 100);\n\n//画线，设置起点。\ncontext.moveTo(200, 200);\n//设置轨迹\ncontext.lineTo(500, 500);\n//画线\ncontext.stroke();\n\n//绘制空心矩形\ncontext.beginPath();\ncontext.moveTo(400, 400);\ncontext.lineTo(450, 400);\ncontext.lineTo(450, 450);\ncontext.lineTo(400, 450);\ncontext.closePath();\ncontext.stroke();\n\n//绘制实心梯形\ncontext.beginPath();\ncontext.moveTo(100, 400);\ncontext.lineTo(200, 400);\ncontext.lineTo(250, 500);\ncontext.lineTo(50, 500);\ncontext.fill();\n\n//绘制圆弧\ncontext.beginPath();\ncontext.arc(550, 150, 100, getRadian(90), getRadian(360), true);\ncontext.stroke();\ncontext.beginPath();\ncontext.arc(550, 150, 100, 0, getRadian(90), true);\ncontext.stroke();\n\n//绘制圆弧（必须要设定起始点）\ncontext.beginPath();\ncontext.fillRect(600, 400, 10, 10);\ncontext.fillRect(700, 500, 10, 10);\ncontext.fillStyle = 'blue';\ncontext.fillRect(700, 400, 10, 10);\n\ncontext.beginPath();\ncontext.moveTo(700, 400);\ncontext.arcTo(600, 600, 700, 700, 500);\ncontext.stroke();\n\n//绘制矩形线条\ncontext.beginPath();\ncontext.rect(700, 10, 50, 50);\ncontext.stroke();\ncontext.fillStyle = 'red';\ncontext.fill();\n\nfunction drawHeart() {\n  context.fillStyle = 'purple';\n  //三次曲线\n  context.beginPath();\n  context.moveTo(75, 40);\n  context.bezierCurveTo(75, 37, 70, 25, 50, 25);\n  context.bezierCurveTo(20, 25, 20, 62.5, 20, 62.5);\n  context.bezierCurveTo(20, 80, 40, 102, 75, 120);\n  context.bezierCurveTo(110, 102, 130, 80, 130, 62.5);\n  context.bezierCurveTo(130, 62.5, 130, 25, 100, 25);\n  context.bezierCurveTo(85, 25, 75, 37, 75, 40);\n  context.fill();\n}\ndrawHeart();\n```"
  },
  {
    "path": "ES6入门/ES6入门系列一（基础）.md",
    "content": "---\ntitle: ES6入门系列一（基础）\ndate: 2017/02/21 14:47:10\n---\n\n##1、let命令\n**Tips:**\n\n1. 块级作用域（只在当前块中有效）\n2. 不会变量提升（必须先申明在使用）\n3. 让变量独占该块，不再受外部影响\n4. 不允许重复声明\n\n**总之：let更像我们熟知的静态语言的的变量声明指令**\n\nES6新增了let命令，用来声明变量。用法类似于var，但所声明的变量，只能在let命令所在的代码块内有效。\n\nlet声明的变量只有块级作用域\n\n\t'use strict'\n\t{\n\t  let a = 1;\n\t}\n\tconsole.log(a); //结果是什么？\n\n看一段熟悉的代码：\n\n\tvar a = [];\n\tfor (var i = 0; i < 10; i++) {\n\t  a[i] = function () {\n\t    console.log(i);\n\t  };\n\t}\n\tconsole.log(a[6]()); //结果是什么？\n\n如果改用let的话，那么看以下代码输出什么？\n\n\t'use strict'\n\tvar a = [];\n\tfor (let i = 0; i < 10; i++) {\n\t  a[i] = function () {\n\t    console.log(i);\n\t  };\n\t}\n\tconsole.log(a[6]()); // ?\n\n同时，在使用let的时候，必须先申明再使用，不像var会变量提升：\n\n\t'use strict'\n\tconsole.log(a);\n\tlet a = 1;\n\nES6中明确规定，如果区块存在let和const，那么该区块就形成封闭作用域，凡是在声明致歉就使用这些变量，就会报错。简称“暂时性死区”（temporal dead zone，简称TDZ）。\n\n看一个不太容易发现的死区：（注：该代码未测试）\n\n\tfunction bar(x=y, y=2) {\n\t  return [x, y];\n\t}\n\t\n\tbar(); // 报错\n\n调用bar之所以报错，是因为参数x默认值等于另一个参数y，而此时y还没有声明，属于“死区”。\n\n需要注意，函数参数作用域和函数体的作用域是分离的：\n\n\tlet foo = 'outer';\n\t\n\tfunction bar(func) {\n\t  let foo = 'inner';\n\t  console.log(func()); // outer\n\t}\n\t\n\tbar(function(){\n\t  console.log(foo);\n\t});\n\n同时，let还不允许重复声明\n\t\n\t{\n\t  let a = 1;\n\t  var a = 1;\n\t}\n\t{\n\t  let a = 1;\n\t  let a = 2;\n\t}\n\n##2、const命令\n**Tips：**\n\n1. const用于声明常量，一旦声明，值就不能改变\n2. const具有块级作用域\n3. const不能变量提升（先声明后使用）\n4. 不可重复声明\n\n**const看起来很像我们熟知的静态语言的只读对象**\n\nconst声明常量，一旦声明，值将是不可变的。\n\n\t'use strict'\n\tconst PI = 3.1415;\n\tPI // 3.1415\n\tPI = 3; //Error\n\nconst指令指向变量所在的地址，所以对该变量进行属性设置是可行的（未改变变量地址），如果想完全不可变化（包括属性），那么可以使用冻结。\n\n\t'use strict'\n\tconst C1 = {};\n\tC1.a = 1;\n\tconsole.log(C1.a); // 1 \n\n\t//冻结对象，此时前面用不用const都是一个效果\n\tconst C2 = Object.freeze({}); \n\tC2.a = 1; //Error,对象不可扩展\n\tconsole.log(C2.a);\n\n##3、全局对象属性\n\nJavaScript中，全局对象是最顶层的对象，浏览器中是window对象，Node中是global对象，ES5规定，所有全局变量都是全局对象的属性。\n\n在ES6中，var和function申明的变量，属于全局对象的属性，let和const则不是全局对象的属性。\n\n\t'use strict'\n\tlet b = 2;\n\tconsole.log(global.b); // undefined"
  },
  {
    "path": "ES6入门/ES6入门系列三（特性总览下）.md",
    "content": "---\ntitle: ES6入门系列三（特性总览下）\ndate: 2017/02/21 14:47:10\n---\n\n# 0、导言\n\n最近从coffee切换到js，代码量一下子变大了不少，也多了些许陌生感。\n\n为了在JS代码中，更合理的使用ES6的新特性，特在此对ES6的特性做一个简单的总览。\n\n# 1、模块(Module - Chrome测试不可用)\n\n>在ES6中，有class的概念，不过这只是语法糖，并没有解决模块化问题。Module功能则是为了解决模块化问题而提出的。\n\n我们可以使用如下方式定义模块：\n\n11_lib.js文件内容\n\n\t// 导出属性和方法\n\texport var PI = 3.1415926;\n\texport function calcCircularArea(r){\n\t  return PI * r * r;\n\t}\n\napp.js文件内容\n\n\t//导出所有，使用别名调用\n\timport * as lib from '11_lib';\n\tconsole.log(lib.calcCircularArea(2));\n\tconsole.log(lib.PI);\n\t\n\t//导出属性和方法\n\timport {calcCircularArea, PI} from '11_lib';\n\tconsole.log(calcCircularArea(2));\n\tconsole.log(PI);\n\n\n# 2、模块加载器(Module Loaders - Chrome测试不可用)\n\n既然用了定义module的规范，那么也就需要一个模块加载器，需要支持如下内容：\n\n1. 动态加载\n2. 状态隔离\n3. 全局命名空间隔离\n4. 编译钩子\n5. 嵌套虚拟化\n\n```javascript\nSystem.import('11_lib').then(function(m) {\n  console.log(m.calcCircularArea(2));\n  console.log(m.PI);\n});\n```\n\n# 3、图 + 集合 + 弱引用图 + 若引用集合(Map + Set + WeakMap + WeakSet)\n\n在ES6中，新增了几种数据结构。\n\n**Map**是一种类似于Object的结构，Object本质上是键值对的集合，但是只能是字符串当做键，所以有一定的使用限制。Map的话，则可以拿任意类型来作为key。具体使用如下：\n\n\tvar map = new Map();\n\tvar key = {key: 'hah'};\n\tmap.set(key, '1'); //设置key-value\n\tmap.set(key, 2); //对已有key进行设置，表示覆盖\n\tconsole.log(map.get(key)); //获取key的值\n\tconsole.log(map.size);//获取map的元素个数\n\tmap.has(key); //判断map中有指定的key\n\tmap.delete(key); //删除map中指定的key\n\tmap.clear(); //清空map\n\n**WeakMap**和Map是比较类似的，唯一的区别是只接受对象作为键名（null除外），而且键名所指向的对象，不计入垃圾回收机制。\n\n**Set**是一种类似于数组，但成员的值都是唯一（引用唯一）的一种数据结构。具体使用如下：\n\n\tvar set = new Set();//定义set\n\tset.add(1).add(2).add(1).add('2'); //添加数据\n\tconsole.log(set.size);//查看set中元素的数量，结果应该是3，因为重复添加不计算，2和'2'不等。\n\tset.delete(1); //删除set的值（通过value删除）。\n\tset.has(1); //set是否包含某个value\n\tset.keys(); //返回set的所有key\n\tset.values(); //返回set的所有value\n\tset.clear();//清空set\n\n**WeakSet**和Set也是比较类型的，和Set有两个区别，一个是成员只能是对象；二个是WeakSet是不可遍历的。\n\n\n# 4、代理（Proxies） (Chrome测试不可用)\n\n代理允许用宿主的行为来创建对象，能够实现拦截，对象的虚拟化，日志和分析等功能。\n\n# 5、数据类型Symbols\n\n在ES5中，JS只有6中原始类型，在ES6中，新增了Symbols类型，成为了JS中的第7种原始类型。\n该类型表示独一无二的值。使用如下：\n\nvar key = Symbol(); //定义Symbol对象\nconsole.log(typeof key); //symbol ，表示为类型，而且不是string类型的。\nkey = Symbol('这是一个说明'); //可以在定义Symbol的时候，添加一个说明\n\nSymbol不能与其他类型值进行运算，但是可以显式转换为字符串，和转换为布尔值\n\n\tconsole.log(key.toString());\n\tconsole.log(String(key));\n\t\n\tif(key){\n\t  console.log('key is true');\n\t}\n\n在对象的内部，要使用Symbol值定义属性时，必须放在方括号中。\n\n\tvar obj = {\n\t  [key]: 'abc'\n\t};\n\n# 6、可以子类化的内置对象\n\n在ES6中，我们可以自定义类型来继承内置对象，这个时候，如果要自定义构造函数，必须要在构造函数中调用super(),来呼叫父类的构造。\n\n\t'use strict';\n\tclass MyArray extends Array {\n\t    // 如果要定义constuctor，那么就必须要使用super来执行父类的构造\n\t    constructor(){\n\t      super();\n\t    }\n\t}\n\t\n\tvar arr = new MyArray();\n\tarr[1] = 12;\n\tconsole.log(arr.length === 2);\n\n# 7、新增的API（Math + Number + String + Array + Object APIs）\n\n如下代码，一目了然：\n\n\t//数字类api\n\tNumber.EPSILON; //增加常量e\n\tNumber.isInteger(Infinity) // false\n\tNumber.isNaN(\"NaN\") // false\n\t\n\t//数学类api\n\tMath.acosh(3) // 1.762747174039086\n\tMath.hypot(3, 4) // 5\n\tMath.imul(Math.pow(2, 32) - 1, Math.pow(2, 32) - 2) // 2\n\t\n\t//字符串类api\n\t\"abcde\".includes(\"cd\") // true\n\t\"abc\".repeat(3) // \"abcabcabc\"\n\t\n\t//数组api\n\tArray.from(document.querySelectorAll('*')) // Returns a real Array\n\tArray.of(1, 2, 3) // Similar to new Array(...), but without special one-arg behavior\n\t[0, 0, 0].fill(7, 1) // [0,7,7]\n\t[1, 2, 3].find(x => x == 3) // 3\n\t[1, 2, 3].findIndex(x => x == 2) // 1\n\t[1, 2, 3, 4, 5].copyWithin(3, 0) // [1, 2, 3, 1, 2]\n\t[\"a\", \"b\", \"c\"].entries() // iterator [0, \"a\"], [1,\"b\"], [2,\"c\"]\n\t[\"a\", \"b\", \"c\"].keys() // iterator 0, 1, 2\n\t[\"a\", \"b\", \"c\"].values() // iterator \"a\", \"b\", \"c\"\n\t\n\t//对象api\n\tObject.assign(Point, { origin: new Point(0,0) })\n\n# 8、二进制和八进制字面量（Binary and Octal Literals）\n\n直接上示例：\n\n\tvar n1 = 0b111110101; //0b前缀，表示二进制字面量\n\tconsole.log(n1); //输出的时候，直接用10进制展示\n\t\n\tvar n2 = 0o12345; //0o前缀，表示八进制字面量\n\tconsole.log(n2);\n\n# 9、承诺（Promises）\n\n**Promise**是ES6中新增的异步编程库。\n\n\t//使用承诺定义一个异步任务\n\tvar p = new Promise((resolve, reject)=>{\n\t  return setTimeout(function(){\n\t    reject('ok');\n\t  }, 2000);\n\t});\n\t\n\tp.then((data)=>{\n\t  console.log(data);\n\t}, (data)=>{\n\t  console.log('error' + data);\n\t}).then(()=>{\n\t  console.log('throw err');\n\t  throw 'Error';\n\t}).catch(err => {\n\t  console.log(err);\n\t});\n\n\n# 10、参考资料\n\n1、ECMAScript 6 features [https://github.com/lukehoban/es6features](https://github.com/lukehoban/es6features)\n\n2、ECMAScript 6 入门 [http://es6.ruanyifeng.com/](http://es6.ruanyifeng.com/)"
  },
  {
    "path": "ES6入门/ES6入门系列二（特性总览上）.md",
    "content": "---\ntitle: ES6入门系列二（特性总览上）\ndate: 2017/02/21 14:47:10\n---\n\n## 0、导言\n\n最近从coffee切换到js，代码量一下子变大了不少，也多了些许陌生感。为了在JS代码中，更合理的使用ES6的新特性，特在此对ES6的特性做一个简单的总览。\n\n\n## 1、箭头函数(Arrows)\n\n使用 => 简写的函数称之为箭头函数，和C#的lambda，CoffeeScript的语法比较类似。\n\n\tvar arr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];\n\t\n\t//简单使用\n\tvar arr2 = arr.map(x => x + 1);\n\tconsole.log(arr2);\n\t//等价于\n\tarr2 = arr.map(function (x) {\n\t  return x + 1;\n\t});\n\tconsole.log(arr2);\n\t\n\t//此处必须用()包裹对象，否则语法错误\n\tvar arr3 = arr.map((x, i) => ({\n\t  idx: i,\n\t  value: x\n\t}));\n\tconsole.log(arr3);\n\t//等价于\n\tarr3 = arr.map(function (x, i) {\n\t  return {\n\t    idx: i,\n\t    value: x\n\t  };\n\t});\n\tconsole.log(arr3);\n\t\n\t//如果函数是一个语句块\n\tvar arr4 = [];\n\tarr.forEach(x => {\n\t  if (x % 3 === 0) {\n\t    arr4.push(x);\n\t  }\n\t});\n\tconsole.log(arr4);\n\t//等价于\n\tarr4.length = 0;\n\tarr.forEach(function (x) {\n\t  if (x % 3 === 0) {\n\t    arr4.push(x);\n\t  }\n\t});\n\tconsole.log(arr4);\n\t\n\t//此处this一直指向obj对象。和一般function不同，箭头函数共享词法作用域。\n\tvar obj = {\n\t  name: 'test',\n\t  foods: ['fish', 'milk'],\n\t  eat() {\n\t    this.foods.forEach(x =>\n\t      console.log(this.name + 'like eat ' + x) //注意。此地不能有分号，因为属于表达式，不是语句块。\n\t    );\n\t  }\n\t};\n\tobj.eat();\n\n## 2、类定义（classes）\n\n在ES6中，可以直接使用class关键字定义类，并可以定义构造函数，静态方法，get/set 方法，实例方法等。\n\n\t'use strict';\n\tclass Animal {\n\t  constructor(name) {\n\t    this.name = name;\n\t  }\n\t  eat() {\n\t    console.log(this.name + ' should eat food.');\n\t  }\n\t};\n\t\n\tclass Dog extends Animal {\n\t  //构造函数\n\t  constructor(age) {\n\t      super('Dog');\n\t      this.age = age;\n\t      //实例方法\n\t      this.instanceFun = function () {\n\t        console.log('Instance Function.');\n\t      }\n\t    }\n\t    //静态方法\n\t  static go() {\n\t      console.log('Dog will go.');\n\t    }\n\t    //原型方法\n\t  prototypeFunc() {\n\t    console.log('Prototype Function.');\n\t  }\n\t\n\t  //get、set\n\t  get dogName() {\n\t    return 'Name: ' + this.name;\n\t  }\n\t  set dogName(value) {\n\t    this.name = value;\n\t  }\n\t\n\t  //get\n\t  get dogAge() {\n\t    return 'Age: ' + this.age;\n\t  }\n\t}\n\t\n\tvar dog = new Dog(4);\n\tDog.go();\n\tdog.eat();\n\tdog.instanceFun();\n\tdog.prototypeFunc();\n\tconsole.log(dog.dogName);\n\tdog.dogName = 'x';\n\tconsole.log(dog.dogName);\n\tconsole.log(dog.dogAge);\n\tdog.dogAge = 5; //会失败，属性没有getter。\n\n## 3、增强的对象常量（Enhanced Object Literals）\n\n\tvar obj = {\n\t  name: 'obj',\n\t  __proto__: {\n\t    name: 'parent'\n\t  },\n\t\n\t  toString() {\n\t    // 可以通过super直接取到原型对象的属性\n\t    return super.name + ':' + this.name;\n\t  },\n\t\n\t  ['prop_' + (() => 1)()]: 1 //动态属性\n\t};\n\t\n\tconsole.log(obj.toString());\n\tconsole.log(obj.prop_1);\n\n## 4、模板字符串（Template Strings）\n\n简化了字符串的构造，拼接等。\n\n\t//基本字符串，\\n有效。\n\tvar str = `Basic string '\\n' in Javascript.`;\n\tconsole.log(str);\n\t// Basic string '\n\t// ' in Javascript.\n\t\n\t//多行字符串\n\tstr = `Multiline \n\tstrings`;\n\tconsole.log(str);\n\t// Multiline\n\t// strings\n\t\n\t//字符串插值\n\tvar name = 'Jay';\n\tstr = `Hello, ${name}`;\n\tconsole.log(str); // 'Hello, Jay.'\n\n## 5、解构（Destructuring）-- Node和Chrome中执行不成功，忽略\n\n允许使用模式匹配，来匹配数组和对象。\n\n## 6、Default + Rest + Spread -- Node和Chrome中执行不成功，忽略\n\n## 7、局部变量+常量（Let + Const）\n\n\t'use strict'; //必须启用严格模式\n\t\n\t{\n\t  let x = 1;\n\t}\n\tconsole.log(x); //Error:x is not defined.\n\t\n\tconst PI = 3.14;\n\tPI = 3.15; //Error: 无法对常量赋值\n\tconsole.log(PI); //3.14\n\n## 8、迭代器 + For..Of（Iterators + For..Of）\n\n比较类似于C#中的IEnumerable，使用for..of来访问迭代器。它不要求实现一个数组，而是使用和LINQ类似的懒加载。\n\n\t(function () {\n\t  'use strict';\n\t  let test = {\n\t    [Symbol.iterator]() {\n\t      let pre = 0,\n\t        cur = 1;\n\t      return {\n\t        next() {//此处方法名不能变\n\t          pre = cur;\n\t          cur = pre + cur;\n\t          console.log('pre = ' + pre);\n\t          console.log('cur = ' + cur);\n\t\t\t  //返回值的属性名也不能改变\n\t          return {\n\t            done: false,\n\t            value: cur\n\t          };\n\t        }\n\t      }\n\t    }\n\t  };\n\t\n\t  for (var n of test) {\n\t    if (n > 1000) {\n\t      break;\n\t    }\n\t    console.log(n);\n\t  }\n\t})();\n\n\t// 用于获取数组的键值\n\tfor (var item of[1, 3, 5, 7, 9]) {\n\t  console.log(item);\n\t}\n\n\n## 9、生成器（Generators）\n\n允许在function*()函数中使用yield关键字。\n\n\tfunction* foo(x) {\n\t  var y = 2 * (yield(x + 1));\n\t  var z = yield(y / 3);\n\t  return (x + y + z);\n\t}\n\t\n\tvar it = foo(5);\n\t\n\tconsole.log(it.next()); // { value:6, done:false }\n\tconsole.log(it.next(12)); // { value:8, done:false }\n\tconsole.log(it.next(13)); // { value:42, done:true }\n\n## 10、unicode\n\n增加了对unicode字符的支持。比如“𠮷”（这个和吉不一样哦！）\n\n\tconsole.log('𠮷'.length); //2\n\t\n\t// 正则表达式增加了u这个参数，匹配unicode字符。\n\tconsole.log(\"𠮷\".match(/./u)[0].length) // 2\n\t\n\t// new form\n\t\"\\u{20BB7}\"==\"𠮷\"==\"\\uD842\\uDFB7\"\n\t\n\t// new String ops\n\t\"𠮷\".codePointAt(0) == 0x20BB7\n\t\n\t// for-of iterates code points\n\tfor(var c of \"𠮷\") {\n\t  console.log(c);\n\t}\n\n## 11、参考资料\n\n1、ECMAScript 6 features [https://github.com/lukehoban/es6features](https://github.com/lukehoban/es6features)\n\n2、ECMAScript 6 入门 [http://es6.ruanyifeng.com/](http://es6.ruanyifeng.com/)"
  },
  {
    "path": "ES6入门/ES6入门系列四（测试题分析）.md",
    "content": "---\ntitle: ES6入门系列四（测试题分析）\ndate: 2017/02/21 14:47:10\n---\n\n## 0、导言\n\nES6中新增了不少的新特性，来点测试题热热身。具体题目来源请看：[http://perfectionkills.com/javascript-quiz-es6/](http://perfectionkills.com/javascript-quiz-es6/)。\n\n以下将一题一题来解析what和why。\n\n## 1、题目一\n\n\t(function(x, f = () => x) {\n\t  var x;\n\t  var y = x;\n\t  x = 2;\n\t  return [x, y, f()];\n\t})(1)\n\n\tA、 [2, 1, 1]\n\tB、 [2, undefined, 1]\n\tC、 [2, 1, 2]\n\tD、 [2, undefined, 2]\n\n**解析：**本题主要考察的知识点是1、参数值与函数体内定义的重名变量的优先级；2、ES6的默认参数；3、箭头函数。\n在本题中，先执行x的定义，然后函数参数x=1，接着是y = x = 1,接着再x = 2，第三个是执行f函数，箭头函数如果只是表达式，那么等价于return 表达式，由于箭头函数的作用域等于定义时的作用域，那么函数定义时x=1，所以最后的return x 等价于 return 1\n\n## 2、题目二\n\n\t(function() {\n\t  return [\n\t    (() => this.x).bind({ x: 'inner' })(),\n\t    (() => this.x)()\n\t  ]\n\t}).call({ x: 'outer' });\n\t\n\tA、 ['inner', 'outer']\n\tB、 ['outer', 'outer']\n\tC、 [undefined, undefined]\n\tD、 Error\n\n**解析：**本题主要考察的是箭头函数的作用域问题，箭头函数的作用域等于定义时的作用域，所以通过bind设置的this是无效的。那么结果就显而易见了。\n\n## 3、题目三\n\n\tlet x, { x: y = 1 } = { x }; y;\n\t\n\tA、 undefined\n\tB、 1\n\tC、 { x: 1 }\n\tD、 Error\n\n**解析：**本题主要考察的是对象赋值，先定义x，然后在赋值的时候会执行一次y=1，最后返回y的值。\n\n## 4、题目四\n\n\t(function() {\n\t  let f = this ? class g { } : class h { };\n\t  return [\n\t    typeof f,\n\t    typeof h\n\t  ];\n\t})();\n\t\n\tA、 [\"function\", \"undefined\"]\n\tB、 [\"function\", \"function\"]\n\tC、 [\"undefined\", \"undefined\"]\n\tD、 Error\n\n**解析：**本题主要考察定义函数变量时，命名函数的名称作用域问题。在定义函数变量时，函数名称只能在函数体中生效。\n\n## 5、题目五\n\n\t(typeof (new (class { class () {} })))\n\t\n\tA、 \"function\"\n\tB、 \"object\"\n\tC、 \"undefined\"\n\tD、 Error\n\n**解析：**本题主要考察对象的类型，和原型方法。该提可以分解如下：\n\n\t// 定义包含class原型方法的类。\n\tvar Test = class{\n\t  class(){}\n\t};\n\tvar test = new Test(); //定义类的实例\n\ttypeof test; //出结果\n\n## 6、题目六\n\n\ttypeof (new (class F extends (String, Array) { })).substring\n\t\n\tA、 \"function\"\n\tB、 \"object\"\n\tC、 \"undefined\"\n\tD、 Error\n\n**解析：**本题主要考察ES6中class的继承，以及表达式的返回值和undefined的类型。题目其实可以按照如下方式分解：\n\n\t//由于JS的class没有多继承的概念，所以括号被当做表达式来看\n\t(String, Array) //Array,返回最后一个值\n\t(class F extends Array); //class F继承成Array\n\t(new (class F extends Array)); //创建一个F的实例\n\t(new (class F extends (String, Array) { })).substring; //取实例的substring方法，由于没有继承String，Array没有substring方法，那么返回值为undefined\n\ttypeof (new (class F extends (String, Array) { })).substring; //对undefined取typeof\n\n## 7、题目七\n\n\t[...[...'...']].length\n\t\n\tA、 1\n\tB、 3\n\tC、 6\n\tD、 Error\n\n**解析：**本题主要考察的是扩展运算符...的作用。扩展运算符是将后面的对象转换为数组，具体用法是：\n\n\t[...<数据>] 比如 [...'abc']等价于[\"a\", \"b\", \"c\"]\n\n\n## 8、题目八\n\n\ttypeof (function* f() { yield f })().next().next()\n\t \n\tA、 \"function\"\n\tB、 \"generator\"\n\tC、 \"object\"\n\tD、 Error\n\n**解析：**本题主要考察ES6的生成器。题目可以如下分解：\n\n\tfunction* f() { yield f }; //定义一个生成器\n\tvar g = f(); //执行生成器\n\tvar temp = g.next(); //返回第一次yield的值\n\tconsole.log(temp); //测试，查看temp，其实是一个object\n\ttemp.next()；//对对象调用next方法，无效\n\n\n## 9、题目九\n\n\ttypeof (new class f() { [f]() { }, f: { } })[`${f}`]\n\t\n\tA、 \"function\"\n\tB、 \"undefined\"\n\tC、 \"object\"\n\tD、 Error\n\n**解析：**本题主要考察ES6的class，以及动态属性和模板字符串等。 实际上这个题动态属性和模板字符串都是烟雾弹，在执行new class f()的时候，就已经有语法错误了。\n\n\n## 10、题目十\n\n\ttypeof `${{Object}}`.prototype\n\t\n\tA、 \"function\"\n\tB、 \"undefined\"\n\tC、 \"object\"\n\tD、 Error\n\n**解析：**本题考察的知识点相对单一，就是模板字符串。分解如下：\n\n\tvar o = {Object},\n\t  str = `${o}`;\n\ttypeof str.prototype; \n\n\n## 11、题目十一\n\n\t((...x, xs)=>x)(1,2,3)\n\t\n\tA、 1\n\tB、 3\n\tC、 [1,2,3]\n\tD、 Error\n\n**解析：**本题主要考察的是Rest参数的用法，在ES6中，Rest参数只能放在末尾，所以该用法的错误的。\n\n## 12、题目十二\n\n\tlet arr = [ ];\n\tfor (let { x = 2, y } of [{ x: 1 }, 2, { y }]) { \n\t  arr.push(x, y);\n\t}\n\tarr;\n\t\n\tA、 [2, { x: 1 }, 2, 2, 2, { y }]\n\tB、 [{ x: 1 }, 2, { y }]\n\tC、 [1, undefined, 2, undefined, 2, undefined]\n\tD、 Error\n\n**解析：**本题看起来是考察let的作用域和of迭代的用法。实则是考察let的语法，let之后是一个参数名称。所以，语法错误\n\n## 13、题目十三\n\n\t(function() {\n\t  if (false) {\n\t    let f = { g() => 1 };\n\t  }\n\t  return typeof f;\n\t})()\n\n\tA、 \"function\"\n\tB、 \"undefined\"\n\tC、 \"object\"\n\tD、 Error\n\n**解析：**本题非常有迷惑性，看似考察的let的作用域问题，实则考察了箭头函数的语法问题。\n\n## 14、题目答案\n\n相信大家看过题目的解析，对题目答案已经了然。为了完善本文，还是在最后贴出所有题目的答案：\n\nABBAB CBDDB DDD\n\n"
  },
  {
    "path": "GoLang学习笔记/01_开始GO.md",
    "content": "---\ntitle: 01_开始GO\ndate: 2017/02/21 14:47:10\n---\n\n# 0、前言\n\nGo是啥？看[这里](https://golang.org)。简而言之，就是一门系统级的编程语言。\n\n为嘛要学/用它？\n\n1. 追个潮流，体验下不同风格的语言设计思想\n2. 跨平台部署，单文件部署\n3. 强大的功能（系统级）\n4. 对分布式的友好支持\n\n上手有啥问题？\n\n1. 不友好的语法（也有一部分人觉得语法很优雅，对我来说，这语法可算不上友好）\n2. 和大多数语言不一样的设计风格（需要慢慢消化）\n3. 没有趁手的IDE（LiteIDE, Sublime Text, VsCode这几个可以自己优化得比较好用）\n\n# 1、开干\n\n虽然比较恶心语法，但是其他优秀的点，也是比较吸引人的，再加上项目需要，那就开干。\n\n因为有其他前/后端语言的经验，就跳过常规学习过程，直接上手做项目。\n\n**注意：虽然跳过了不少步骤，但是语法还是要学习的，书还是要看的，先来一本《Go语言编程》**\n\n### 1.1、创建项目基架\n\n``Go`` 是一个有很多规范的语法，就连创建目录结构也有一定的约束。之前的版本略过，我本地安装的是 ``go1.7.3`` 版本，通过 ``go version`` 可查看。\n\n接下来就为该版本，创建一个相对较为标准的项目结构。\n\n"
  },
  {
    "path": "JS札记/ES6 Class如何管理私有数据.md",
    "content": "---\ntitle: ES6 Class如何管理私有数据\ndate: 2017/02/21 14:47:10\n---\n\n## 0、前言\n\n在ES5时代，要模拟对象的私有变量，是比较容易的，代码如下：\n\n```javascript\nfunction Person(){\n  var _age = 20; //定义一个私有变量，外部无法访问。\n  this.setAge = function(value){\n    _age = value;\n  }\n  this.getAge = function(){\n    return _age;\n  }\n}\n```\n\n在ES6中，虽然可以在 ``Class`` 的 ``constructor`` 中实现类似function的私有方法，但是实际上，ES6中并不推荐这种做法。这样极大的加重了对象的实例。\n\n那我们就来看看在ES6中有多少方法可以实现私有数据管理。\n\n## 1、在构造函数中存储私有数据\n\n该方式，和在ES5中，没有什么区别。\n\n```javascript\nclass Person{\n  constructor(){\n    var _age = 20;\n    this.setAge = value => _age = value;\n    this.getAge = _ => _age;\n  }\n}\n```\n\n该方式有一个变种，就是利用构造参数来存储，减少重新定义变量。\n\n```javascript\nclass Person{\n  constructor(age){\n    this.setAge = value => age = value;\n    this.getAge = _ => age;\n  }\n}\n```\n\n**优点：**\n1. 数据绝对安全，外部无法直接通过属性访问到。\n2. 不会与其他私有属性有任何冲突。如 \n```javascript\nlet p = new Person(); \np.age = 10; \np.getAge(); //20\n```\n\n**缺点：**\n1. 代码不怎么优雅，需要把方法设置为实例方法，才能访问到私有数据。\n2. 实例方法，比较浪费内存（每个实例都会拷贝一份）。\n\n## 2、通过命名约定来使用私有数据\n\n该方式是在ES6 Class 中，我个人比较推荐的一个方式，实现代码如下：\n\n```javascript\nclass Person{\n  constructor(){\n    this._age = 0;\n  }\n\n  setAge(value){\n    this._age = value;\n  }\n  getAge(){\n    return this._age;\n  }\n}\n```\n\n**优点：**\n1. 代码看起来非常不错，简单易懂。\n2. 能否在原型方法中访问。\n\n**缺点：**\n1. 并不安全，如果不遵守约定，直接操作_age，也是可行的，如：\n```javascript\nlet p = new Person();\np._age = 555;\np.getAge(); // 555\n```\n2. 如果在对象上设置同名属性，会覆盖掉原本是私有属性。\n\n## 3、利用WeakMap来存储私有数据\n\n该方式是利用WeakMap可以用Object来做key的特点，把this当做key来存储具体的私有属性。具体实现如下：\n\n```javascript\nlet dataStore = new WeakMap();\nclass Person{\n  constructor(){\n    dataStore.set(this, {age: 20});\n  }\n\n  setAge(value){\n    let oldObj = dataStore.get(this);\n    let newObj = Object.assign(oldObj, {age: value});\n    dataStore.set(this, newObj);\n  }\n\n  getAge(){\n    return dataStore.get(this).age;\n  }\n} \n```\n\n如何使用？\n\n```javascript\nlet p1 = new Person();\np1.getAge(); // 20\np1.setAge(25);\np1.getAge(); // 25\n```\n\n**优点：**\n1. 能够使用原型方法，内存占用小；\n2. 比命名约定属性名称安全性更高；\n2. 不会有命名冲突（允许同名实例属性）；\n\n**缺点：**\n1. 代码没有命名约定方式（方式2）优雅；\n2. 依赖外部对象；\n\n## 4、利用Symbol来生成私有属性key。\n\n该方式和命名约定方式没有本质区别，只是用 ``Symbol`` 来生成key，提高了key的安全性。具体实现代码如下：\n\n```javascript\nconst keyForAge = Symbol('age'); \nclass Person{\n  constructor(){\n    this[keyForAge] = 20;\n  }\n  \n  setAge(value){\n    this[keyForAge] = value;\n  }\n\n  getAge(){\n    return this[keyForAge];\n  }\n}\n```\n\n**优点：**\n1. 能够使用原型方法，内存占用小；\n2. 比命名约定属性名称安全性更高，但也并不安全；\n```javascript\nlet p1 = new Person();\nObject.keys(p1); // []，无法直接访问到属性名\np1[keyForAge] = 30;\np1.getAge(); // 30\nReflect.ownKeys(p1); // [Symbol(age)]，通过能方式能遍历Key\n```\n2. 不会有命名冲突（允许同名实例属性）；\n\n**缺点：**\n1. 代码没有命名约定方式（方式2）优雅；\n2. 依赖外部对象；\n3. 不是绝对安全；\n\n## 5、Other\n\n能够达到的目的的方式有很多，也没有那个有绝对优势，根据实际的需求，来选择合适的方式才是最佳的方式。\n\n**参考资料**\n\n1. [http://www.2ality.com/2016/01/private-data-classes.html](http://www.2ality.com/2016/01/private-data-classes.html)"
  },
  {
    "path": "JS札记/JS实现继承的几种方式.md",
    "content": "---\ntitle: JS实现继承的几种方式\ndate: 2017/02/21 14:47:10\n---\n\n## 前言\n\nJS作为面向对象的弱类型语言，继承也是其非常强大的特性之一。那么如何在JS中实现继承呢？让我们拭目以待。\n\n## JS继承的实现方式\n\n既然要实现继承，那么首先我们得有一个父类，代码如下：\n\n\t// 定义一个动物类\n\tfunction Animal (name) {\n\t  // 属性\n\t  this.name = name || 'Animal';\n\t  // 实例方法\n\t  this.sleep = function(){\n\t    console.log(this.name + '正在睡觉！');\n\t  }\n\t}\n\t// 原型方法\n\tAnimal.prototype.eat = function(food) {\n\t  console.log(this.name + '正在吃：' + food);\n\t};\n\n### 1、原型链继承\n\n**核心：** 将父类的实例作为子类的原型\n\n\tfunction Cat(){\t\n\t}\n\tCat.prototype = new Animal();\n\tCat.prototype.name = 'cat';\n\n\t//　Test Code\n\tvar cat = new Cat();\n\tconsole.log(cat.name);\n\tconsole.log(cat.eat('fish'));\n\tconsole.log(cat.sleep());\n\tconsole.log(cat instanceof Animal); //true \n\tconsole.log(cat instanceof Cat); //true\n\n特点：\n\n1. 非常纯粹的继承关系，实例是子类的实例，也是父类的实例\n2. 父类新增原型方法/原型属性，子类都能访问到\n3. 简单，易于实现\n\n缺点：\n\n1. 要想为子类新增属性和方法，必须要在``new Animal()``这样的语句之后执行，不能放到构造器中\n2. 无法实现多继承\n3. 来自原型对象的引用属性是所有实例共享的（详细请看附录代码： [示例1](javascript:void(0);)）\n4. 创建子类实例时，无法向父类构造函数传参\n\n推荐指数：★★（3、4两大致命缺陷）\n\n### 2、构造继承\n\n**核心：**使用父类的构造函数来增强子类实例，等于是复制父类的实例属性给子类（没用到原型）\n\n\tfunction Cat(name){\n\t  Animal.call(this);\n\t  this.name = name || 'Tom';\n\t}\n\t\n\t// Test Code\n\tvar cat = new Cat();\n\tconsole.log(cat.name);\n\tconsole.log(cat.sleep());\n\tconsole.log(cat instanceof Animal); // false\n\tconsole.log(cat instanceof Cat); // true\n\n特点：\n\n1. 解决了1中，子类实例共享父类引用属性的问题\n2. 创建子类实例时，可以向父类传递参数\n3. 可以实现多继承（call多个父类对象）\n\n\n缺点：\n\n1. 实例并不是父类的实例，只是子类的实例\n2. 只能继承父类的实例属性和方法，不能继承原型属性/方法\n3. 无法实现函数复用，每个子类都有父类实例函数的副本，影响性能\n\n\n推荐指数：★★（缺点3）\n\n### 3、实例继承\n\n**核心：**为父类实例添加新特性，作为子类实例返回\n\n\tfunction Cat(name){\n\t  var instance = new Animal();\n\t  instance.name = name || 'Tom';\n\t  return instance;\n\t}\n\t\n\t// Test Code\n\tvar cat = new Cat();\n\tconsole.log(cat.name);\n\tconsole.log(cat.sleep());\n\tconsole.log(cat instanceof Animal); // true\n\tconsole.log(cat instanceof Cat); // false\n\n特点：\n\n1. 不限制调用方式，不管是``new 子类()``还是``子类()``,返回的对象具有相同的效果\n\n\n缺点：\n\n1. 实例是父类的实例，不是子类的实例\n2. 不支持多继承\n\n推荐指数：★★\n\n### 4、拷贝继承\n\n\tfunction Cat(name){\n\t  var animal = new Animal();\n\t  for(var p in animal){\n\t    Cat.prototype[p] = animal[p];\n\t  }\n\t  Cat.prototype.name = name || 'Tom';\n\t}\n\t\n\t// Test Code\n\tvar cat = new Cat();\n\tconsole.log(cat.name);\n\tconsole.log(cat.sleep());\n\tconsole.log(cat instanceof Animal); // false\n\tconsole.log(cat instanceof Cat); // true\n\n特点：\n\n1. 支持多继承\n\n缺点：\n\n1. 效率较低，内存占用高（因为要拷贝父类的属性）\n2. 无法获取父类不可枚举的方法（不可枚举方法，不能使用for in 访问到）\n\n推荐指数：★（缺点1）\n\n### 5、组合继承\n\n**核心：**通过调用父类构造，继承父类的属性并保留传参的优点，然后通过将父类实例作为子类原型，实现函数复用\n\n\tfunction Cat(name){\n\t  Animal.call(this);\n\t  this.name = name || 'Tom';\n\t}\n\tCat.prototype = new Animal();\n\t\n\t// Test Code\n\tvar cat = new Cat();\n\tconsole.log(cat.name);\n\tconsole.log(cat.sleep());\n\tconsole.log(cat instanceof Animal); // true\n\tconsole.log(cat instanceof Cat); // true\n\n特点：\n\n1. 弥补了方式2的缺陷，可以继承实例属性/方法，也可以继承原型属性/方法\n2. 既是子类的实例，也是父类的实例\n3. 不存在引用属性共享问题\n4. 可传参\n5. 函数可复用\n\n缺点：\n\n1. 调用了两次父类构造函数，生成了两份实例（子类实例将子类原型上的那份屏蔽了）\n\n推荐指数：★★★★（仅仅多消耗了一点内存）\n\n### 6、寄生组合继承\n\n**核心：**通过寄生方式，砍掉父类的实例属性，这样，在调用两次父类的构造的时候，就不会初始化两次实例方法/属性，避免的组合继承的缺点\n\n\tfunction Cat(name){\n\t  Animal.call(this);\n\t  this.name = name || 'Tom';\n\t}\n\t(function(){\n\t  // 创建一个没有实例方法的类\n\t  var Super = function(){};\n\t  Super.prototype = Animal.prototype;\n\t  //将实例作为子类的原型\n\t  Cat.prototype = new Super();\n\t})();\n\n\t// Test Code\n\tvar cat = new Cat();\n\tconsole.log(cat.name);\n\tconsole.log(cat.sleep());\n\tconsole.log(cat instanceof Animal); // true\n\tconsole.log(cat instanceof Cat); //true\n\n特点：\n\n1. 堪称完美\n\n缺点：\n\n1. 实现较为复杂\n\n推荐指数：★★★★（实现复杂，扣掉一颗星）\n\n\n## 附录代码：\n\n示例一：\n\n\tfunction Animal (name) {\n\t  // 属性\n\t  this.name = name || 'Animal';\n\t  // 实例方法\n\t  this.sleep = function(){\n\t    console.log(this.name + '正在睡觉！');\n\t  }\n\t  //实例引用属性\n\t  this.features = [];\n\t}\n\tfunction Cat(name){\n\t}\n\tCat.prototype = new Animal();\n\t\n\tvar tom = new Cat('Tom');\n\tvar kissy = new Cat('Kissy');\n\t\n\tconsole.log(tom.name); // \"Animal\"\n\tconsole.log(kissy.name); // \"Animal\"\n\tconsole.log(tom.features); // []\n\tconsole.log(kissy.features); // []\n\t\n\ttom.name = 'Tom-New Name';\n\ttom.features.push('eat');\n\t\n\t//针对父类实例值类型成员的更改，不影响\n\tconsole.log(tom.name); // \"Tom-New Name\"\n\tconsole.log(kissy.name); // \"Animal\"\n\t//针对父类实例引用类型成员的更改，会通过影响其他子类实例\n\tconsole.log(tom.features); // ['eat']\n\tconsole.log(kissy.features); // ['eat']\n\t\n\t原因分析：\n\t\n\t关键点：属性查找过程\n\t\n\t执行tom.features.push，首先找tom对象的实例属性（找不到），\n\t那么去原型对象中找，也就是Animal的实例。发现有，那么就直接在这个对象的\n\tfeatures属性中插入值。\n\t在console.log(kissy.features); 的时候。同上，kissy实例上没有，那么去原型上找。\n\t刚好原型上有，就直接返回，但是注意，这个原型对象中features属性值已经变化了。\n"
  },
  {
    "path": "JS札记/JavaScript之毒瘤.md",
    "content": "---\ntitle: JavaScript之毒瘤\ndate: 2017/02/21 14:47:10\n---\n\n## 0、导言\n\nJavaScript中有许多难以避免的问题特性。接下来就一一揭示。\n\n## 1、全局变量\n\n在所有JavaScript的糟糕特性中，最为糟糕的就是全局变量的依赖。全局变量使得在同一个程序中运行独立的子程序变得更难。\n\n## 2、作用域\n\nJavaScript是类C的语法，但是却没有提供类C的块级作用域。\n\n## 3、自动插入分号\n\nJavaScript有一个自动修复机制，会试图通过自动插入分号来修正有缺损的程序，但是它有可能掩盖更为严重的错误。\n\n## 4、保留字\n\n有太多的单词在JavaScript中被保留，它们不能用来命名变量或者函数（在大部分执行环境下，部分关键字是可用的）。\n\n## 5、Unicode字符\n\nJavaScript设计之初，Unicode预计只会有65536个字符。实际上，到现在Unicode有多大百万个字符。这也就导致了JavaScript会认为一对字符是两个不同的字符（Unicode把一对字符视为一个单一的字符）\n\n## 6、typeof\n\n不要指望typeof返回的类型。比如null或者是检测对象，另外检测正则可能会返回function或者是object。\n\n## 7、parseInt\n\nparseInt把一个字符串转换为整数，会在遇到非数字时停止解析。另外如果第一个字符是0，还会按照8进制来取值。\n\n## 8、运算符（+）\n\n+运算符可以用于加法运算或者是字符串连接，究竟如何执行会取决于其参数类型。\n\n## 9、 浮点数\n\n二进制浮点数不能正确的处理十进制小数，因此0.1+0.2 不等于0.3。\n\n## 10、NaN\n\nNaN是一个特殊的数量值，它表示不是一个数字。也是唯一一个不等于自身的JavaScript数值。\n\n## 11、伪数组\n\nJavaScript没有真正的数组，就连Array也是通过object来模拟的，如果完全达不到真正的数组的地步。同时typeof运算符也不能辨别数组和对象。\n\n## 12、假值\n\nJavaScript中包含诸多的假值，如: 0, NaN, '', false, null, undefined\n\n## 13、hasOwnProperty\n\nhasOwnProperty方法被用做一个过滤器来避开for..in语句的隐患，但hasOwnProperty是一个普通的方法，所以是可以被重写的。\n\n## 14、对象\n\nJavaScript的对象，永远不会是真的空对象，因为可以从原形链取得成员属性。\n\n"
  },
  {
    "path": "JS札记/JavaScript之糟粕.md",
    "content": "---\ntitle: JavaScript之糟粕\ndate: 2017/02/21 14:47:10\n---\n\n## 0、导言\n\n在上篇《JavaScript之毒瘤》中，列举了一些在JavaScript中难以避免的问题特性。本篇将会展示JavaScript中有问题的特性，但我们很容易就能便面它们。通过这些简单的做法，让JavaScript称为一门更好的语言。\n\n## 1、==\n\nJavaScript有两组相等运算符。 === 和 !==，以及 == 和 !==。 === 和 !== 不会进行类型转换，一般会按照你期望的方式工作。由于JavaScript的类型转换系统相当复杂，如果要确保==和 != 不出错，就必须要牢记转换规则。另外==运算符缺乏传递性。\n\n\t//关于传递性\n\tif a === b, b === c then a === c;\n\tif a == b, b == c then a 不一定等于 c，破坏了传递性\n\n**小测验**：\n\n\t'' == '0'\n\t0 == ''\n\t0 == '0'\n\t\n\tfalse == 'false'\n\tfalse == '0'\n\t\n\tfalse == undefined\n\tfalse == null\n\tnull == undefined\n\t\n\t'\\t\\t\\n' == 0\n\n**总结**：推荐使用===和!==，尽量避免使用==和!=。\n\n## 2、with语句\n\nJavaScript提供了一个with语句，本意是用它来快速访问对象，不幸的是，它的结果可能有时不可预料。\n\n**小测验**：\n\n\twindow.a = 1;\n\tvar obj={};\n\twith(obj){\n\t  console.log(a);\n\t}\n\tobj.a = 2;\n\twith(obj){\n\t  console.log(a);\n\t}\n\n**结论**：避免使用with。\n\n## 3、eval\n\neval函数传递一个字符串给JavaScript*编译器*，并且执行结果。有问题问题呢？首先是代码难以阅读，另外需要运行编译器，导致性能低下；同时，还减弱了程序的安全性。**注：**Function构造函数，setTimeout、setInterval的字符串参数形式和eval是执行方式一致。\n\n**结论**：避免使用eval，setTimeout、setInterval的字符串参数和Function构造函数。\n\n## 4、continue\n\ncontinue语句跳到循环顶部，性能比较低下。\n\n**小测验**：\n\n\tvar counter = 10;\n\tconsole.time('t1');\n\tvar sum = 0;\n\tfor(var i = 0; i < counter; i++){\n\t  if(i % 3 !== 0){\n\t    continue;\n\t  }\n\t  sum = sum + i;\n\t}\n\tconsole.log(sum);\n\tconsole.timeEnd('t1');\n\t\n\tconsole.time('t2');\n\tvar sum = 0;\n\tfor(var i = 0; i < counter; i++){\n\t  if(i % 3 === 0){\n\t    sum = sum + i;\n\t  }\n\t}\n\t  \n\tconsole.log(sum);\n\tconsole.timeEnd('t2');\n\n**结论**：尽量优化代码，减少continue的使用。\n\n## 5、switch\n\nswitch语句中，除非明确的中断流程，否则每次条件判断后，都可以穿越到下一个case条件。这很容易造成bug。\n\n**小测验**：\n\n\tvar a = 15;\n\tswitch(a){\n\t  case a * 1 :\n\t    console.log('a*1');\n\t  case a / 1:\n\t    console.log('a/1');\n\t  default:\n\t    console.log('a');\n\t}\n\n**结论**：不要刻意的使用case条件穿越。\n\n## 6、缺少块的语句\n\nif、while、do或for可以接受代码块，也可以接受单行语句。单行语句的形式是一种带刺的玫瑰。虽然它可以节约2个字节，但它模糊了程序的结构。\n\n**小测验**：\n\n\tif(1 == '0')\n\t  console.log('1 == 0');\n\t  console.log('my god');\n\t\n\t// VS\n\tif(1 == '0'){\n\t  console.log('1 == 0');\n\t}\n\tconsole.log('my god');\n\n**结论**：避免使用模糊程序结构的单行语句。\n\n## 7、++ --\n\n递增和递减使得可以用非常简洁的风格去编码。但是它可能造成缓冲区溢出、同时往往让代码变得拥挤也不易于理解。\n\n**小测验**：\n\n\tvar a = 1;\n\ta = a++ + ++a;\n\tconsole.log(a);\n\ta = 1;\n\ta = a++ + a++;\n\tconsole.log(a);\n\t\n\n**结论**：避免使用++ --。\n\n## 8、位运算符\n\nJavaScript有着和Java相同的一套位运算符。Java中位运算符处理整数，非常快。在JavaScript中，只有双精度浮点数，所以位运算非常慢。另外，&非常容易误写为&&，使得bug容易被隐藏起来。\n\n**小测验**：\n\n\tvar counter = 10000;\n\tvar a = 5;\n\tvar sum = 0;\n\tconsole.time('t1');\n\tfor(var i = 0; i < counter; i++){\n\t  sum += a << 1;\n\t}\n\tconsole.log(sum);\n\tconsole.timeEnd('t1');\n\t\n\tsum = 0;\n\tconsole.time('t2');\n\tfor(var i = 0; i < counter; i++){\n\t  sum += a * 2;\n\t}\n\tconsole.log(sum);\n\tconsole.timeEnd('t2');\n\n**结论**：避免使用位运算符。\n\n## 9、function语句 与 function表达式\n\nJavaScript中既有function语句，也有function表达式，这令人困惑，似乎看起来也差不多。function语句在解析时会产生变量提升，放宽了函数必须先申明后使用的的要求。同时，JS在if语句中使用function语句也是被禁止的，但实际上大多数浏览器允许在if中使用function语句，这有可能会导致兼容性问题。\n\n由于一个语句不能以一个函数表达式开头，如下如下写法，可以改写为另外一种形式。\n\t\n\tfunction (){}(); //Error \n\t\n\t(function(){}()); // Right\n\n**小测验**：\n\n\t// function语句\n\tfunction fun(){}\n\t\n\t// function表达式\n\tvar fun = function(){};\n\n**结论**：合理使用function语句和function表达式。\n\n## 10、类型的包装对象\n\nJavaScript有一种类型的包装对象，如 new Number(1);这很容易令人困惑。\n\n**小测验**:\n\n\tvar num1 = new Number(1);\n\tvar num2 = 1;\n\tconsole.log(typeof num1);\n\tconsole.log(num1 === num2);\n\n**结论**：避免使用包装对象，如new Boolean(),new String(),new Number()等\n\n## 11、new\n\nnew运算符创建一个继承于其原型的新对象，并将新创建的对象绑定给this。但是，如果忘记使用new，那么就得到一个普通的函数调用，对象属性也会被绑定到全局对象上。这不会导致什么编译警告，也没有运行警告。\n\n根据惯例，需要用new的函数，以首字母大写命名。这能部分程度上便于我们发现错误。\n\n**小测验**：\n\n\tfunction Person(){\n\t  this.name = 'Default';\n\t  this.sex = undefiend;\n\t}\n\t\n\tPerson();\n\tnew Person();\n\t\n\t//更好的实现\n\tfunction Person(){\n\t  if(this === window){\n\t    return new Person();\n\t  }\n\t  this.name = 'Default';\n\t  this.sex = undefiend;\n\t}\n\n\n**结论**：合理的避免使用new。另外可以先判断this，再做对应处理。\n\n## 12、void\n\n大部分语言中，void是一种类型，在Js中，void是一种运算符，接收一个运算数，并返回undefined\n\n**小测验**：\n\tvoid 0\n\tvoid true\n\n**结论**：有限的使用void\n\n\n\n\n\n\n"
  },
  {
    "path": "JS札记/JavaScript的深拷贝的实现.md",
    "content": "---\ntitle: JavaScript的深拷贝的实现\ndate: 2017/02/21 14:47:10\n---\n\n## JavaScript的数据类型\n\n### 简单数据类型\n\n1. string\n2. number\n3. boolean\n4. function\n5. null\n6. undefined\n\n### 复杂数据类型\n\n1. String\n2. Number\n3. Boolean\n3. Function\n4. Date\n5. Array\n6. RegExp \n7. Object \n\n## 各种类型的深复制方式：\n\n先来看看简单类型的复制方式：\n\n\t//string\n\tvar s1 = 'abc';\n\tvar s2 = s1;\n\ts2 = 'ccc';\n\tconsole.log(s1);\n\t\n\t//number\n\tvar n1 = 12.1;\n\tvar n2 = n1;\n\tn2 = 7410;\n\tconsole.log(n1);\n\t\n\t//boolean\n\tvar b1 = true;\n\tvar b2 = b1;\n\tb2 = false;\n\tconsole.log(b1);\n\n\t//null\n\tvar nu1 = null;\n\tvar nu2 = nu1;\n\tnu2 = 'abc';\n\tconsole.log(nu1);\n\t\n\t//undefined\n\tvar u1 = undefined;\n\tvar u2 = u1;\n\tu2 = 'abc';\n\tconsole.log(u1);\n\n从以上的代码可以看出，简单类型，只需要直接赋值就是深复制了。但是也有一个例外，那就是function。\n\n接着来看看String、Number、Boolean、Date的深复制：\n\t\n\t//String\n\tvar s1 = new String('s1');\n\tvar s2 = new String(s1);\n\tconsole.log(s2);\n\t\n\t//Number\n\tvar n1 = new Number('1');\n\tvar n2 = new Number(n1);\n\tconsole.log(n2);\n\t\n\t//Boolean\n\tvar b1 = new Boolean(1);\n\tvar b2 = new Boolean(b1);\n\tconsole.log(b2);\n\n\t//Date\n\tvar d1 = new Date();\n\tvar d2 = new Date(d1);\n\tconsole.log(d2);\n\n除以上的做法之外，还需要对实例属性进行拷贝。那么剩下的Function、function、RegExp和Array还有Object又该怎么拷贝呢？这几个比较特殊，我们一个一个来：\n\n对于Function和function的深拷贝，我们可以按照如下的方式来做：\n\n\tvar f1 = new Function('a', 'console.log(\"f1\" + a);');\n\tvar f2 = function(b){console.log('f2' + b);};\n\t\n\t//通过toString获取源代码(有浏览器兼容问题)\n\tvar code = f1.toString();\n\t//利用eval进行复制\n\tvar f1_copy = (function(functionCode){\n\t  eval('var f = ' + functionCode);\n\t  return f;\n\t})(code);\n\t\n\tf1_copy('abc');\n\t\n\t//当然f2也可以用同样的方式来复制。\n\n接着，我们来看下RegExp，可以同样同时eval来执行拷贝，也可以使用如下方式：\n\n\tvar reg1 = /abc/g;\n\tvar reg2 = new RegExp('abc', 'gmi');\n\t\n\tvar reg1_copy = (function(reg){\n\t  var pattern = reg.valueOf();\n\t  var flags = (pattern.global ? 'g' : '') + \n\t    (pattern.ignorecase ? 'i' : '') + (pattern.multiline ? 'm' : '');\n\t  return new RegExp(pattern.source, flags);\n\t})(reg1);\n\n最后，我们来说一说Array的复制，有的人可以说，直接用slice复制一份出来就是了，那我们来看看，是否真的达到效果的呢？\n\n\tvar o = {name: 'Jay'};\n\tvar arr1 = [o, '22', 1];\n\tvar arr2 = arr1.slice(0);\n\tarr2[0].name = 'Arr2';\n\tconsole.log(arr1[0].name);\n\n很简短的代码，直接就把slice抛弃了，slice只能保证Array是新的，并不意味着内部的元素是深拷贝的，那么如何做呢？就是遍历元素，对每个元素进行深拷贝了。代码如下：\n\n\tvar o = {name: 'Jay'};\n\tvar arr1 = [o, '22', 1];\n\t\n\tvar arr2 = [];\n\tfor(var i = 0, len = arr1.length; i < len; i++){\n\t  //注意，deepClone还未实现\n\t  arr2.push(deepClone(arr1[i]));\n\t}\n\n以上对针对不同的类型，特殊的代码，那么如何来拷贝实例属性呢？代码如下：\n\n\tvar o = {p1: '1', p2: 2, p3: function(){}};\n\t\n\tvar copy = {};\n\tfor(var p in o){\n\t  //注意deepClone还未实现\n\t  copy[p] = deepClone(o[p]);\n\t}\n\n**注意：针对复杂类型，还需要同时copy.constructor = source.constructor来保证构造函数一致。**\n\n## 最终的深复制代码\n\n通过以上的分析与代码示例，那么我们最终的代码又是怎样的呢？详细代码如下：\n\n\t//自调用函数，防御性编程\n\t;\n\t(function (window) {\n\t  'use strict';\n\t\n\t  function getCustomType(obj) {\n\t    var type = typeof obj,\n\t      resultType = 'object';\n\t    //简单类型\n\t    if (type !== 'object' || obj === null) {\n\t      resultType = 'simple';\n\t    } else if (obj instanceof String || obj instanceof Number || obj instanceof Boolean || obj instanceof Date) {\n\t      resultType = 'complex';\n\t    } else if (obj instanceof Function) {\n\t      resultType = 'function';\n\t    } else if (obj instanceof RegExp) {\n\t      resultType = 'regexp';\n\t    } else if (obj instanceof Array) {\n\t      resultType = 'array';\n\t    }\n\t    return resultType;\n\t  }\n\t\n\t  function cloneProperties(dest, source) {\n\t    dest.constructor = source.constructor;\n\t    for (var p in source) {\n\t      dest[p] = deepClone(source[p]);\n\t    }\n\t    return dest;\n\t  }\n\t\n\t  function cloneSimple(obj) {\n\t    return obj;\n\t  }\n\t\n\t  function cloneComplex(obj) {\n\t    var result = new obj.constructor(obj);\n\t    return cloneProperties(result);\n\t  }\n\t\n\t  function cloneFunction(obj) {\n\t    var funCopy = (function (f) {\n\t      eval('var abcdefg_$$$$ = ' + obj.toString());\n\t      return abcdefg_$$$$;\n\t    })(obj);\n\t    return cloneProperties(funCopy);\n\t  }\n\t\n\t  function cloneRegExp(obj) {\n\t    var pattern = obj.valueOf();\n\t    var flags = (pattern.global ? 'g' : '') +\n\t      (pattern.ignorecase ? 'i' : '') + (pattern.multiline ? 'm' : '');\n\t    var reg = new RegExp(pattern.source, flags);\n\t    return cloneProperties(reg);\n\t  }\n\t\n\t  function cloneArray(obj) {\n\t    var resultArr = [];\n\t    for (var i = 0, len = obj.length; i < len; i++) {\n\t      resultArr.push(deepClone(obj[i]));\n\t    }\n\t    for (var p in obj) {\n\t      if (typeof p === 'number' && p < len) {\n\t        continue;\n\t      }\n\t      resultArr[p] = deepClone(obj[p]);\n\t    }\n\t    return resultArr;\n\t  }\n\t\n\t  function cloneObject(obj) {\n\t    var result = {};\n\t    result.constructor = obj.constructor;\n\t    for (var p in obj) {\n\t      result[p] = deepClone(obj[p]);\n\t    }\n\t    return result;\n\t  }\n\t\n\t  function deepClone(obj) {\n\t    var f = undefined;\n\t    switch (getCustomType(obj)) {\n\t    case 'simple':\n\t      f = cloneSimple;\n\t      break;\n\t    case 'complex':\n\t      f = cloneComplex;\n\t      break;\n\t    case 'function':\n\t      f = cloneFunction;\n\t      break;\n\t    case 'regexp':\n\t      f = cloneRegExp;\n\t      break;\n\t    case 'array':\n\t      f = cloneArray;\n\t      break;\n\t    case 'object':\n\t      f = cloneObject;\n\t      break;\n\t    }\n\t    return f.call(undefined, obj);\n\t  }\n\t\n\t  //挂载到window对象上\n\t  window.deepClone = deepClone;\n\t})(window);\n"
  },
  {
    "path": "JS札记/[20141121]JavaScript之Array常用功能汇总.md",
    "content": "---\ntitle: JavaScript之Array常用功能汇总\ndate: 2014/11/21 00:00:00\n---\n\n**导语：**在JavaScript中，Array是一个使用比较频繁的对象，那么它到底有哪些常用的方法呢？\n\n首先，我们先看一下Array对象的类型：\n\n\ttypeof Array // 'function'\n\tArray instanceof Object // true\n\n从上可以看出，Array本质是一个function，同样派生自Object，定义如下：\n\n\tfunction Array(args) {}\n\n###接下来，我们来看Array自身的方法：\n\n#### #1、concat()\n\n定义：原型方法，连接两个或更多的数组，并返回结果（新数组）。\n\n\tArray.prototype.concat = function(items) {};\n\n示例：\n\n\tvar arr1 = [1, 2];\n\tvar arr2 = arr1.concat([3, 4]);\n\tvar arr3 = arr2.concat([5, 6], [7, 8] ,10, {});\n\tconsole.log(arr1); // [1, 2]\n\tconsole.log(arr2); // [1, 2, 3, 4]\n\tconsole.log(arr3); // [1, 2, 3, 4, 5, 6, 7, 8,  10, Object]\n\n**注意：**concat不仅可以连接单个对象，也可以连接多个对象，同时如果是参数为数组，那么会将数组元素拆分并连接，如果是对象，则直接将对象连接。<span style=\"color:red;\"><b>该方法不会改变原始数组</b></span>\n\n#### #2、join()\n\n定义：原型方法，把数组的所有元素放入一个字符串。元素通过指定的分隔符进行分隔。\n\n\tArray.prototype.join = function(separator) {};\n\n示例：\n\n\tvar arr = [1, 2, 3];\n\tconsole.log(arr.join('|')); // '1|2|3'\n\tconsole.log(arr.join(''));  // '123'\n\tconsole.log(arr.join('---'));  // '1---2---3'\n\n**注意：**太常用了，没什么可注意的~\n\n#### #3、pop()\n\n定义：原型方法，删除并返回数组的最后一个元素。\n\n\tArray.prototype.pop = function() {};\n\n示例：\n\n\tvar arr1 = [1, 2, 3, 4];\n\tvar lastOne = arr1.pop();\n\tconsole.log(lastOne);  // 4\n\tconsole.log(arr1);     // [1, 2, 3]\n\n**注意：**该方法无参数，有返回值，返回数组最后一个元素。<span style=\"color:red;\"><b>该方法会改变原始数组</b></span>\n\n#### #4、push()\n\n定义：原型方法，向数组的末尾添加一个或更多元素，并返回新的长度。\n\n\tArray.prototype.push = function(items) {};\n\n示例：\n\n\tvar arr1 = [1, 2];\n\tvar len = arr1.push(3);\n\tvar arr2 = arr1.push(4, 5);\n\tconsole.log(len);\n\tconsole.log(arr1);\n\tconsole.log(arr2);\n\n**注意：**该方法的返回值会返回数组的新长度。<span style=\"color:red;\"><b>该方法会改变原始数组</b></span>\n\n#### #5、reverse()\n\n定义：原型方法，颠倒数组中元素的顺序。\n\n\tArray.prototype.reverse = function() {};\n\n示例：\n\n\tvar arr1 = [1, 2, 3, 4, 5];\n\tvar res = arr1.reverse();\n\tconsole.log(res);\n\tconsole.log(arr1);\n\n**注意：**该方法的返回值为自身（翻转后的值），<span style=\"color:red;\"><b>该方法会改变原始数组</b></span>\n\n#### 6、shift()\n\n定义：原型方法，删除并返回数组的第一个元素。\n\n\tArray.prototype.shift = function() {};\n\n示例：\n\n\tvar arr1 = [1, 2, 3];\n\tvar res = arr1.shift();\n\tconsole.log(res);\n\tconsole.log(arr1);\n\n**注意：**该方法返回数组第一个元素，和pop()方法对应（返回并删除最后一个元素）。<span style=\"color:red;\"><b>该方法会改变原始数组</b></span>\n\n#### #7、slice()\n\n定义：原型方法，从某个已有的数组返回选定的元素。\n\n\tArray.prototype.slice = function(start,end) {};\n\n示例：\n\n\tvar arr = [1, 2, 3, 4, 5, 6, 7, 8, 9];\n\tvar res1 = arr.slice(0, 3);\n\tvar res2 = arr.slice(0, 100);\n\tvar res3 = arr.slice(-1,-6);\n\tvar res4 = arr.slice(-6, -1);\n\tconsole.log(res1);\n\tconsole.log(res2);\n\tconsole.log(res3);\n\tconsole.log(res4);\n\tconsole.log(arr)\n\n**注意：**该方法支持逆向索引，同时索引采取区间左闭右开的原则。<span style=\"color:red;\"><b>该方法不会改变原始数组</b></span>\n\n#### #8、sort()\n\n定义：原型方法，对数组的元素进行排序。\n\n\tArray.prototype.sort = function(compareFn) {};\n\n示例：\n\t\n\tvar arr = [1, 5, 2, 3, 4, 7, 8, 6, 9];\n\tvar res1 = arr.sort(); //如果是数字，默认从小到大排序\n\tconsole.log(res1);\n\tvar arr2 = ['a', 'c', 'b'];\n\tvar res2 = arr2.sort();//如果是字符，按照字符顺序（ASCII，字符串同）排序\n\tconsole.log(res2);\n\t//遇到复杂数据，经过测试是按照数组<正则<数字<对象<字符串<函数 这个顺序\n\tvar arr3 = [{name:'name'}, 134, 'aaa', function(){}, [], /a/];\n\tvar res3 = arr3.sort();\n\tconsole.log(arr3);\n\t\n\t//可以通过自定义规则实现复杂的排序\n\tvar res4 = arr.sort(function(a1, a2){\n\t\tif(a1 === a2){ // 两者相等，那么就算想等\n\t\t\treturn 0;\n\t\t}\n\t\tif(a1%3 === 0){ //如果a1被3整除，那么a1小\n\t\t\treturn -1;\n\t\t}\n\t\tif(a2%3 === 0){ //如果a2被3整除，那么a2小\n\t\t\treturn 1;\n\t\t}\n\t\treturn a2%3-a2%3; //不满足以上条件，那么根据余数比大小，余数小的元素小\n\t})\n\tconsole.log(res4);\n\n**注意：**该方法返回自身（排序后数组）。可通过function(a1, a2){}实现非常复杂的排序规则。<span style=\"color:red;\"><b>该方法会改变原始数组</b></span>\n\n#### #9、splice()\n\n定义：原型方法，删除元素，并向数组添加新元素。（该方法相等较复杂，悠着点用）\n\n\tArray.prototype.splice = function(start,deleteCount,items) {};\n\n示例：\n\n\tvar arr1 = [1, 2, 3, 4, 5, 6, 7, 8, 9];\n\tvar res1 = arr1.splice(0, 3, 'new1', 'new2');\n\tconsole.log(res1);  // [1, 2, 3] \n\tconsole.log(arr1);  // ['new1', 'new2', 4, 5, 6, 7, 8, 9] \n\n\tarr1 = [1, 2, 3, 4, 5, 6, 7, 8, 9];\n\tres1 = arr1.splice(-6, 3, 'new1', 'new2');\n\tconsole.log(res1);  // [4, 5, 6]\n\tconsole.log(arr1);  // [1, 2, 3, 'new1', 'new2', 7, 8, 9]\n\n**注意：**splice()函数支持倒叙索引，同时第二个参数是长度（不是下标），新插入的数据会插入在start下标位置。返回值为删除的元素数组。<span style=\"color:red;\"><b>该方法会改变原始数组</b></span>\n\n\n#### #10、unshift()\n\n定义：原型方法，向数组的开头添加一个或更多元素，并返回新的长度。\n\n\tArray.prototype.unshift = function(items) {};\n\n示例：\n\n\tvar arr1= [1, 2, 3];\n\tvar res1 = arr1.unshift('new1', 'new2');\n\tconsole.log(res1); // 5\n\tconsole.log(arr1); // [\"new1\", \"new2\", 1, 2, 3] \n\n**注意：**该方法和push相对(在末尾添加元素，返回新长度)，该方法的返回值是新数组长度。<span style=\"color:red;\"><b>该方法会改变原始数组</b></span>\n\n### 我们还可以为Array添加更多的常用功能，比如：\n\t\n\tArray.prototype.where = function(predicateFn){\n\t    var parameterIsFn = typeof predicateFn === 'function'\n\t    var result = [];\n\t    for(var i = 0, len = this.length; i < len; i++){\n\t        if(!parameterIsFn || predicateFn(this[i])){\n\t            result.push(this[i]);\n\t        }\n\t    }\n\t    return result;\n\t};\n\t\n\tvar arr = ['new1', 'new2', 1, 2, 3];\n\tvar res = arr.where(function(item){\n\t    return typeof item === 'number';\n\t});\n\tconsole.log(res);\n\t\n\n\n"
  },
  {
    "path": "JS札记/那些不常见的JavaScript题目（上）.md",
    "content": "---\ntitle: 那些不常见的JavaScript题目（上）\ndate: 2017/02/21 14:47:10\n---\n\n## 0、导言\n\nJavaScript超乎寻常的灵活性，让JavaScript可以有很多特殊的用法，让我们来领略一下它们的风采吧。\n\n此为上篇，下篇请查阅：[下篇](那些不常见的JavaScript题目（下）.md)\n\n## 1、那些不常见（好玩）的题目\n\n---\n### 1.1、\n\n```javascript\n[\"1\", \"2\", \"3\"].map(parseInt)\n```\n**解析：** 该题考察数组的 ``Array.map()`` 函数和 ``parseInt()``的用法。\n\n``Array.map()``，接受两个两个，第一个是回调函数 ``function(currentValue, index, array)``，第二个是可选参数 ``thisArg``，用来指定回调函数的 ``this`` 对象，默认是 ``window``。\n\n```javascript\n['1', '2', '3'].map(function(currentValue, index, array){\n  console.log(currentValue, index, array, this);\n  return '1';\n}, {});\n```\n通过上面的代码可以验证 ``Array.map()`` 的用法，想更详细了解，请参考 [MDN Array.map](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map)。\n\n接着看 ``parseInt``，``parseInt`` 默认接受两个函数，第一个要是要转化的元素，第二个是进制，默认2~36，0的话，就当成10进制处理。如果超过则会返回 ``NaN``。把它作为map的回调函数时，相当于给parseInt传递了三个参数。多余的参数对结果不影响，所以问题可以变形为：``[parseInt('1', 0), parseInt('2', 2), parseInt('3',2)]``，所以就能得出答案了。\n\n**答案：** ``[1, NaN, NaN]``\n\n---\n### 1.2、\n\n```javascript\n[typeof null, null instanceof Object]\n```\n\n**解析：** 该题考察了对``null``的认识。 ``null`` 本身是一个类型（并不是Object的实例），但对它进行 ``typeof`` 会返回 ``object``。\n\n**答案：** ``['object', false]``\n\n---\n### 1.3、\n\n```javascript\n[ [3,2,1].reduce(Math.pow), [].reduce(Math.pow)] ]\n```\n\n**解析：**此题和1.1比较类似，考察 ``Array.reduce()`` 和 ``Math.pow()`` 方法的使用。\n\n``Array.reduce()`` 用于将数组的多个值根据指定的处理函数，合并为一个值。它的回调函数定义为 ``function(previousValue, currentValue, currentIndex, array){}``，其中：\n\n1. previousValue // 上一次的运算结果（注意：该变量的初始值为数组的第一个元素）\n2. currentValue // 当前数组元素\n3. currentIndex // 当前数组索引\n4. array //数组本身\n\n另外，需要注意，由于默认 ``previousValue`` 的初始值是第一个数组元素，那么实际的回调函数调用次数为：``arr.length - 1``。如果数组元素小于1，就会因为无法提供初始值，而导致方法异常（报错）。想了解更多，参考：[MDN Array.reduce()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/reduce)。\n\n``Math.pow()`` 用于计算指定值的次方，接受两个参数，前者为基数，后者为几次方，如：``Math.pow(2, 3) // 8``。\n\n**答案：** ``[9, error]``\n\n---\n### 1.4、\n\n```javascript\nvar val = 'smtg';\nconsole.log('Value is ' + (val === 'smtg') ? 'Something' : 'Nothing');\n```\n\n**解析：** 此题比较简单，考察+号运算符和?号运算符的优先级问题，+优先级大于?。\n\n**答案：** ``'Something'``\n\n---\n\n### 1.5、\n\n```javascript\nvar name = 'World!';\n(function () {\n  if (typeof name === 'undefined') {\n    var name = 'Jack';\n    console.log('Goodbye ' + name);\n  } else {\n    console.log('Hello ' + name);\n  }\n})();\n```\n\n**解析：** 此题考察函数作用域、变量提前、全局变量和局部变量相关的知识点。JS默认是函数作用域，并且函数申明，变量申明会提前到函数体开头。所以题目代码等价于：\n\n```javascript\nvar name = 'World!';\n(function () {\n  var name;\n  if (typeof name === 'undefined') {\n    name = 'Jack';\n    console.log('Goodbye ' + name);\n  } else {\n    console.log('Hello ' + name);\n  }\n})();\n```\n\n再结合局部变量，会隐藏全局变量，所以答案就出来。\n\n\n**答案：** ``Goodbye Jack``\n\n---\n\n### 1.6、\n\n```javascript\nvar END = Math.pow(2, 53);\nvar START = END - 100;\nvar count = 0;\nfor (var i = START; i <= END; i++) {\n  count++;\n}\nconsole.log(count);\n```\n\n**解析：** 此题考察的知识比较偏门，考察了JS中**能正确计算且不失精度的最大整数**，当达到这个数之后，++（自加）操作将不会产生变化了。所以<=将永远满足条件。\n\n**答案：** ``死循环``\n\n---\n\n### 1.7、\n\n```javascript\nvar ary = [0,1,2];\nary[10] = 10;\nary.filter(function(x) { return x === undefined;});\n```\n\n**解析：** 此题考察了数组自动补齐元素和 ``Array.filter()`` 的用法。\n\n数组自动补齐的元素为 ``undefined``，示例如下：\n\n```javascript\nvar arr = [];\narr[9] = 1;\nconsole.log(arr); // [undefined * 9, 1]\n```\n\n``Array.filter()`` 用于筛选满足条件的数组元素，接受回调函数 ``function(currentValue, currentIndex, array){}``。但是一定要注意：**它会忽略自动填充的undefined的元素**\n\n**再次警告：是仅仅忽略自动填充的undefined元素，如果不是自动填充的undefined，是不会被忽略的。**\n\n```javascript\nvar arr = [1, 'str', undefined, null];\narr.filter(function(item){console.log(item)}); //执行四次，没有忽略\n\nvar arr = [];\narr[9] = 1;\narr.filter(function(item){console.log(item)}); //执行1次，忽略了自动填充的9个元素\n```\n\n**为什么有这种现象呢？**\n\n因为filter在判断的时候，是通过key来进行的，对比一下一下两种方式的key：\n\n```javascript\nvar arr = [1, 'str', undefined, null];\nObject.keys(arr); //一共有四个。\n\nvar arr = [];\narr[9] = 1;\nObject.keys(arr); //一共只有一个。\n```\n\n**引申：该判断逻辑也适合大多数数组方法，如reduce，map等等。**\n\n**答案：** ``[]``\n\n---\n\n### 1.8、\n\n```javascript\nvar two   = 0.2\nvar one   = 0.1\nvar eight = 0.8\nvar six   = 0.6\n[two - one == one, eight - six == two]\n```\n\n**解析：** 该题考察了JS中的浮点数运算有误差的知识点。这个到底规则是什么，I don't know.\n\n**答案：** ``[true, false]``\n\n---\n\n### 1.9、\n\n```javascript\nfunction showCase(value) {\n  switch(value) {\n    case 'A':\n      console.log('Case A');\n      break;\n    case 'B':\n      console.log('Case B');\n      break;\n    case undefined:\n      console.log('undefined');\n      break;\n    default:\n      console.log('Do not know!');\n  }\n}\nshowCase(new String('A'));\n```\n\n**解析：** 此题考察 ``new String('A')`` 产生的结果为 ``object``，另外switch判断分支是使用 ``===``，所以只能到default分支。\n\n**答案：** ``'Do not know!'``\n\n---\n\n### 1.10、\n\n```javascript\nfunction showCase2(value) {\n  switch(value) {\n  case 'A':\n    console.log('Case A');\n    break;\n  case 'B':\n    console.log('Case B');\n    break;\n  case undefined:\n    console.log('undefined');\n    break;\n  default:\n    console.log('Do not know!');\n  }\n}\nshowCase(String('A'));\n```\n\n**解析：** 此题和1.9考察点类似，不过不适用new的 ``String('A')``，返回的是字符串的 ``'A'``。\n\n**注意，以此类推，Number(1), 也返回数字 1 。**\n\n**答案：** ``'Case A'``\n\n---\n\n### 1.11、\n\n```javascript\nfunction isOdd(num) {\n  return num % 2 == 1;\n}\nfunction isEven(num) {\n  return num % 2 == 0;\n}\nfunction isSane(num) {\n  return isEven(num) || isOdd(num);\n}\nvar values = [7, 4, '13', -9, Infinity];\nvalues.map(isSane);\n```\n\n**解析：** 此题没有什么难点，只需要记得 ``Infinity % 2 //NaN``就可以了。其他直接根据传入的值，运算即可。\n\n**答案：** ``[true, true, true, false, false]``\n\n---\n\n### 1.12、\n\n```javascript\nparseInt(3, 8)\nparseInt(3, 2)\nparseInt(3, 0)    \n```\n**解析：** 此题的考察点，在1.1就考察过了。\n\n**答案：** ``3, NaN, 3``\n\n---\n\n### 1.13、\n\n```javascript\nArray.isArray(Array.prototype)\n```\n\n**解析：** 此题考察Array的原型。\n\n```javascript\nArray.prototype // []\n```\n\n**答案：** ``true``\n\n---\n\n### 1.14、\n\n```javascript\nvar a = [0];\nif ([0]) { \n  console.log(a == true);\n} else { \n  console.log(\"wut\");\n}\n```\n\n**解析：**  死记硬背吧。[参考图](https://dorey.github.io/JavaScript-Equality-Table/)\n\n**答案：** ``false``\n\n---\n\n### 1.15、\n\n```javascript\n[]==[]\n```\n\n**解析：** 此题如上，也可以理解没有类型转换，引用不同。\n\n**答案：** ``false``\n\n---\n\n### 1.16、\n\n```javascript\n'5' + 3  \n'5' - 3\n```\n\n**解析：** 此题考察类型转换，和 + 号的作用。当有字符串操作数时，+ 表示字符串连接。\n\n**答案：** ``'53', 2``\n\n---\n\n### 1.17、\n\n```javascript\n1 + - + + + - + 1 \n```\n\n**解析：** 此题，个人根据结果归纳了一套规则：\n\n1. 首先，忽略所有的+号， 得到： ``1 - - 1``\n2. 然后 - - 得正，成为 + 号，得到：``1 1``\n3. 把最后的操作数相加，即为结果\n\n测试代码：\n\n```javascript\n5 + 1 - 1 + 2 + 3 + 1 // 11\n5 - 1 - 1 - 1 - 1 - 10 - 5 - 20 // -34\n1 - - - - - - 1 // 2\n```\n\n**答案：** ``2``\n\n---\n\n### 1.18、\n\n```javascript\nvar ary = Array(3);\nary[0]=2\nary.map(function(elem) { return '1'; });\n```\n\n**解析：** 此题的考察点在1.7就考察过了。但是需要注意，map函数，并不改变结果数组的长度。\n\n**答案：** ``['1', undefined*2]``\n\n---\n\n### 1.19、\n\n```javascript\nfunction sidEffecting(ary) { \n  ary[0] = ary[2];\n}\nfunction bar(a,b,c) { \n  c = 10\n  sidEffecting(arguments);\n  return a + b + c;\n}\nbar(1,1,1)\n```\n\n**解析：** 此题考察改变 ``arguments``的属性值，会不会影响该对象。由于 ``arguments`` 是个对象，实际上是会影响的。**注意：使用 ``use strict`` 可以避免此种情况。**\n\n**答案：** ``21``\n\n**注意：当使用ES6语法，参数有rest parameters的时候，结果就不在一样了。**\n\n```javascript\nfunction sidEffecting(ary) {\n  ary[0] = ary[2];\n}\nfunction bar(a, b, c=3) {\n  c = 10;\n  sidEffecting(arguments);\n  return a + b + c;\n}\nbar(1,1,1); // 12\n```\n\n---\n\n### 1.20、\n\n```javascript\nvar a = 111111111111111110000,\nb = 1111;\na + b;\n```\n\n**解析：** 当数值超过**JS能正确计算且不失精度的最大整数**时，会产生缺少精度问题。导致结果不太可预料。\n\n基本上，超过16位数的整数都有这个问题了。很多时候，超过16位之后的数字会被补0。如下：\n\n```javascript\n// 一共20个2，超过16位之后的，将会为0。\n22222222222222222222 // 22222222222222220000\n\n98765432109876543210 // 98765432109876540000\n\n//当从1开始时，超过17位才为0。\n12345678901234567890 // 12345678901234567000\n\n```\n\n**答案：** ``111111111111111110000``"
  },
  {
    "path": "JS札记/那些不常见的JavaScript题目（下）.md",
    "content": "---\ntitle: 那些不常见的JavaScript题目（下）\ndate: 2017/02/21 14:47:10\n---\n\n## 0、导言\n\nJavaScript超乎寻常的灵活性，让JavaScript可以有很多特殊的用法，让我们来领略一下它们的风采吧。\n\n此为下篇，上篇请查阅：[上篇](那些不常见的JavaScript题目（上）.md)\n\n## 1、那些不常见（好玩）的题目（下）\n\n---\n### 1.21、\n\n```javascript\nNumber.MIN_VALUE > 0\n```\n**解析：** 此题考察数字的最小值，它的最小值是大于0的浮点数。\n\n**答案：** ``true``\n\n---\n### 1.22、\n\n```javascript\n[1 < 2 < 3, 3 < 2 < 1]\n```\n\n**解析：** 此题考察运算顺序。直接从左往右计算，即可。等价于：\n\n```javascript\n[true < 3, false < 1]\n=>\n[1 < 3, 0 < 1]\n```\n\n**答案：** ``[true, true]``\n\n---\n### 1.23、\n\n```javascript\n2 == [[[2]]]\n```\n\n**解析：** 此题考察类型转化，全部调用toString(),转换为了``'2'``\n\n**答案：** ``true``\n\n---\n### 1.24、\n\n```javascript\n3.toString();\n3..toString();\n3...toString();\n```\n\n**解析：** 此题考察.的结合性问题，对于到底属于数字还是函数调用呢，其实只能是数字。三个点是什么语法？\n\n**注意：这也导致了整数字面量无法直接调用toString()**\n\n**答案：** ``error, '3', error``\n\n---\n\n### 1.25、\n\n```javascript\n(function(){\n  var x = y = 1;\n})();\nconsole.log(y);\nconsole.log(x);\n```\n\n**解析：** 此题考察作用域问题和var定义变量问题。当没有用var定义变量时，变量会成为全局变量。\n\n**答案：** ``1, error``\n\n---\n\n### 1.26、\n\n```javascript\nvar a = /123/, b = /123/;\na == b\na === b\n```\n\n**解析：** 此题考察正则表达式比较，即使字面量相等，它们也不相等。\n\n**答案：** ``false, false``\n\n---\n\n### 1.27、\n\n```javascript\nvar a = [1, 2, 3],\n    b = [1, 2, 3],\n    c = [1, 2, 4];\na ==  b;\na === b;\na > c;\na < c;\n```\n\n**解析：** 此题同样考察对象比较。当比较相等时，引用不同，所以皆不等。当比较大小时，会按照数组元素，依次比较字典序。\n\n**答案：** ``false, false, false, true``\n\n---\n\n### 1.28、\n\n```javascript\nvar a = {}, b = Object.prototype;\n[a.prototype === b, Object.getPrototypeOf(a) === b]\n```\n\n**解析：** 此题比较有误导性，对于一个对象实例来说，获取原型的方法是：``a.constructor.prototype`` 或者是 ``Object.getPrototypeOf(a)``，对于 ``a.prototype``，直接当成一个属性访问，由于未定义，所以会产生 ``undefined``。\n\n**答案：** ``false, true``\n\n---\n\n### 1.29、\n\n```javascript\nfunction f() {}\nvar a = f.prototype, b = Object.getPrototypeOf(f);\na === b\n```\n\n**解析：** 此题同样考察原型相关知识，前者是f的原型，后者是f的构造函数（Function）的原型。\n\n**答案：** ``false``\n\n---\n\n### 1.30、\n\n```javascript\nfunction foo() { }\nvar oldName = foo.name;\nfoo.name = \"bar\";\n[oldName, foo.name]\n```\n\n**解析：** 函数的名称为常量，但需要注意，赋值不会报错。\n\n**答案：** ``['foo', 'foo']``\n\n---\n\n### 1.31、\n\n```javascript\n\"1 2 3\".replace(/\\d/g, parseInt)\n```\n\n**解析：** 此题考察 ``String.replace()`` 的回调函数，它的回调函数定义是 ``funcation(matchValue, group, valueIndex, sourceStr){}``，依次为匹配到的值、正则分组，该值在字符串中的index，字符串本身。由于在该题中，正则没有分组，所以，调用了三次 ``parseInt`` 如下:\n\n```javascript\nparseInt('1', 0, '1 2 3');\nparseInt('2', 2, '1 2 3');\nparseInt('3', 4, '1 2 3');\n```\n\n**答案：** ``'1 NaN 3'``\n\n---\n\n### 1.32、\n\n```javascript\nfunction f() {}\nvar parent = Object.getPrototypeOf(f);\nf.name // ?\nparent.name // ?\ntypeof eval(f.name) // ?\ntypeof eval(parent.name) //  ?   \n```\n**解析：** 通过 ``Object.getPrototypeOf()`` 获取原型，参数是实例，当f为实例的时候，获取实例f的原型就等于 ``Function.prototype``，由于Function的原型还是一个function。所以 ``parent = Function.prototype 是一个没有名字的function``。\n\n因此:\n```javascript\nf.name // f\nparent.name // ''\n```\n使用 ``eval``，执行f，会返回f这个函数，执行'',会返回 ``undefined``。\n\n**答案：** ``'f', '', 'function', 'undefined'``\n\n---\n\n### 1.33、\n\n```javascript\nvar lowerCaseOnly =  /^[a-z]+$/;\n[lowerCaseOnly.test(null), lowerCaseOnly.test()]\n```\n\n**解析：** 正则 ``test()`` 方法，会把参数当成字符串来使用，但要注意，``null`` 会被当成 ``'null'``来使用，无参则为 ``undefined``。 \n\n**答案：** ``[true, true]``\n\n---\n\n### 1.34、\n\n```javascript\n[,,,].join(\", \")\n```\n\n**解析：**  由于JS的Array本质也是对象，所以具有对象的一个特点，会忽略最后一个单引号，``[,,,].length // 3``。\n\n另外，这种方式定义，实际上是没有产生真正的key的。``[,,,] // [undefined * 3]``\n\n**答案：** ``[, , ]``\n\n---\n\n### 1.35、\n\n```javascript\nvar a = {class: \"Animal\", name: 'Fido'};\na.class\n```\n\n**解析：** 此题理应考察 ``class`` 为JS中的保留字，由于JS版本升级，此种写法已经可以正常返回属性值了。\n\n**注意：在IE8-的浏览器中，会有语法错误，使用了保留字** \n\n**答案：** ``'Animal'``\n\n---\n### 1.36、\n```javascript\nvar a = new Date(\"epoch\")\n```\n\n**解析：** 日期通过new返回的一定是一个Date对象，如果参数格式不合理，则会返回 ``Invalid Date`` 的日期对象。\n\n**答案：** ``Invalid Date``\n\n---\n### 1.37、\n\n```javascript\nvar a = Function.length,\n    b = new Function().length\na === b\n```\n\n**解析：** ``Function.length`` 默认为1，``Function`` 实例的 ``length`` 属性等于 ``function``的参数个数。\n\n**答案：** ``false``\n\n---\n### 1.38、\n\n```javascript\nvar a = Date(0);\nvar b = new Date(0);\nvar c = new Date();\n[a === b, b === c, a === c]\n```\n\n**解析：** 直接函数调用，不关心参数是啥，都会返回当前日期字符串。\n\n通过 ``new Date()``，如果无参数，返回当前日期对象。\n\n通过 ``new Date(0)``，就有一个参数，并且是数字，则参数含义为long类型的UTC时间。\n\n**答案：** ``[false, false, false]``\n\n---\n### 1.39、\n\n```javascript\nvar min = Math.min(), max = Math.max()\nmin < max\n```\n\n**解析：** 此题考察 ``Math.min()`` 和 ``Math.max()`` 的用法。\n\n当 ``Math.min()`` 无参数时，返回 ``Infinity``，参考：[MDN Math.min](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/min)\n\n当 ``Math.max()`` 无参数时，返回 ``-Infinity``，参考：[MDN Math.min](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/max)\n\n\n**答案：** ``false``\n\n---\n### 1.40、\n\n```javascript\nvar a = new Date(\"2014-03-19\"),\n    b = new Date(2014, 03, 19);\n[a.getDay() === b.getDay(), a.getMonth() === b.getMonth()]\n```\n\n**解析：** 此题考察使用年月日构造函数时，月份是从0开始计算的。\n\n**答案：** ``[false, false]``\n\n\n## 2、参考资料\n\n[MDN](https://developer.mozilla.org/en-US) -- Mozilla 开发者网络\n\n[MDN Javascrpt](https://developer.mozilla.org/en-US/docs/Web/JavaScript) --MDN JavaScript目录\n\n"
  },
  {
    "path": "LICENSE",
    "content": "The MIT License (MIT)\n\nCopyright (c) 2015 hstar\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n\n"
  },
  {
    "path": "MongoDB入门基础/01_记一次MongoDB裸奔.md",
    "content": "---\ntitle: 01_记一次MongoDB裸奔\ndate: 2017/02/21 14:47:10\n---\n\n# 导言\n\n大意失荆州，裸奔的 `MongoDB` 被黑了。虽然并不是什么非常重要的数据，但也给自己敲响的一个警钟。虽然我们平时不容易接触到数据安全，但我们在开发，部署项目的时候，一定要养成良好的安全意识。\n\n根据木桶原理，整个系统的安全性，取决于整个系统最薄弱的环节。所以，我们要尽可能多的考虑更多组成部分的安全性。\n\n# 事件发现\n\n本月初，发生了大家所熟知的 `MondoDB赎金事件`。当时本人也保持了一定的关注，并去 [https://www.shodan.io/](https://www.shodan.io/) 溜达了一圈，顺便连了几个裸奔的MongoDB（当然，绝未做任何更改）。\n\n直到昨天下午，发现我应用的管理员账户登录不上了。多次检查密码，发现还是无法解决，此时有点怀疑被黑了。由于应用有新建用户功能，新建一个和管理员账户同名的账户，居然成功了。这个时候，我想多半是遭了，只等晚上回去确认了。\n\n回到家，远程到服务器，一连接，果然遭了（可怜我那几十个代码片段 + 几个Gist），需要赎金0.1BTC。\n\n# 原因分析\n\n此时可能就要问了，都知道了裸奔不安全，为嘛还不修复？\n\n我能说我懒么？心大么？\n\n因为当时我部署的版本的3.2，据说3.2默认没有开启外网访问。我心大到直接未经尝试就认为这是对的。\n\n实际这句话也没错，Linux版本的 `3.x` 确实是默认绑定到 `127.0.0.1` 上的。可TM我是运行在 `Windows` 上的，由于安装的时候，默认没有创建配置文件，导致一运行就绑定到所有host上了。\n\n**当上，以上都是外因！根本原因还是自己疏忽大意，安全意识薄弱。**\n\n# 解决（重头再来）\n\n没有备份，直接无法恢复。\n\n另外，0.1个BTC我是拿不出来的（我也不相信他会好心给你恢复），再加上数据也不是太重要，就直接把安全设置配置上，重头开始。\n\n那现在是如何配置安全性的呢？\n\n以下操作，均在未开启授权访问时执行\n\n首先是添加用户并设置角色：\n\n```bash\n# 切换到admin库\nuse admin\n\n# 创建User\ndb.createUser({user: '<name>', pwd: '<password>', roles: [\n\t{role: 'readWrite', db: '<dbname>'},\n\t{role: 'dbAdmin', db: '<dbname>'}\n]})\n```\n\n接下来就是创建一个配置文件（2.6之后，配置文件是yaml格式），内容如下：\n\n```\nsystemLog:\n    destination: file\n    path: c:\\data\\log\\mongod.log\nstorage:\n    dbPath: c:\\data\\db\nnet:\n    bindIp: 127.0.0.1\n    port: 27017\n```\n\n**注意：配置文件中一定要设定 `log path` 和 `db path`**\n\n**注意2：如果要限制外网访问，就可以配置 net -> bindIp，另外也可以调整端口**\n\n此时如何运行呢？\n\n```bash\nmongod --config \"D:\\MongoDB\\mongod.conf\" --auth\n```\n\n带上 `--auth` 就是开启授权访问。\n\n最后客户端访问也需要稍微修改下，只需要修改 `mongoAddress` 配置即可：\n\n```bash\n# MongoDB Connection String Format\nmongodb://[username:password@]host1[:port1][,host2[:port2],...[,hostN[:portN]]][/[database][?options]]\n\n# 实例地址\nconst mongoAddress = 'mongodb://admin:Pwd0603@127.0.0.1:27017/dojo'\n```\n\n### 就这么一点简单的步骤，就能实现 `MongoDB` 较高的安全性，可这却是非常容易忽略的点。希望大家引以为戒。\n\n# 总结\n\n1. 一定不能疏忽大意，安全意识要加强。\n2. 一定要结果实际验证，才能下结论。不能不清楚外置条件，人云亦云。\n3. 不要有我的应用小，就不会被黑这种想法。批量攻击，才不会放过你。\n4. 定期做好数据备份，被攻击是一回事，能否恢复又是另外一回事了。"
  },
  {
    "path": "MongoDB入门基础/02_Mongo权限探索.md",
    "content": "---\ntitle: 02_Mongo权限探索\ndate: 2017/02/21 14:47:10\n---\n\n# 0x0、导言\n\n经过一次 `MongoDB` 被黑之后，就想把 `MongoDB` 的权限仔细的了解一遍。安全第一！安全第一！\n\n为了避免版本不一致导致的差异，特此说明：以下命令均在 `Mongo3.4` 中测试，理论上支持所有 `3.x` 版本\n\n# 0x1、MongoDB权限系统\n\n`MongoDB` 的权限管理也是符合 `RBAC` 的权限系统。\n\n既然是 `RBAC` 的权限管理，那么就一定会有 `actions` 和 `resources` 的概念。\n\n在 `MongoDB` 中，每一种操作都对应一种 `action`，在 [Action List](https://docs.mongodb.com/manual/reference/privilege-actions/) 可以查看所有的 `Action` 列表\n\n同理，`Collection/Database/Cluster` 都是 `Resrouce`, `resource` 列表也可以在 [Resource Document](https://docs.mongodb.com/manual/reference/resource-document/) 找到。\n\n\n## 1.1、如何授权启动\n\n在 `MongoDB` 中，默认是无授权启动的。如果我们要开启授权，那么需要在启动 `MongoDB` 的时候，加上 `--auth` 参数：\n\n```bash\nmongod --config \"<config path>\" --auth\n```\n如果使用配置文件，那么也可以直接配置\n\n```\nsecurity\n    authorization: enabled\n```\n\n**注意：如果没有添加账户，就算设置 `--auth` 参数也无效，需要先添加用户，在添加用户的时候，必须要指定用户角色。**\n\n## 1.2、MongoDB角色管理\n\n在 `MongoDB` 中，具有两种类型的角色，一类是系统角色，一类是自定义角色。\n\n一般来说，我们只需要关注系统角色。自定义角色需要通过角色相关的Shell来进行CRUD。\n\n### 1.2.1、系统角色\n\n`MongoDB` 按照分类，具有较多的角色，列举如下，也可在 [security-built-in-roles](https://docs.mongodb.com/manual/core/security-built-in-roles/) 查看所有内建角色明细。\n\n* 数据库用户相关角色\n\n\t1. read --只能查看单个数据库\n\t2. readWrite -- 可读写单个数据库\n\n* 数据库管理员相关角色\n\n\t1. dbAdmin -- 数据库管理员，能进行差不多各种操作\n\t2. dbOwner -- 等于dbAdmin、readWrite、userAdmin的并集，数据库拥有者\n\t3. userAdmin -- 能够管理各种用户，角色等（如果是admin集合的，则能管理所有db）\n\n* 集群管理员相关角色\n\n\t1. clusterAdmin -- 是clusterManager，clusterMonitor，clusterMonitor权限的集合，还多了个删除数据库操作。\n\t2. clusterManager -- 主要是配置集群\n\t3. clusterMonitor -- 主要是监控集群\n\t4. hostManager -- 主要是配置主机\n\n* 备份还原相关角色\n\n\t1. backup -- 备份操作\n\t2. restore -- 还原操作\n\n* 针对所有DB的角色\n\n\t1. readAnyDatabase -- 可读取所有的数据库\n\t2. readWriteAnyDatabase -- 可读写所有的数据库\n\t3. userAdminAnyDatabase -- 所有db的用户管理员\n\t4. dbAdminAnyDatabase -- 所有db的DB管理员\n\n* 超级管理员角色\n\n\t1. root -- 超级超级管理员，最大权限了\n\n* 内部角色\n\n\t1. __system -- 既然是内部角色，我们就不要去用了\n\n### 1.2.2、自定义角色\n\n除了系统角色之外，还可以自定义角色，能够更灵活的控制权限。\n\n详细，请参考：[role-management](https://docs.mongodb.com/manual/reference/method/js-role-management/)\n\n## 1.2、MongoDB用户管理\n\n有了Role的知识，我们来看User，就很easy了。\n\n和一般的系统权限类似，MongoDB 也提供了诸多和用户相关的操作\n\n1. db.auth() -- 用于登录\n\n```\ndb.auth( {\n   user: <username>, -- 用户\n   pwd: <password>, -- 密码\n   mechanism: <authentication mechanism>,  -- 可选，认证机制\n   digestPassword: <boolean> \n} )\n```\n\n2. db.createUser()  -- 创建用户\n\n```\n{ user: \"<name>\", -- 用户名\n  pwd: \"<cleartext password>\", -- 密码\n  customData: { <any information> }, -- 自定义的数据\n  roles: [ -- 配置角色，db角色，设置数据库， 否则直接写角色名\n    { role: \"<role>\", db: \"<database>\" } | \"<role>\",\n    ...\n  ]\n}\n```\n\n3. db.updateUser() -- 更新用户\n\n```\ndb.updateUser(\n   \"<username>\", -- 要更新的用户名\n   { -- 要更新的用户对象\n     customData : { <any information> },\n     roles : [\n               { role: \"<role>\", db: \"<database>\" } | \"<role>\",\n               ...\n             ],\n     pwd: \"<cleartext password>\"\n    },\n    writeConcern: { <write concern> }\n)\n```\n\n**注：仅仅只需要更新role的话，考虑使用grantRolesToUser，revokeRolesFromUser**\n\n5. db.removeUser()\n6. db.dropAllUsers()\n7. db.dropUser()\n8. db.grantRolesToUser()\n9. db.revokeRolesFromUser()\n10. db.getUser()\n11. db.getUsers()\n\n其他用户方法都比较类似，查看详细： [user-management](https://docs.mongodb.com/manual/reference/method/js-user-management/)\n\n\n# 0x2、使用授权的MongoDB\n\n如果在 `MongoDB` 中配置了授权，那么连接到带授权的 `MongoDB` 也会有些许区别。\n\n```bash\n# 简单的MongoDB Connection String\nmongodb://127.0.0.1/testdb\n\n# 带端口的MongoDB\nmongodb://127.0.0.1:27018/testdb\n\n# 带授权的MongoDB\nmongodb://user1:password1@127.0.0.1:27017/testdb\n\n# 带授权，且User是admin的场景\nmongodb://user1:password1@127.0.0.1:27017/testdb/?authSource=admin\n```\n\n如果是 `shell`，则需要先执行 `db.auth('name', 'pwd')`，之后才能执行其他命令。\n\n# 0x3、总结\n\n加强安全意识，人为预防常规安全风险。\n\n<time>2017-1-21 16:09:48</time>"
  },
  {
    "path": "Other/Go Go.md",
    "content": "---\ntitle: Go Go\ndate: 2017/02/21 14:47:10\n---\n\n# 题记\n\n> 学习是进步的源泉\n\n在这个云计算、多核盛行的时代，学习一门与之相配合的语言也就无可厚非了。那么对多核与并行计算原生支持的Go就是我的选择了...\n\n关于GO的好坏，我不会去深究，在每个人眼中，这都是主观的。喜欢就会觉得好，不喜欢好也是坏。当然，它本身的优势与劣势是值得我们关注了，这决定了它的适用性。\n\n工欲善其事必先利其器，本人学习一门语言之前，喜欢先寻找趁手的兵器。搜索之，继而找到了LiteIDE。由于偏爱集成开发环境，那么Sublime也就不是我的首选了。\n\n以上全是废话，下面开始搭建开发环境。\n\n# 1、准备工作\n\n1. PC + Windows操作系统（我承认我只会玩Windows...）\n2. 你得下载Go语言：[https://golang.org/](https://golang.org/) BTW：我下载的文件名是：go1.4.1.windows-amd64.msi\n3. LiteIDE：[https://github.com/visualfc/liteide](https://github.com/visualfc/liteide)\n\n# 2、搭建环境\n<span style=\"font-size:25px;font-weight:bold;\">首</span>先，你需要安装Go语言。什么？你不会？双击msi(或者exe)，Next到手软就可以了。当然，最后应该是Finish。经过这个步骤，go就安装上了，什么环境变量啥的都给你配置好了。当然，安装成功不成功不是你说了算，那么打开cmd，输入一个go试试？如果报错，那么狠抱歉，请重试该步骤，或者检查环境变量。如果提示Go is a tool for managing Go source code ,那么恭喜你，安装已成功！\n\n<span style=\"font-size:25px;font-weight:bold;\">接</span>下面，安装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。既然这样，那么解压缩，找个地儿一扔就搞定，是不是更简单？\n\n# 3、永远的经典：Hello World\n既然到了这里，想必以上两个小玩意已经安装好了。那么就应该开始写代码。据说程序员爱代码不爱妹子，这是真的么？\n\n找到liteIDE的安装目录，进行/bin目录，双击“liteide.exe”（PS：一看到这个丑丑的太极图标，我就在想，这货应该是一个国人开发的吧，呵呵，果真是！）。和一般IDE无异，File->New->Go1 Command Project(Not Use GOPATH)，当然其他也是可以选的，惊喜在等着你。输入必要的信息，那么一个项目就创建好了。\n\n打开main.go文件，输入代码（这段代码我是借鉴的，肯定不是copy的）：\n\n\t// demo1 project main.go\n\tpackage main\n\t\n\timport (\n\t\t\"fmt\"\n\t)\n\t\n\tfunc main() {\n\t\tfmt.Println(\"Hello World!\")\n\t}\n\n\n然后点击工具栏上的FR(File Run),这坑爹的按钮，简直反人类。。我点了N次Go按钮之后，才发现这货才是运行。。在右下方区域就能看到输出了。。Hello World~\n\n\n**后记：** 至此，一个简单的Go开发环境已经搭建好了。\n\n## 或许你还有疑问\n\n1. 如果生成可执行文件呢？ **可以利用LiteIDE的Build按钮，或者是控制台命令 ``go build``**\n2. 如果我是64位系统，如何生成32位可执行程序呢？ **我也不知道，只能给你关键字“交叉编译”**\n3. 更多，就留给你慢慢发掘吧。Over！\n"
  },
  {
    "path": "Other/NPM使用详解（上）.md",
    "content": "---\ntitle: NPM使用详解（上）\ndate: 2017/02/21 14:47:10\n---\n\n## 1、NPM是什么？\n\nNPM是JavaScript的包管理工具，在安装NodeJS（什么？你不知道node？来，我们合计合计：[https://nodejs.org/](https://nodejs.org/)）的时候,会自动安装上npm。\n\n要查看安装的npm版本，只需要打开cmd控制台，输入``npm -v``\n\nNPM使得JavaScript开发者分享和重用代码非常容易，同时也让你能否非常方便的更新你分享的代码。\n\nNPM能够自己升级自己，使用命令如下： ``npm install npm -g``\n\n## 2、NPM的使用\n\n以下代码示例中：<>表示必选参数，[]表示可选参数\n\n### #最常用命令\n\n#### 2.1、 init：用于初始化项目\n\n\t/*\n\t * npm init [-f|--force|-y|--yes]\n\t */\n\t\n\t//在文件夹中打开cmd，然后输入npm init，打开项目初始化向导\n\tnpm init \n\t\n\t//如果文件夹名称满足npm的module name,\n\t//那么使用如下方式，可以直接生成一个默认的package.json\n\t//如果文件夹名称不满足要求，那么会出错\n\tnpm init -f\n\tnpm init --force\n\tnpm init --force=true \n\tnpm init -y\n\tnpm init --yes\n\tnpm init --yes=true\n\n\n#### 2.2、install：用于安装模块\n\n\t/*\n\t * npm install (with no args in a package dir)\n\t * npm install <tarball file>\n\t * npm install <tarball url>\n\t * npm install <folder>\n\t * npm install [@<scope>/]<name> [--save|--save-dev|--save-optional] [--save-exact]\n\t * npm install [@<scope>/]<name>@<tag>\n\t * npm install [@<scope>/]<name>@<version>\n\t * npm install [@<scope>/]<name>@<version range>\n\t * npm i (with any of the previous argument usage)\n\t */\n\t\n\t//直接使用npm install 或者是npm i，表示根据package.json，安装所有依赖\n\tnpm install\n\tnpm i\n\t\n\t//如果加上--production参数，那么只会安装dependencies的模块，\n\t//而不会安装devDependencies的内模块\n\tnpm install --production\n\tnpm i --production\n\t\n\t//使用全局上下文来初始化\n\tnpm install -g\n\tnpm i -g\n\t\n\t//安装指定模块\n\tnpm install <packageName>\n\tnpm install <packageName> -g //全局安装\n\tnpm install <packageName>@<version> //指定要安装的模块版本\n\tnpm install <packageName>@<version_start-version_end> //指定要安装的模块版本\n\tnpm install <packageName> --registry=<url> //指定零食的仓库地址\n\tnpm install <packageName> --msvs_version=<vs_version> //指定编译使用的VS版本\n\tnpm install <packageName> --save // 安装模块并修改package.json的dependencies\n\tnpm install <packageName> --save-dev //安装模块并修改package.json的devDependencies\n\t\n\tnpm install <tarball url> //从指定的压缩包地址安装，示例如下：\n\tnpm install https://github.com/indexzero/forever/tarball/v0.5.6\n\t\n\tnpm install <tarball file> //从指定的压缩包安装，如下(注意压缩包格式)：\n\tnpm install del-1.2.0.tar.gz //使用.tgz和.tar.gz格式\n\n\tnpm install @<scope>/<packageName> //安装私有包\n\n\n#### 2.3、uninstall：用于卸载模块\n\n\t/*\n\t * npm uninstall [@<scope>/]<package> [--save|--save-dev|--save-optional]\n\t */\n\t\n\t//直接卸载模块，加上-g参数，表示卸载全局的模块\n\tnpm uninstall <packageName> \n\tnpm uninstall <packageName> -g\n\t\n\t//卸载时同时修改package.json文件\n\tnpm uninstall <packageName> --save-dev\n\tnpm uninstall <packageName> --save\n\n#### 2.4、update：用于更新模块\n\n\t/*\n\t * npm update [-g] [<name> [<name> ...]]\n\t */\n\t\n\t//更新一个或多个模块，加上-g参数，表示更新全局的模块\n\tnpm update <packageName> [packageName2...]\n\tnpm update <packageName> [packageName2...] -g\n\t\n\t//更新时同时修改package.json文件\n\tnpm update <packageName> [packageName2...] --save-dev\n\tnpm update <packageName> [packageName2...] --save\n\n#### 2.5、config：用于设置npm参数\n\n\t//设置指定参数\n\tnpm config set <key> <value> [--global]\n\tnpm set <key> <value> [--global] //可以省略config\n\t//获取现有参数值\n\tnpm config get <key>\n\tnpm get <key> //可以省略config\n\t//删除指定参数，此时参数值会变为默认值\n\tnpm config delete <key>\n\t//查看npm信息；注意：此命令不是查看所有参数配置\n\tnpm config list\n\t//编辑全量的npm配置文件（.npmrc）\n\tnpm config edit\n\t//可以将config使用c代替，执行以上所有命令\n\tnpm c [set|get|delete|list]\n\n#### 2.6、cache：管理包缓存\n\n\t//将指定的包加入npm缓存\n\tnpm cache add <tarball file>\n\tnpm cache add <folder>\n\tnpm cache add <tarball url>\n\tnpm cache add <name>@<version>\n\t//查看现有的npm包缓存，如果加上path参数，则查看该路径下的文件\n\tnpm cache ls [<path>] \n\teg: npm cache ls gulp\n\t//清空缓存。如果加上path，则清理指定路径下的包缓存\n\tnpm cache clean [<path>]\n\teg: npm cache clean gulp\n\n"
  },
  {
    "path": "Other/NPM使用详解（下）.md",
    "content": "---\ntitle: NPM使用详解（下）\ndate: 2017/02/21 14:47:10\n---\n\n在浏览本文之前，建议您先浏览《NPM使用详解（上）》\n\n在上一文中，罗列出了最常用的NPM命令，那么本文将继续分解剩下的NPM命令\n\n---\n\n#### 1、access\n\n#### 2、adduser\n\n\t//用于启动在指定的git仓库添加用户的向导\n\tnpm adduser [--registry=url] [--scope=@orgname] [--always-auth]\n\t//eg:\n    npm adduser --registry=http://registry.npmjs.org\n\n#### 3、bin\n\n\t//打印出npm执行安装的文件夹\n    npm bin\n\n#### 4、bugs\n\n\t//查看某个包的issue列表\n    npm bugs <pkgname>\n\t//eg:（将会用浏览器打开https://github.com/sindresorhus/del/issues）\n\tnpm bugs del \n    // 可以直接在一个包的文件夹中执行无参数的命令，将自动打开该包的issue列表\n    //eg:(在del文件夹下执行cmd)\n    npm bugs\n\n#### 5、build\n\n#### 6、bundle(已过期)\n\n#### 7、completion\n\n#### 8、dedupe\n\n\t//\n\tnpm dedupe [package names...]\n    //可简化为如下调用\n\tnpm ddp [package names...]\n\n#### 9、deprecate\n\n    //为指定版本的包添加过期警告\n\tnpm deprecate <name>[@<version>] <message>\n\t// eg:\n\tnpm deprecate my-thing@\"< 0.2.3\" \"critical bug fixed in v0.2.3\"\n\n#### 10、dist-tag\n\t\n\tnpm dist-tag add <pkg>@<version> [<tag>]\n\tnpm dist-tag rm <pkg> <tag>\n\tnpm dist-tag ls [<pkg>]\n\n#### 11、docs\n\t\n\t//打开包的文档页面\n\tnpm docs [<pkgname> [<pkgname> ...]]\n\tnpm docs (with no args in a package dir)\n    // 打开包的首页readme\n\tnpm home [<pkgname> [<pkgname> ...]]\n\tnpm home (with no args in a package dir)\n\n#### 12、edit\n\n\tnpm edit <name>[@<version>]\n\n#### 13、explore\n\n\tnpm explore <name> [ -- <cmd>]\n\n#### 14、help\n\n\t//打开本地npm的帮助文件\n\tnpm help <topic>\n\tnpm help some search terms\n\t//eg:(打开config的本地帮助)\n    npm help config\n\n#### 15、help-search\n\n\t//从npm的markdown文档中查询所有的term，并展示\n\tnpm help-search some search terms\n\n#### 16、link\n\t\n\tnpm link (in package folder)\n\tnpm link [@<scope>/]<pkgname>\n\tnpm ln (with any of the previous argument usage)\n\n#### 17、logout\n\n\t//从指定的仓库登出\n\tnpm logout [--registry=url] [--scope=@orgname]\n\n#### 18、ls\n\n\t//列举当前文件夹下的所有包\n\tnpm list [[@<scope>/]<pkg> ...]\n\tnpm ls [[@<scope>/]<pkg> ...]\n\tnpm la [[@<scope>/]<pkg> ...]\n\tnpm ll [[@<scope>/]<pkg> ...]\n\n#### 19、npm\n\n\tnpm <command> [args]\n\n#### 20、outdated(☆☆☆☆☆)\n\n\t//检查当前文件夹中的包版本（当前，需要，最新）\n\tnpm outdated [<name> [<name> ...]]\n\n#### 21、owner\n\n\t//管理包的拥有者\n\tnpm owner ls <package name>\n\tnpm owner add <user> <package name>\n\tnpm owner rm <user> <package name>\n\n#### 22、pack(☆☆☆☆☆)\n\n\t//压缩包文件夹\n\tnpm pack [<pkg> [<pkg> ...]]\n\t//eg：在del目录中直接执行\n\tnpm pack\n\t//或者在项目目录中，执行\n\tnpm pack del\n\n#### 23、prefix\n\n\t//打印本地前缀到控制台，如果-g，则打印全局的前缀\n\n#### 24、prune(☆☆☆☆☆)\n\n\t//删除多余的包(如果指定包名，则删除指定的包)\n\tnpm prune [<name> [<name ...]]\n\tnpm prune [<name> [<name ...]] [--production]\n\n#### 25、publish\n\t\n\t//发布包\n\tnpm publish <tarball> [--tag <tag>] [--access <public|restricted>]\n\tnpm publish <folder> [--tag <tag>] [--access <public|restricted>]\n\n#### 26、rebuild\n\n\t//重新编译包\n\tnpm rebuild [<name> [<name> ...]]\n\tnpm rb [<name> [<name> ...]]\n\n#### 27、repo\n\t\n\t//在浏览器中打开包的仓库地址\n\tnpm repo <pkgname>\n\tnpm repo (with no args in a package dir)\n\n#### 28、restart\n\n\t//重新启动包\n\tnpm restart [-- <args>]\n\n#### 29、rm\n\n\t//移除包\n\tnpm rm <name>\n\tnpm r <name>\n\tnpm uninstall <name>\n\tnpm un <name>\n\n#### 30、root\n\n\t//打印node_modules文件夹到控制台\n\tnpm root\n\n#### 31、run-script\n\n\t//运行任意的包脚本\n\tnpm run-script [command] [-- <args>]\n\tnpm run [command] [-- <args>]\n\n#### 32、search\n\n#### 33、shrinkwrap\n\n#### 34、star\n\n\t//给指定的包加star\n\tnpm star <pkgname> [<pkg>, ...]\n\tnpm unstar <pkgname> [<pkg>, ...]\n\n#### 35、stars\n\n\t//查看指定用户的stars\n\tnpm stars\n\tnpm stars [username]\n\n#### 36、start\n\n#### 37、stop\n\n#### 38、tag\n\n#### 39、test\n\n#### 40、unpublish\n\n#### 41、version\n\n\tnpm version [<newversion> | major | minor | patch | premajor | preminor | prepatch | prerelease]\n\n\t//查看项目相关信息\n\tnpm version\n\tnpm version major\n\n#### 42、view\n\n#### 43、whoami\n\n\n\t\n"
  },
  {
    "path": "Other/Thrift简单实践.md",
    "content": "---\ntitle: Thrift简单实践\ndate: 2017/02/21 14:47:10\n---\n\n## 0、什么是RPC\n\n**RPC**（Remote Procedure Call - 远程过程调用）,是通过网络从远程计算机上请求服务，而不需要了解底层网路技术的细节。简单点说，就是**像调用本地服务（方法）一样调用远端的服务（方法）。**\n\n### RPC与REST的区别\n\nRPC是一种协议，REST是一种架构风格。\n\nRPC以行为为中心，REST以资源为中心。当加入新功能时，RPC需要增加更多的行为，并进行调用。REST的话，调用方法基本不变。\n\nRPC可以不基于HTTP协议，因此在后端语言调用中，可以采用RPC获得更好的性能。REST一般是基于HTTP协议。\n\n## 1、RPC框架Thrift（0.9.3）\n\nThrift是一种开源的高效的、支持多种编程语言的远程服务调用框架。支持C++, Java, Python, PHP, Ruby, Erlang, Perl, Haskell, C#, Cocoa, JavaScript, Node.js, Smalltalk, OCaml 和 Delphi等诸多语言，能够很好的进行跨语言调用。\n\nThrift官网： [https://thrift.apache.org/](https://thrift.apache.org/)\n\n## 2、Thrift的简单实践（Windows）\n\n### 2.1 安装Thrift\n\n在[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系统编译版本。\n\n该文件是一个绿色文件，可以放置在目录中，进入该目录的cmd，就可以直接使用thrift。输入``thrift -version``可以查看当前Thrift的版本。\n\n![img1](http://7xit2j.com1.z0.glb.clouddn.com/abc1.png)\n\n至此，Thrift已完成安装\n\n### 2.2 编写接口定义文件\n\n在安装好Thrift之后，需要我们编写接口定义文件，用来约定服务和thrift类型的接口定义。\n\nThrift主要有一下这些类型：\n\n1. bool     --简单类型，true or false\n2. byte     --简单类型，单字符\n3. i16      --简单类型，16位整数\n4. i32      --简单类型，32位整数\n5. i64      --简单类型，64位整数\n6. double   --简单类型，双精度浮点型\n7. string   --简单类型，utf-8编码字符串\n8. binary   --二进制，未编码的字符序列\n9. struct\t--结构体，对应结构体、类等对象类型\n10. list    --list容器\n11. set     --set容器\n12. map     --map容器\n13. enum    --枚举类型\n\n接下来，利用这些类型，编写一个简单的.thrift接口定义文件。\n\n\t/* 1.thrift file content */\n\tnamespace js ThriftTest\n\tnamespace csharp ThriftTest\n\t\n\tservice ThriftTest{\n\t  double plus(1:double num1, 2:double num2)\n\t}\n\n更复杂的案例： [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)\n\n在利用``thrift --gen js:node --gen js 1.thrift``来生成好客户端代码和服务端代码。可以跟多个--gen <language>参数，来实现一次性生成多个语言的代码。\n\n\n### 2.3 利用Thrift实现nodeJS服务端\n\n\tvar thrift = require('thrift');\n\t\n\tvar ThriftTest = require(\"./gen-nodejs/ThriftTest\");\n\tvar ttypes = require(\"./gen-nodejs/1_types\");\n\t\n\t\n\tvar nodeServer = thrift.createServer(ThriftTest, {\n\t  //完成具体的事情\n\t  plus: function(n1, n2, callback){\n\t    console.log(`server request, n1 = ${n1}, n2 = ${n2}.`);\n\t    callback(null, n1 + n2);\n\t  }\n\t});\n\t\n\t//处理错误，假设不处理，如果客户端强制断开连接，会导致后端程序挂掉\n\tnodeServer.on('error', function(err){\n\t  console.log(err);\n\t});\n\t\n\tnodeServer.listen(7410);\n\tconsole.log('node server started... port: 7410');\n\t\n\t//如果client的浏览器，通信采用http的时候，需要创建http server\n\tvar httpServer = thrift.createWebServer({\n\t  cors: {'*': true}, //配置跨域访问\n\t  services: {\n\t    '/thrift': { //配置路径映射\n\t      transport: thrift.TBufferedTransport,\n\t      protocol: thrift.TJSONProtocol,\n\t      processor: ThriftTest,\n\t      handler: { //具体的处理对象\n\t        plus: function(n1, n2, callback) {\n\t          console.log(`http request, n1 = ${n1}, n2 = ${n2}.`);\n\t          callback(null, n1 + n2);\n\t        }\n\t      }\n\t    }\n\t  }\n\t});\n\t\n\thttpServer.on('error', function(err) {\n\t  console.log(err);\n\t});\n\t\n\thttpServer.listen(7411);\n\tconsole.log('http server started... port: 7411');\n\n### 2.4 Node Client 调用\n\n\tvar thrift = require('thrift');\n\tvar ThriftTest = require('./gen-nodejs/ThriftTest');\n\tvar ttypes = require('./gen-nodejs/1_types');\n\t\n\ttransport = thrift.TBufferedTransport()\n\tprotocol = thrift.TBinaryProtocol()\n\t\n\tvar connection = thrift.createConnection(\"localhost\", 7410, {\n\t  transport : transport,\n\t  protocol : protocol\n\t});\n\t\n\tconnection.on('error', function(err) {\n\t  console.log(false, err);\n\t});\n\t\n\tvar client = thrift.createClient(ThriftTest, connection);\n\t\n\tvar sum = client.plus(1, 1, function(err, result){\n\t  //connection.end(); //如果不关闭连接，那么强制断开连接，将会导致后端出现error\n\t  if(err){\n\t    console.log(err);\n\t    return;\n\t  }\n\t  console.log(result);\n\t});\n\n### 2.5、Http Client 调用\n\n\t<!DOCTYPE html>\n\t<html lang=\"en\">\n\t<head>\n\t  <meta charset=\"UTF-8\">\n\t  <title>Thrift Test Client</title>\n\t</head>\n\t<body>\n\t  <input type=\"text\" id=\"num1\"> + <input type=\"text\" id=\"num2\"> <button onclick=\"call()\">=</button> <span id=\"result\">?</span>\n\t <!--  <script src=\"jquery.js\"></script> -->\n\t  <script src=\"thrift.js\"></script>\n\t  <script src=\"gen-js/1_types.js\"></script>\n\t  <script src=\"gen-js/ThriftTest.js\"></script>\n\t  <script>\n\t    var transport = new Thrift.Transport(\"http://127.0.0.1:7411/thrift\");\n\t    var protocol = new Thrift.TJSONProtocol(transport);\n\t    var client = new ThriftTest.ThriftTestClient(protocol);\n\t    var el_result = document.getElementById('result');\n\t    function call(){\n\t      var num1 = +document.getElementById('num1').value,\n\t      num2 = +document.getElementById('num2').value;\n\t      client.plus(num1, num2, function(result) {\n\t        el_result.innerText = result;\n\t        alert('调用成功！');\n\t      });\n\t    }\n\t  </script>\n\t  <script>\n\t  </script>\n\t</body>\n\t</html>\n\n**注意：如果在thrift生成代码时，使用了--gen js:jquery参数，那么在浏览器调用的时候，就必须依赖jquery。**\n\n## 3、demo地址\n\n[https://github.com/hstarorg/HstarDemoProject/tree/master/thrift_demo](https://github.com/hstarorg/HstarDemoProject/tree/master/thrift_demo)"
  },
  {
    "path": "Other/TypeScript札记：初体验.md",
    "content": "---\ntitle: TypeScript札记：初体验\ndate: 2017/02/21 14:47:10\n---\n\n## 1、简介\n\nTypeScript 是一种由微软开发的自由和开源的编程语言。它是JavaScript的一个超集，而且本质上向这个语言添加了可选的静态类型和基于类的面向对象编程。\n\nTypeScript是一种Compile-to-JavaScript的语言\n\nTypeScript扩展了JavaScript的句法，所以现有的JavaScript程序可以不加修改，直接在TypeScript下工作。同时，TypeScript编译产生JavaScript以确保兼容性。\n\n## 2、特点&优势\n\n2.1、兼容现有JS代码\n\n2.2、类型系统，面向对象设计，保证程序的健壮性（编译检查）\n\n2.3、良好的语法，良好的工具支持\n\n2.4、良好的社区支持\n\n## 3、快速开始\n\n3.1、 **工具**\n\n如果是VS开发，安装 [TypeScript 1.4 for Visual Studio 2013](https://portal.qiniu.com/signup?code=3lo24xqrim8gi)，版本随时变化，建议下载最新版本。\n\n如果是NPM用户，那么直接 ``npm install -g typescript``\n\n3.2、 **Hello Jay**\n\n使用VS的用户，直接新建项（TypeScript File即可）；使用其他IDE的用户，如果IDE支持TypeScript，那么直接新建TypeScript；其他则新建文本文件，后缀名为ts。如果是不能在IDE中编译，那么可以直接通过npm安装typescript之后，使用tsc fileName.ts，进行编译。\n\n打开1.ts文件，输入：\n\n\tfunction hello(name: string){\n\t  return 'Hello,' + name;\n\t}\n\t\n\tvar res = hello('Jay');\n\tconsole.log(res);\n\n执行``tsc 1.ts``之后，生成一个1.js文件（具有可读性的标准js文件）：\n\n\tfunction hello(name) {\n\t    return 'Hello,' + name;\n\t}\n\tvar res = hello('Jay');\n\tconsole.log(res);\n\n## 4、参考资料\n\n1、 [官网：http://www.typescriptlang.org/](http://www.typescriptlang.org/)\n\n2、 [入门指南： https://github.com/vilic/typescript-guide](https://github.com/vilic/typescript-guide)\n"
  },
  {
    "path": "Other/Windows下把Nginx，PM2包装为服务.md",
    "content": "---\ntitle: Windows下把Nginx，PM2包装为服务\ndate: 2017-2-25 09:55:26\n---\n\n# 0x0、前言\n\n在 `Windows` 上部署 `Node` 或者前后端分离的静态Web程序时，我们一般会使用到 `PM2` 和 `Nginx`。\n\n`PM2` 用于管理 `Node` 程序。\n\n`Nginx` 用于托管静态文件或者反向代理。\n\n# 0x1、遇到的问题\n\n在使用 `PM2` 和 `Nginx` 的时候，我们不能包装服务器一直不关机。只要一关机，我们就需要手动去重新启动我们的服务，这种方式非常不友好。\n\n所以，我们需要它们能开机自动启动。虽然有多种方式，但最佳的无疑是包装为 `Windows Service` 。\n\n# 0x2、winsw\n\n在寻找方案的过程中，我们发现了 [winsw](https://github.com/kohsuke/winsw)，它是一个可以将可执行程序包装为 `Windows Service` 的包装程序。\n\n接下来就用它来演示如何将 `Nginx` 和 `PM2` 包装为 `Windows Service`\n\n## 将 `Nginx` 包装为 `Windows Service`\n\n首先，我们要先准备好 `Windows` 版本的 `Nginx`，[Nginx Donwload Page](http://nginx.org/en/download.html)，选择合适的版本后，下载解压到目录中。\n\n接着我们从 [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/)。\n\n选择合适的版本后（建议用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) 。\n\n将下载好的文件放到 `nginx` 的目录下（和nginx.exe同级），并改名为 `nginx-service.exe`（名字可以自定义）。\n\n之后，我们需要创建对应的服务配置文件 `nginx-service.xml` （该文件对应上一步骤的名称），并输入以下内容：\n\n```xml\n<service>\n  <id>nginx</id>\n  <name>Nginx</name>\n  <description>High Performance Nginx Service.</description>\n  <logpath>D:\\GreenSoft\\nginx-1.11.5\\logs\\</logpath>\n  <logmode>rotate</logmode>\n  <executable>D:\\GreenSoft\\nginx-1.11.5\\nginx.exe</executable>\n  <stopexecutable>D:\\GreenSoft\\nginx-1.11.5\\nginx.exe -s stop</stopexecutable>\n</service>\n```\n\n最后我们就可以使用 `nginx-service install` 来安装服务，并通过 `net start nginx` 来启动了。\n\n如果要重启服务呢？使用 `net stop nginx & net start nginx`\n\n## 将 `PM2` 包装为 `Windows Service` （公司权限限制，暂未测试通过）\n\n和 `Nginx` 类似，我们只需要将wrapper程序改名，然后创建匹配的`xml`配置文件即可。\n\n```xml\n<service>\n  <id>pm2</id>\n  <name>PM2</name>\n  <description>Node applications management tools.</description>\n  <logmode>rotate</logmode>\n  <executable>%AppData%\\npm\\pm2 resurrect</executable>\n  <stopexecutable>%AppData%\\npm\\pm2 save &amp; %AppData%\\npm\\pm2 delete all</stopexecutable>\n</service>\n```"
  },
  {
    "path": "Other/开发者讨厌你API的十个原因.md",
    "content": "---\ntitle: 开发者讨厌你API的十个原因\ndate: 2017/02/21 14:47:10\n---\n\n##1、文档的吸引力太弱##\n###解决之道###\n1. 采用大图片：[示例站点](https://www.twilio.com/docs)\n2. 文档清晰度：[示例站点](https://stripe.com/docs/api)\n3. 文档易于查找：[示例站点](https://stripe.com/docs/)\n4. 生动的文档：\n\t1. [Swagger](https://github.com/wordnik/swagger-core)\n\t2. [I/O Docs](https://github.com/mashery/iodocs)\n\t3. 采用RAML(RESTful API 模型语言) [RAML官网](raml.org)\n\t\n##2、您的沟通技能需要工作（你不能保证开发者始终被通知到）\n###解决之道###\n1. 使用变更日志：[http://developer.github.com/changes/](http://developer.github.com/changes/)\n2. 使用路线图：[https://developers.facebook.com/roadmap/](https://developers.facebook.com/roadmap/)\n3. 采用发布日志：[http://techblog.constantcontact.com/api/release-updates](http://techblog.constantcontact.com/api/release-updates)\n4. 使用博客（Blog）：[http://aws.typepad.com/](http://aws.typepad.com/)\n5. 使用论坛（Forum）：[http://stackoverflow.com/questions/tagged/soundcloud](http://stackoverflow.com/questions/tagged/soundcloud)\n6. 邮件通知\n\n##3、你不能使API使用简单##\n###解决之道###\n1. 说明你是做什么的：[https://www.twilio.com/voice/api](https://www.twilio.com/voice/api)\n2. 支持快速注册：[https://manage.stripe.com/register](https://manage.stripe.com/register)\n3. 使用step1-step2-step3说明使用步骤：[示例站点](http://developer.constantcontact.com/get-started.html)\n4. 提供快速入门手册：[https://www.twilio.com/docs/quickstart](https://www.twilio.com/docs/quickstart)\n5. 提供免费版或者免费试用版：[https://parse.com/plans](https://parse.com/plans)\n6. 提供丰富的SDK（支持多种开发语言）\n7. 使用GitHub ：[https://github.com/OneNoteDev](https://github.com/OneNoteDev)\n\n##4、没有提供法律申明##\n###解决之道###\n1. 要明确权利与义务：[http://500px.com/terms](http://500px.com/terms)\n2. 编写使用协议：[https://www.etsy.com/developers/terms-of-use](https://www.etsy.com/developers/terms-of-use)\n3. 申明越短越好：[http://googledevelopers.blogspot.com](http://googledevelopers.blogspot.com)\n4. 申明要想长远：[https://developers.google.com/youtube/terms](https://developers.google.com/youtube/terms)\n5. 分享你的财富：[http://slideshare.net/jmusser](http://slideshare.net/jmusser)\n\n##5、你的API不可靠（慢、错误、不可靠）##\nAPI会被停运(Outage)、Bug、速率(Rate limit)、变更(包含有计划的变更和未被文档跟踪的变更)、ToS违规、Provider biz change、网络等原因影响。\n\n不要让API返回未知的错误信息，让用户迷惑。\n###解决之道###\n1. 使用状态页：[http://status.aws.amazon.com/](http://status.aws.amazon.com/)\n2. 监控API：[http://www.apiscience.com](http://www.apiscience.com)\n3. 不要隐藏API的变化，如停运：[http://blog.akismet.com](http://blog.akismet.com)\n\n##6、没有提供能帮助我调用成功的工具##\n###解决之道###\n1. 提供开发者仪表板：[https://manage.stripe.com/test/dashboard](https://manage.stripe.com/test/dashboard)\n2. 提供 Debug/Log 等日志：[示例站点](www.twilio.com/user/account/developer-tools/app-monitor)\n3. 提供用于测试的沙盒环境：[https://www.twilio.com/user/account](https://www.twilio.com/user/account)\n4. 提供Playground：[https://developers.google.com/oauthplayground](https://developers.google.com/oauthplayground)\n5. 提供测试控制台：[https://apigee.com/providers](https://apigee.com/providers)\n\n##7、只管销售，但不提供售后服务##\n###解决之道###\n1. Evangelists：[http://sendgrid.com/developers](http://sendgrid.com/developers)\n2. Events：[https://www.twilio.com/conference](https://www.twilio.com/conference)\n3. Hackathons\n4. PS：不知道如何翻译，so总结一点，就是提供售后支持。\n\n##8、API太复杂了（你使用你自己定制的授权、协议、格式）##\n###解决之道###\n1. 使用REST（当前最流行的风格）\n2. 使用JSON格式（XML也还好）\n3. 保持务实：[http://apigee.com/about/content/web-api-design](http://apigee.com/about/content/web-api-design)\n\n##9、你的TTFHW（Time to *(your)* First Hello World）太长##\n###解决之道###\n1. 极好的开发者体验：[http://developerexperience.org](http://developerexperience.org)\n2. 在所有问题修正前，先说“Sorry”\n\n##10、你还没有从最好的学习到的##\n1. 学习榜样的做法（Twilio,Stripe,GitHub.SendGrid）\n2. 保持进步\n3. 记住一句话：API是旅程，不是目的地"
  },
  {
    "path": "Other/浅析12306前端优化点.md",
    "content": "---\ntitle: 浅析12306前端优化点\ndate: 2017/02/21 14:47:10\n---\n\n## 关于12306\n\n中国铁路客户服务中心([12306.cn](https://kyfw.12306.cn/))，相信大家都不陌生。作为一个超大型的类电商网站，具体业务不予置评，但从前端设计来看，却有诸多的不足。\n\n## 12306订票首页分析\n\n12306首页([https://kyfw.12306.cn/otn/](https://kyfw.12306.cn/otn/))请求达到32个，累计文件大小近800k。\n\n其中有一半是图片资源，大小达到444kb。\n\n![12306首页图片资源](http://images.cnblogs.com/cnblogs_com/humin/771181/o_1.jpg)\n\n另外有6个css文件请求，特别的是有2次css请求完全指向同一个css文件。\n\n![12306首页CSS请求](http://images.cnblogs.com/cnblogs_com/humin/771181/o_2.jpg)\n\n还有8个js请求\n\n![12306首页JS请求](http://images.cnblogs.com/cnblogs_com/humin/771181/o_3.jpg)\n\n这就是首页初次打开所需要的内容，当然还有两个html页面，就不计算了。可以看到，打开整个页面文件大小达到800k，和taobao，jd之类的比起来，这个大小是很小的，但是为什么给用户的体验就是卡顿、加载中、加载中呢？\n\n接下来，就从前端的角度来看下，有没有可以优化的地方呢？\n\n## 优化1、使用浏览器缓存\n\n在页面加载中，12306请求了如此多的资源，很多资源看起来，根本就是不太容易变化的，应在HTTP标头中设置有效期，尽可能多的使用浏览器缓存。\n\n## 优化2、图片优化\n\n数据传输时间，在访问网站的过程中，是一个耗时比较大的过程，其中又以图片传输为最，如果网站上有较多的图片，那么就要想办法减少体积，延迟加载等等。在12306的页面上，logo（https://kyfw.12306.cn/otn/resources/images/logo.png ），icon（https://kyfw.12306.cn/otn/resources/images/logo.png ）等等图片都是可以优化的。\n\n## 优化3、图片组合为CSS贴图\n\n浏览器一般都有并发连接数限制，也就是同时请求的资源数量是有效的，前端优化点之一就是减少请求数量，那么12306中的诸多小图片完全可以合并到一个大图之中，采用贴图定位的方式，降低请求数量。\n\n## 优化4、暂缓JS解析\n\n由于JS是阻塞加载的，一般来说，把js放在head中会影响页面的渲染速度，很多时候，我们都推荐把js放在body结束标记之前。但12306偏偏没有这么做，把大把的js放在head中。\n\n## 优化5、使用css而不是图片控制背景\n\n这个似乎是大家都知道的常识，就算为了兼容老版本的浏览器，也可以考虑做优雅降级。但12306偏偏就大量使用背景图。\n\n## 优化6、CSS合并\n\n同样为了减少请求数，应该尽量将CSS压缩合并。分析12306的站点css，发现部分合并了，部分没有，而且有些css连压缩都没做，很难想象是怎么打算的。另外，外部控件的样式（不会变的样式）完全可以打包放到cdn上。\n\n## 优化7、JS合并\n\nJS同上，该打包就打包，不要搞一堆js出来，加载还慢。。\n\n\n## 总结\n\n抢票还是每年的一个老大难的问题。12306，你可以推说你的核心逻辑复杂，这个我接受。但你完全可以把前端的一些基本优化点做到吧。就算我买不到票，至少我抢票的时候心情不至于太差。。\n\n以上，抢票之余作为一个伪前端的发泄。\n\n\n\n\n\n\n\n"
  },
  {
    "path": "Other/程序集强签名.md",
    "content": "---\ntitle: 程序集强签名\ndate: 2017/02/21 14:47:10\n---\n\n## 1、特点\n\n1.1、强签名的程序集可以注册到GAC（全局应用程序集缓存）,不同的应用程序可以共享同一个dll。\n\n1.2、强签名的库（应用程序）只能引用强签名的库。非强签名的库（应用程序）没有限制，既可以引用强签名的库，也可以引用非强签名的库。（实际测试，强签名的应用只引用非强签名的程序集但不使用是可以的，只要使用了引入库的东西，那就会报：引用的程序集没有强签名）\n\n1.3、强签名无法保护源代码，但能防止dll被第三方篡改。\n\n1.4、能防止dll冲突。\n\n## 2、方法\n\n**--如果有源代码**\n\n项目 -> 右键 -> 属性 -> 签名：\n\n![Signing panel](http://7ximjo.com1.z0.glb.clouddn.com/1.png)\n\n勾选 Sign the assembly（签名程序集）\n\n![Signing panel](http://7ximjo.com1.z0.glb.clouddn.com/2.png)\n\n填写 key file name，如果勾选了密码保护key file，那么就输入密码。点击OK就创建好了签名文件。\n\n将属性保存，然后再次编译，程序集就是强签名程序集了。\n\n\n**--如果没有源代码**\n\n1、首先创建一个签名key file，可以通过有源代码的方式，创建key file备用；也可以通过VS 控制台执行``sn.exe -k D:\\sn.snk`` 生成key。\n\n2、将无源代码的dll，通过 ``ILDASM.exe MagicOrm.dll /OUTPUT=D:\\MagicOrm.il``进行反汇编\n\n**注意：如果dll有资源文件，同时会生成MagicOrm.res文件**\n\n3、带上签名文件重新汇编为dll，如果有资源文件生成，也需要带上，命令：``ILASM.exe MagicOrm.il /dll /output=D:\\MagicOrm.dll /Key=sn.snk /Resource=MagicOrm.res``\n\n## Over\n"
  },
  {
    "path": "PHP学习之路/01_PHP简易安装环境.md",
    "content": "---\ntitle: 01_PHP简易安装环境\ndate: 2017/02/21 14:47:10\n---\n\n## 0、导言\n\n**PHP** 是啥，我想应该不用解释了吧。\n\n最近发布的最新版本 ``PHP7`` ，提供之前版本的2倍速度提升，感觉很有吸引力哈。在看到2016年编程语言趋势和想到之前也想体验一下 ``PHP`` 的情况下，就说干就干，直接来简单学习下这门语言。\n\n## 1、PHP简易环境搭建\n\n### 1.1、PHP安装\n\n``PHP`` 的安装相当简单，打开 ``PHP`` 的下载地址：[http://php.net/downloads.php](http://php.net/downloads.php)，可以看到它的版本下载。\n\n我这里是 Windows 环境，就点击 【Windows downloads】 进入Windows版本的PHP下载地址： [http://windows.php.net/download#php-7.0](http://windows.php.net/download#php-7.0) 。在这里找到对应的版本下载即可。\n\n**注意：请下载对应x86,x64的zip包，不要下载Debug Pack包。至于Non Thread Safe 与 Thread Safe，由于本人刚接触，不知道有什么区别，随意下载一个就行。** \n\n我是Win10 x64版本，所以直接下载的：【VC14 x64 Non Thread Safe (2016-May-25 23:02:13)】（有最新版本下载最新版本即可）。\n\n下载好之后，是一个压缩包。解压到目录中，在环境变量中配置 ``Path`` 为该目录。\n\n打开 ``cmd`` 窗口，执行 ``php -v`` ，如果输出 ``PHP`` 的版本号，则表示安装成功！\n\n### 1.2、IDE的选择\n\nPHP有比较多IDE，这里推荐 [PhpStorm](https://www.jetbrains.com/phpstorm/) 和 [VsCode](https://code.visualstudio.com/)。\n\n本人使用的 ``VsCode``，足够轻量。\n\n### 1.3、依赖管理工具\n\n一个成熟的语言，一定会有很多现成的包，如C#的Nuget，Node的npm。在PHP中，也有同样的工具：Composer。\n\n*如何在Windows下使用：Composer？*\n\n首先，进入Composer下载地址：[https://getcomposer.org/download/](https://getcomposer.org/download/)，找到【Composer-Setup.exe】，然后下载安装。\n\n安装成功之后在控制台执行：``composer`` 会输出一系列命令，则证明安装成功。\n\n然后就可以通过 ``composer install <package>`` 来安装依赖包了。 想了解更多 ``composer`` 命令，请查询：[https://getcomposer.org/doc/](https://getcomposer.org/doc/)。\n\n**注意，我在Windows中使用composer安装时，先使用了 ``composer config disable-tls true`` 和 ``composer config secure-http false`` 才得以成功安装依赖。**\n\n### 1.4、Server程序\n\nPHP自带有一个命令行的Server，用于开发测试已经足够使用了。所以，我直接使用了该Server。\n\n只需要在php项目的根目录，打开cmd，执行 ``php -S localhost:9999`` 就可以启动一个PHP Server了。\n\n想了解更多关于PHP自带的Web Server，请参考 [http://php.net/features.commandline.webserver](http://php.net/features.commandline.webserver)\n\n\n## 2、Hello PHP\n\n新建一个目录，创建 ``index.php``，输入以下内容：\n\n```php\n<?php\n  mb_internal_encoding('UTF-8');\n\n  mb_http_output('UTF-8');\n?>\n<!doctype html>\n<html>\n  <head>\n    <title>Php Info</title>\n  </head>\n  <body>\n    <?php\n      phpinfo();\n    ?>\n  </body>\n</html>\n```\n\n打开控制台，使用 ``php -S localhost:9999`` 启动WebServer。\n\n用浏览器访问 [http://localhost:9999](http://localhost:9999)，就可以看到当前服务器的PHP环境信息了。\n\n## 3、Other\n\n3.1、推荐资料： [PHP之道](http://laravel-china.github.io/php-the-right-way/)\n\n3.2、PHP的编码问题，一般在php的页面上，我们都需要设置：\n\n```php\n<?php\n  mb_internal_encoding('UTF-8'); //内部编码为UTF-8\n\n  mb_http_output('UTF-8'); //服务器输出内容编码为UTF-8\n?>\n```\n\n要想用Server运行含有该代码的PHP页面。需要特别配置一下 ``php.ini`` 文件。\n\n在PHP的解压目录，找到 ``php.ini-development``，复制一份为 ``php.ini``，然后找到 ``extension_dir``，设置为：``extension_dir = \"你的PHP解压目录\\ext\"``，然后找到 ``;extension=php_mbstring.dll`` 去掉前面的注释。\n\n3.3、PHP框架推荐\n\n* Yaf 官方框架，超高性能\n\nhttp://www.laruence.com/manual/index.html\n\nhttp://php.net/manual/zh/yaf.installation.php\n\n* LazyPHP 超级简单的框架，建议读源码\n\nhttps://github.com/easychen/LazyPHP\n\n* Slim 据说还不错\n\nhttp://www.slimframework.com/\n\n* Laravel 高人气框架\n\nhttps://laravel.com/\n\nhttps://lumen.laravel.com/  专注API开发的PHP。\n\n* ThinkPHP 中文\n\nhttp://www.thinkphp.cn/\n\n* InitPHP (A PHP Framework) - (from github)\n\nhttp://www.initphp.com/\n\n* TinyMVC (from github)\n\nhttps://github.com/mohrt/tinymvc-php\n\n"
  },
  {
    "path": "PHP学习之路/02-PHP基础语法（上）.md",
    "content": "---\ntitle: 02-PHP基础语法（上）\ndate: 2017/02/21 14:47:10\n---\n\n# 0、导言\n\n学习一门语言，首先要了解它能做什么？其次，就应该去学习应该如何做。那这个的前提就是语法的学习。\n\n语法决定了代码应该如何写（仅仅是可运行），接着我们就来看看PHP它的语法吧。\n\n*注意：本文测试代码全部运行在PHP7上。*\n\n# 1、基础中的基础\n\n1.1、 PHP文件以 ``.php`` 结尾，对于渲染HTML的PHP文件，其本质还是一个HTML页面，只要可以嵌入PHP逻辑代码。\n\n1.2、 在前端 ``.php`` 文件中，要嵌入PHP代码，需要使用 &lt;?php 你的代码 ?>。\n\n1.3、PHP的每个语句以分号结束（部分场景省略分号也不报错）。\n\n1.4、 PHP中，有两种注释方式。\n\n```php\n// 我是单行注释\n\n/*\n  我是多行注释\n*/\n```\n\n1.5、PHP的输出，也有两种方式\n\n一是 ``echo`` \n\n```\necho 'abc', 'aaaa';\necho('abc', 'aaaa');\n```\n\n二是 ``print``\n\n```\nprint 'abc';\nprint('abc');\n$result = print('abc');\n```\n\n**注意：``echo``、``print``即是语言结构，也算是函数，所以可以不加括号调用，也可加括号调用。**\n\n**注意2：``echo`` 输出没有返回值，``print`` 有返回值1。**\n\n**注意3：``echo`` 输出比 ``print`` 快！**\n\n1.6、数据的格式化输出，在输入时，我们可以用更简单的方法拼接字符串\n\n```php\n$name = 'Jay';\necho 'My name is {$name}';\necho \"My name is {$name}\";\nprint 'My name is {$name}';\nprint \"My name is {$name}\";\n```\n\n会输出：\n\n```html\n My name is {$name}\n My name is Jay\n My name is {$name}\n My name is Jay\n```\n\n**！！！注意：只有当使用双引号（\"\"）包裹字符串的时候，才可以使用简易字符串拼接。**\n\n# 2、数据类型\n\nPHP有个和大多数语言雷同的类型系统，系统提供了如下类型：\n\n1. String（字符串）\n2. Integer（整型）\n3. Float（浮点型）\n4. Boolean（布尔型）\n5. Array（数组）\n6. Object（对象）\n7. NULL（空值）。\n\nPHP中的变量命名以$为标记，之后跟变量名称（变量名字只能包含数字字母和下划线），变量区分大小写。\n\nPHP是弱类型语言，所以同一个变量，可以存储多种类型数据。\n\n```php\n$a = '我是字符串'; //定义字符串（单引号，双引号皆可）\necho $a, '<br>';\n$a = 10; // 我的整数\necho $a, '<br>';\n$a = 0x10; // 定义16进制整数\necho $a, '<br>';\n$a = 010; // 定义8进制整数\necho $a, '<br>';\n$a = 0.1; // 浮点数\necho $a, '<br>';\n$a = 8E-5; // 指数形式定义浮点数\necho $a, '<br>';\n$a = true; // Bool类型，只有true,false，注意不区分大小写，写成True，TrUe都没问题。\necho $a, '<br>';\n$a = NuLL; // NULL类型只有一个null值，同样不区分大小写。\n```\n\n输出如下：\n\n```html\n我是字符串<br>\n10<br>\n16<br>\n8<br>\n0.1<br>\n8.0E-5<br>\n1<br>  // 注意，Bool类型，true会输出1，false会输出0\n<br> // NULL类型，无任何输出\n```\n\n以上演示了PHP中的简单类型，还剩下Array和Object两个复杂类型。\n\n**Array 类型**\n\n数组又分为以下几种：\n\n1. 简单数组（数值数组，下标为数字）\n\n```php\n$arr = ['item1', 'item2'];\n// 等价于\n$arr = array('item1', 'item2');\n\n// 仅能通过下标访问元素\necho $arr[0];\n```\n\n2. 关联数组\n\n```php\n$arr = ['key1' => 'value1', 'key2' => 'value2'];\n//等价于\n$arr = array('key1' => 'value1', 'key2' => 'value2');\n\n// 仅能通过key访问\necho $arr['key1'];\n```\n\n3. 多维数组（数组包含数组）\n\n```php\n$arr = ['key1' => ['a', 'b'], 'key2' => ['c', 'd']];\n//等价于\n$arr = array('key1' => array('a', 'b'), 'key2' => array('c', 'd'));\n\n// 根据数组类型，通过key或者是下标访问\necho $arr['key1'][0];\n```\n\n数组Demo合集：\n\n```php \n$arr = ['item1', 'item2'];\nprint_r($arr);\necho '<br>'; \n$arr = array('item1', 'item2');\nprint_r($arr);\necho '<br>'; \n//仅能通过下标访问\necho '$arr第一个元素是：', $arr[0], '<br><br>';\n\n$arr = ['key1' => 'value1', 'key2' => 'value2'];\nprint_r($arr);\necho '<br>';\n$arr = array('key1' => 'value1', 'key2' => 'value2');\nprint_r($arr);\necho '<br>';\n// 通过key访问\necho '$arr的key1值是：', $arr['key1'], 'key2值是：', $arr['key2'], '<br><br>';\n\n$arr = ['key1' => ['a', 'b'], 'key2' => ['c', 'd']];\nprint_r($arr);\necho '<br>';\n$arr = array('key1' => array('a', 'b'), 'key2' => array('c', 'd'));\nprint_r($arr);\necho '<br>';\n// 根据数组类型，通过key或者是下标访问\necho $arr['key1'][0], $arr['key2'][1];\n```\n\n**Object 类型**\n\nPHP中的Object类型，和编译性语言比较类似，是通过new class得到的。\n\n```php\nclass User{\n  var $userName;\n  function setName($name){\n    $this->userName = $name;\n  }\n  function getName(){\n    return $this->userName;\n  }\n}\n$user = new User();\n$user->setName('Jay');\necho $user->getName();\n```\n\n以上代码会输出：``Jay``\n\n# 4、常量与变量\n\n## 4.1、常量\n\nPHP中的常量必须使用 ``define`` 函数来定义。语法如下：\n\n```php\ndefine(常量名称:string, 常量值, 是否区分大小写:bool-默认为false);\n\n//定义一个常量\ndefine('PI', 3.1415926, true);\n```\n\n常量值被定义后，在脚本的其他任何地方都不能被改变，且常量是全局可用的。\n\n## 4.2、变量\n\nPHP的变量有如下几类：\n\n1. local - 局部变量\n2. global - 全局变量\n3. static - 静态变量\n4. parameter - 参数变量\n\n### 4.2.1、局部变量\n\n定义在函数中的变量，被称之为局部变量，只在当前函数有效。\n\n```php\nfunction fun(){\n  $funName = 'fun1';\n  echo $funName;\n}\nfun();\necho $funName; // 出现警告：Undefined variable: funName\n```\n\n### 4.2.2、全局变量\n\n定义在函数外部的变量则是全局变量，如果要在函数内部使用，则需要使用global关键字。示例如下：\n\n```php\n$appName = 'test';\n\nfunction fun1(){\n  // 需要指定，当访问$appName时，是访问全局的$appName，否则会出现一个警告，未定义的变量。\n  global $appName; \n  echo $appName;\n}\n\nfun1();\n```\n\n### 4.2.3、静态变量\n\n局部变量，一般是执行完函数，即被释放。如果想保留该变量，那么就可以使用静态变量。\n\n```php\nfunction funs(){\n  $id = 1;\n  static $static_id = 1;\n  echo '$id = ', $id, ', $static_id=', $static_id, '<br>';\n  $id++;\n  $static_id++;\n}\nfuns();\nfuns();\n```\n\n输出结果为：\n\n```html\n$id = 1, $static_id=1\n$id = 1, $static_id=2\n```\n\n我们可以看到 ``$static_id`` 并没有被释放，一直有效。\n\n**注意：静态变量本质上还是局部变量。**\n\n### 4.2.4 参数变量\n\n这个没啥好说的，函数参数中的变量，类似于局部变量。\n\n### 4.2.5 超级全局变量\n\n什么是超级全局变量呢？\n\n不需要特别定义，可直接在全局任何地方使用,是PHP预定义的全局变量。它们是：\n\n1. $GLOBALS\n2. $_SERVER\n3. $_REQUEST\n4. $_POST\n5. $_GET\n6. $_FILES\n7. $_ENV\n8. $_COOKIE\n9. $_SESSION\n\n# 5、其他\n\n完整Demo地址：[PHP语法演示Demo01](https://github.com/hstarorg/HstarDemoProject/blob/master/php_demo/04-grammar/01.php)\n\n更多内容，请看下回分解。"
  },
  {
    "path": "README.md",
    "content": "# HstarDoc\nmarkdown docs.\nSave my markdown blogs.\n\n# Table of Contents\n<!--TableOfContnets Start-->\n* [AngularJS相关](AngularJS%E7%9B%B8%E5%85%B3)\n    * [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)\n    * [AngularJS官方FAQ.md](AngularJS%E7%9B%B8%E5%85%B3/AngularJS%E5%AE%98%E6%96%B9FAQ.md)\n    * [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)\n    * [AngularJS：Looking under the hood.md](AngularJS%E7%9B%B8%E5%85%B3/AngularJS%EF%BC%9ALooking%20under%20the%20hood.md)\n    * [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)\n    * [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)\n    * [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)\n    * [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)\n    * [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)\n    * [Angular开发Tips.md](AngularJS%E7%9B%B8%E5%85%B3/Angular%E5%BC%80%E5%8F%91Tips.md)\n    * [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)\n    * [[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)\n    * [用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)\n    * [详解angular之$q.md](AngularJS%E7%9B%B8%E5%85%B3/%E8%AF%A6%E8%A7%A3angular%E4%B9%8B$q.md)\n* [Angular系列](Angular%E7%B3%BB%E5%88%97)\n    * [01_Angular2初体验.md](Angular%E7%B3%BB%E5%88%97/01_Angular2%E5%88%9D%E4%BD%93%E9%AA%8C.md)\n    * [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)\n    * [03_Angular2的那些Decorator.md](Angular%E7%B3%BB%E5%88%97/03_Angular2%E7%9A%84%E9%82%A3%E4%BA%9BDecorator.md)\n    * [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)\n    * [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)\n    * [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)\n    * [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)\n    * [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)\n    * [09_Angular2使用ui-router-ng2.md](Angular%E7%B3%BB%E5%88%97/09_Angular2%E4%BD%BF%E7%94%A8ui-router-ng2.md)\n    * [Angular2踩坑大全.md](Angular%E7%B3%BB%E5%88%97/Angular2%E8%B8%A9%E5%9D%91%E5%A4%A7%E5%85%A8.md)\n    * [利用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)\n    * [跟我学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)\n* [C#](C#)\n    * [01_Dotnet Core尝鲜.md](C#/01_Dotnet%20Core%E5%B0%9D%E9%B2%9C.md)\n    * [02_Dotnet Core V2.md](C#/02_Dotnet%20Core%20V2.md)\n    * [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)\n    * [[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)\n    * [实战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)\n    * [实战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)\n    * [实战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)\n* [Canvas学习札记](Canvas%E5%AD%A6%E4%B9%A0%E6%9C%AD%E8%AE%B0)\n    * [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)\n* [CSS3学习之路](CSS3%E5%AD%A6%E4%B9%A0%E4%B9%8B%E8%B7%AF)\n    * [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)\n    * [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)\n    * [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)\n* [ES6入门](ES6%E5%85%A5%E9%97%A8)\n    * [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)\n    * [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)\n    * [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)\n    * [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)\n* [GoLang学习笔记](GoLang%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0)\n    * [01_开始GO.md](GoLang%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/01_%E5%BC%80%E5%A7%8BGO.md)\n* [jQuery拆解](jQuery%E6%8B%86%E8%A7%A3)\n    * [01-目录篇.md](jQuery%E6%8B%86%E8%A7%A3/01-%E7%9B%AE%E5%BD%95%E7%AF%87.md)\n    * [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)\n    * [03-基础结构.md](jQuery%E6%8B%86%E8%A7%A3/03-%E5%9F%BA%E7%A1%80%E7%BB%93%E6%9E%84.md)\n    * [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)\n* [JS札记](JS%E6%9C%AD%E8%AE%B0)\n    * [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)\n    * [JavaScript之毒瘤.md](JS%E6%9C%AD%E8%AE%B0/JavaScript%E4%B9%8B%E6%AF%92%E7%98%A4.md)\n    * [JavaScript之糟粕.md](JS%E6%9C%AD%E8%AE%B0/JavaScript%E4%B9%8B%E7%B3%9F%E7%B2%95.md)\n    * [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)\n    * [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)\n    * [[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)\n    * [那些不常见的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)\n    * [那些不常见的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)\n* [MongoDB入门基础](MongoDB%E5%85%A5%E9%97%A8%E5%9F%BA%E7%A1%80)\n    * [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)\n    * [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)\n* [Other](Other)\n    * [Go Go.md](Other/Go%20Go.md)\n    * [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)\n    * [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)\n    * [Thrift简单实践.md](Other/Thrift%E7%AE%80%E5%8D%95%E5%AE%9E%E8%B7%B5.md)\n    * [TypeScript札记：初体验.md](Other/TypeScript%E6%9C%AD%E8%AE%B0%EF%BC%9A%E5%88%9D%E4%BD%93%E9%AA%8C.md)\n    * [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)\n    * [开发者讨厌你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)\n    * [浅析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)\n    * [程序集强签名.md](Other/%E7%A8%8B%E5%BA%8F%E9%9B%86%E5%BC%BA%E7%AD%BE%E5%90%8D.md)\n* [PHP学习之路](PHP%E5%AD%A6%E4%B9%A0%E4%B9%8B%E8%B7%AF)\n    * [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)\n    * [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)\n* [React Native Cookbook](React%20Native%20Cookbook)\n    * [章节一-new.md](React%20Native%20Cookbook/%E7%AB%A0%E8%8A%82%E4%B8%80-new.md)\n    * [章节一.md](React%20Native%20Cookbook/%E7%AB%A0%E8%8A%82%E4%B8%80.md)\n* [React Native 开发笔记](React%20Native%20%E5%BC%80%E5%8F%91%E7%AC%94%E8%AE%B0)\n    * [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)\n    * [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)\n    * [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)\n    * [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)\n    * [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)\n* [React面面观](React%E9%9D%A2%E9%9D%A2%E8%A7%82)\n    * [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)\n    * [sources](React%E9%9D%A2%E9%9D%A2%E8%A7%82/sources)\n    * [【译】参考手册-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)\n    * [【译】快速起步-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)\n    * [【译】快速起步-事件处理.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)\n    * [【译】快速起步-列表与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)\n    * [【译】快速起步-条件渲染.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)\n    * [【译】快速起步-渲染元素.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)\n    * [【译】快速起步-状态和生命周期.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)\n    * [【译】快速起步-状态提升.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)\n    * [【译】快速起步-组件与属性.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)\n    * [【译】快速起步-组合与继承.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)\n    * [【译】快速起步-表单.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)\n    * [【译】高级指南-不受控组件.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)\n    * [【译】高级指南-深入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)\n    * [【译】高级指南-高阶组件.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)\n* [README.md](README.md)\n* [RxJS小记](RxJS%E5%B0%8F%E8%AE%B0)\n    * [02_RxJS之Observable.md](RxJS%E5%B0%8F%E8%AE%B0/02_RxJS%E4%B9%8BObservable.md)\n* [Sass学习之路](Sass%E5%AD%A6%E4%B9%A0%E4%B9%8B%E8%B7%AF)\n    * [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)\n    * [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)\n* [Vue实践之路](Vue%E5%AE%9E%E8%B7%B5%E4%B9%8B%E8%B7%AF)\n    * [01_认识Vue.md](Vue%E5%AE%9E%E8%B7%B5%E4%B9%8B%E8%B7%AF/01_%E8%AE%A4%E8%AF%86Vue.md)\n    * [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)\n* [_images](_images)\n* [从0开始Stylus](%E4%BB%8E0%E5%BC%80%E5%A7%8BStylus)\n    * [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)\n* [从零开始H5](%E4%BB%8E%E9%9B%B6%E5%BC%80%E5%A7%8BH5)\n    * [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)\n    * [从零开始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)\n    * [从零开始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)\n* [前端相关](%E5%89%8D%E7%AB%AF%E7%9B%B8%E5%85%B3)\n    * [CORS详解.md](%E5%89%8D%E7%AB%AF%E7%9B%B8%E5%85%B3/CORS%E8%AF%A6%E8%A7%A3.md)\n    * [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)\n    * [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)\n    * [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)\n    * [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)\n    * [JSONP详解.md](%E5%89%8D%E7%AB%AF%E7%9B%B8%E5%85%B3/JSONP%E8%AF%A6%E8%A7%A3.md)\n    * [JWT详解.md](%E5%89%8D%E7%AB%AF%E7%9B%B8%E5%85%B3/JWT%E8%AF%A6%E8%A7%A3.md)\n    * [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)\n    * [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)\n    * [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)\n    * [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)\n    * [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)\n    * [Webpack In Angular2.md](%E5%89%8D%E7%AB%AF%E7%9B%B8%E5%85%B3/Webpack%20In%20Angular2.md)\n    * [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)\n    * [Webpack小抄.md](%E5%89%8D%E7%AB%AF%E7%9B%B8%E5%85%B3/Webpack%E5%B0%8F%E6%8A%84.md)\n    * [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)\n    * [Yarn vs. Npm.md](%E5%89%8D%E7%AB%AF%E7%9B%B8%E5%85%B3/Yarn%20vs.%20Npm.md)\n    * [[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)\n    * [[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)\n    * [[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)\n    * [一个元素实现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)\n    * [再说Promise.md](%E5%89%8D%E7%AB%AF%E7%9B%B8%E5%85%B3/%E5%86%8D%E8%AF%B4Promise.md)\n    * [前端模块化：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)\n    * [如何用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)\n    * [探索Decorator.md](%E5%89%8D%E7%AB%AF%E7%9B%B8%E5%85%B3/%E6%8E%A2%E7%B4%A2Decorator.md)\n    * [浏览器 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)\n    * [浏览器关闭事件分析.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)\n    * [浏览器内容安全策略解析.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)\n    * [浏览器历史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)\n    * [简单学ES6.md](%E5%89%8D%E7%AB%AF%E7%9B%B8%E5%85%B3/%E7%AE%80%E5%8D%95%E5%AD%A6ES6.md)\n    * [认识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)\n    * [记一次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)\n    * [说说如何部署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)\n    * [这些年我们处理过的跨域.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)\n    * [那些容易出错的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)\n    * [那些年我们认识的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)\n* [微信小程序](%E5%BE%AE%E4%BF%A1%E5%B0%8F%E7%A8%8B%E5%BA%8F)\n* [数据库之路](%E6%95%B0%E6%8D%AE%E5%BA%93%E4%B9%8B%E8%B7%AF)\n    * [[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)\n    * [说说你所熟知的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)\n* [最佳实践系列](%E6%9C%80%E4%BD%B3%E5%AE%9E%E8%B7%B5%E7%B3%BB%E5%88%97)\n    * [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)\n* [正则表达式](%E6%AD%A3%E5%88%99%E8%A1%A8%E8%BE%BE%E5%BC%8F)\n    * [你真的理解正则修饰符吗.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)\n* [测试相关](%E6%B5%8B%E8%AF%95%E7%9B%B8%E5%85%B3)\n    * [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)\n    * [使用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)\n    * [利用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)\n    * [利用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)\n* [编写高质量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)\n    * [[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)\n    * [[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)\n    * [[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)\n    * [[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)\n    * [[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)\n    * [[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)\n    * [[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)\n    * [[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)\n    * [[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)\n    * [[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)\n    * [[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)\n    * [[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)\n    * [[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)\n* [运维&部署](%E8%BF%90%E7%BB%B4&%E9%83%A8%E7%BD%B2)\n    * [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)\n    * [一个简单易用的容器管理平台-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)\n    * [前端监控系统实现.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)\n    * [记一次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)\n<!--TableOfContnets End-->\n\n\n# The End\n"
  },
  {
    "path": "React Native Cookbook/章节一-new.md",
    "content": "# 章节一、React Native 工具链\n\nReact Native 身处由数十种软件工具构成的生态系统中。包含转换器（Babel，Metro 以及 Webpack），包管理工具（NPM，Yarn），检查器，单元测试框架等。本章将介绍语言基础以及需要在您的 React Native 项目中使用到的最小化的开源工具。您可以使用常规的 JavaScript 或者是一些编译到 JavaScript 的语言，如 TypeScript 或者 ES6+ 来编写 React Native 应用。我希望本章能帮助您了解 JavaScript 惊人的速度。\n\n> **Expo**\n>\n> 最近，React Native 团队与 Expo 合作推出了不需要本地环境支持的开发环境。这是尝试和探索 React Native 非常棒的方式，但是当你想在硬件上运行应用时，一个本地开发环境将至关重要。\n\n## 1.1 配置您的开发环境\n\n如果您也在其他 Web 项目中使用这些工具，您可能会自行排查您的环境。就像木匠在工作现场一样，您需要知道所有的工具是如何运行的以及它们是否需要被调整。React Native 是一个包含 Node.js 、iOS 以及 Android 这三个编程环境的软件包。同时，Node 的包管理工具 NPM，也是需要用到的。\n\n### 问题\n\nReact Native 是一个依赖许多不同工具的软件包。我们如何能确保它们都配置正确呢？拭目以待。\n\n#### Node 和 Watchman\n\nNode.js （通常称之为 Node）可以让您的计算机像 Web 浏览器运行 JavaScript 一样运行本地 JavaSript。由于 Node.js 直接在您的操作系统上运行，因此 Node 可以包装或者绑定 C 语言库，同时也能像 `PHP、Python、Perl、Ruby` 一样，解决相同的编程问题。\n\nWatchman 则是一个用于监控本地文件变更并触发对应事件的小工具。使用它可以再您的模拟器上执行更新后的代码而不需要重新编译整个项目。同时，也可以非常简单快速的安装它。\n\n> **安装 Node.js**\n>\n> 如何安装 Node 取决于您的操作系统，最好的方式是参 [Node.js 官方网站](https://nodejs.org/en/)。如果您使用 Mac OS 系统，您可以使用 Mac OS 的包管理工具 `Homebrew` 来安装 Node.js。\n\n#### 检查 Node.js 是否正确安装\n\n您可能需要在您的电脑上安装多个版本的 Node.js。类似 Node 版本管理器（NVM）这样的工具能够让您在计算机上安装多个版本的 Node.js，这样每个开发项目都可以配置自己的 Node 版本。\n\nPOSIX 风格的操作系统（Linux,BSD,Mac OS） 能够使用符号链接（软链接）来支持多版本的 Node.js。\n\n如果您在 Mac OS 上使用 `Homebrew` 安装了两个版本的 Node，也不要诧异。在列出的目录中，除了您自己的用户名和日期信息之外，应该还有如下一些内容：\n\n```bash\n$> which node\n/usr/local/bin/node\n$> node -v\nv8.6.0\n```\n\n我正在使用 `8.6.0` 版本的 Node；不过，如果我检查 Homebrew 的目录（默认是 /usr/local/Cellar），将能够找到一个符号链接（指向到实际的 Node 地址）：\n\n```bash\n$>ls -l /usr/local/bin/node\nlrwxr-xr-x 1 jon admin 29 27 Sep 15:14 /usr/local/bin/node -> ../Cellar/node/8.6.0/bin/node\n```\n\n更深入一点，我们可以找到被取代的其他版本的 Node。\n\n```bash\n$>ls -l /usr/local/Cellar/node\ntotal 0\ndrwxr-xr-x 14 jon admin 476 11 May 14:14 7.10.0\ndrwxr-xr-x 14 jon admin 476 25 Apr 13:41 7.9.0\ndrwxr-xr-x 14 jon admin 448 27 Sep 15:14 8.6.0\n```\n\n您计算机上的结果可能有所不同；不过，重要的是您已经安装成功了一个最近版本的 Node，并能够在您的项目中使用。\n\n#### NPM\n\nNPM 包含一个命令行运行的包管理工具以及一个全面的开源软件包仓库。NPM 中的 `react-native` 软件包包含了依赖特定平台的 JavaScript ES6 模块代码。例如 `<Text />` 组件在 `iOS` 上是基于 `RCTText.m`的，在 `Android` 上是基于 `ReactTextView.java`的。\n\n> **如何使用 Yarn？**\n>\n> React Native 的历史版本使用 NPM，但 Yarn 在 JavaScript 社区中也很流行。Yarn 是仍然依赖于 npm 仓库一个更快的改进。 `yarn.lock` 能够确保依赖得到正确的维护。Yarn 将优先检查 `yarn.lock`，其次检查 `package.json`，实现无缝向 Yarn 过渡。\n\nNPM 包既可以全局运行，也可以在给定项目的 `node_modules` 目录下运行。最好是全局安装 React Native，本地安装项目相关的依赖。这种方式可以让你在任何地方运行 React Native 的命令行工具 `react-native-cli`。React Native 的特定版本则是项目依赖项的一部分。（译者注：使用 `npm i -g react-native-cli` 全局安装命令行工具，使用 `npm i -S react-native` 本地安装 React Native 框架）\n\n#### 检查 NPM 是否正确安装\n\n```bash\n$> which npm\n/usr/local/bin/npm\n```\n\n您的终端（控制台）将返回一个路径。使用如下命令检查 npm 版本：\n\n```bash\n$> npm -v\n4.2.0\n```\n\n#### 安装 React Native 命令行工具\n\n```bash\nnpm install -g react-native-cli\n```\n\n#### Xcode（iOS 开发需要）\n\nXcode 的 Apple 的官方开发环境，用于构建和运行 Mac OS 和 iOS 的应用程序。你需要安装 Xcode （仅适用于 Mac OS）（译者注：要使用 XCode，请掏钱买 Mac）以编译由 Objective-C 和 Swift 编写的 React Native 组件。\n\nXcode 还附带了命令行工具，这些通过 Node.js 绑定到 Mac OS 的命令行工具是构建代码所必须的。\n\n> **运行 XCode Beta**\n>\n> 随着 iOS 的定期更新，在您的开发机器上，可能会有测试版本的 XCode。多个版本的 Xcode 将会导致模拟器也有多个版本。这种情况下，最好是使用 Xcode 而不是使用命令行（译者注：命令行是指 `react-native run-ios`）来启动模拟器。\n\n#### JDK\n\nAndroid 与 Java 就像糖和黄油，组合在一起将变得美味无比。Android 和 React Native 也不例外。使用 JavaScript 编写的 React 组件能够触及到 Android Java 虚拟机。为了能够本地运行 Android，需要安装 Java Development Kit（JDK）。\n\n[从 Oracle 的官网下载 JDK(至少下载 8 以上版本](http://www.oracle.com/technetwork/java/javase/downloads/index.html)\n\n#### Android Studio\n\nAndroid Studio 是免费的用于构建和部署 Android 应用程序的官方开发环境。一旦用上了它，随之而来的是另一个包管理工具。幸运的是，“React Native 入门指南” 提供了详细步骤去了解它。\n\n## 1.2 通过 Babel 来编写 ES6\n\nBabel 将 20 年的编程语言，带到了 21 世纪。通过 Babel，您可以使用增强的语法来编写 JavaScript，让您的 JavaScript 代码变得更流畅且更具表现力。它将诸如 数据结构转换、处理作用域中的 this 以及类继承等常见模式编程了语言本身的一部分。\n\n通过一系列语法转换，Babel 实现了对语言本身的语法高进。每个转换器都会运行您的代码，将新的 ES6 语言特性转换为等价的 JavaScript 语法。\n\n通过 `react-native` 预设（译者注：`npm i babel-preset-react-native -D`），以下 ES6 代码将会被自动转换。\n\n将以下内容保存到 `babel-transform.js` 文件中：\n\n```js\nAsyncStorage.getItem('loginParameters').then(login => {\n  this.setState({ login });\n});\n```\n\n通过命令行执行：\n\n```bash\n$> babel babel-transform.js\n```\n\nBabel 将返回（为了可读性，已格式化）：\n\n```js\nvar _this = this;\nAsyncStorage.getItem('loginParams').then(function(login) {\n  _this.setState({\n    login: login\n  });\n});\n```\n\nReact Native 完成了以下事情：\n\n1.  展开 `{ login }` 到 `{ login: login }`\n2.  在外部方法中定义 `_this`，并替换 `=>` 运算符\n"
  },
  {
    "path": "React Native Cookbook/章节一.md",
    "content": "# 章节一、入门\n\n在本章中，将包含以下内容：\n\n* 在文本和容器上添加样式\n* 使用图片来模仿一个视频播放器\n* 创建一个切换按钮\n* 将数据项作为列表展示\n* 在视窗中添加选项卡\n* 使用 Flexbox 布局来创建个人资料页\n* 设置导航\n\n## 简介\n\n**React Native** 是一个快速演进的库。在过去的一年中，它在开源社区中变得非常受欢迎。每隔一周，就会有一个性能更优，组件或者设备 API 更多的新版本产出。\n\n虽然快速演进在大部分时候都是优势，不过有时候也会成为一个缺点。有时新版本会带来一些破坏性更新，这要求我们在更新库版本时，必须非常小心。一般情况下，破坏性变更以及重大变化都会记录在发行说明中，在更新版本的时候，请务必阅读发行说明。最重要的是，我们也要确保所有在项目中的使用的第三方库必须与新版本保持一致。\n\n在本章中，我们将学习库中最常见的组件。阅读本书的前提是已完成 React Native 官方文档入门级别的学习，这也意味着我们不会花时间在安装环境和编写 Hello World 示例上。\n\n为了贯穿整本书的示例，我们将创建一个新的 React Native 应用程序，请确保已正确处理好你的开发环境。推荐参考 React Native 官方网站搭建环境，然后在控制台上执行如下一些命令，创建名为 `AnAppName` 的应用程序：\n\n```bash\n$ react-native init --verbose AnAppName # 初始化AnAppName项目\n$ cd AnAppName # 进入AnAppName目录\n$ react-native run-android # 启动Android开发环境\n$ react-native run-ios # 启动iOS开发环境（要求Mac OS）\n```\n\n## 为文本和容器添加样式\n\n我们有若干组件可供使用，但其中用来创建布局或其他组件的最常见和有用的组件则是容器组件和文本年组建。在这个小节中，我们将学习如何使用容器组件和文本组件，更最重要的是，我们将看到样式是如何在 React Native 中的工作的。\n\n我们将尝试创建一个简单的音乐播放器界面；暂时，我们先不使用图标，后续我们再来添加图标。\n\n### 前提\n\n请按照介绍中的步骤进行操作，以创建一个名为 `ContainersText` 的应用程序。\n\n### 实现\n\n1.  在项目根目录下创建 `src` 目录，用来放置我们的 `JavaScript` 代码。\n2.  在 `src` 目录中，创建 `MainApp.js` 文件。\n3.  在 `MainApp.js` 文件中，创建一个无状态组件，用来模仿一个简单的音乐播放器。目前，它仅能够显示歌曲名称和进度。\n\n```js\nimport React from 'react';\nimport { StyleSheet, Text, View } from 'react-native';\n```\n\n4.  一旦我们导入了对应的依赖，我们可以继续编写组件：\n\n```js\nconst MainApp = () => {\n  const name = '01 - Hey, this is my life';\n  return (\n    <View style={styles.container}>\n      <View style={styles.innerContainer} />\n      <Text style={styles.title}>\n        <Text style={styles.subtitle}>Playing:</Text> {name}\n      </Text>\n    </View>\n  );\n};\n```\n\n5.  至此，我们已经准备好了我们的组件。接下来我们需要通过一些样式代码来添加颜色和字体样式：\n\n```js\nconst styles = StyleSheet.create({\n  container: {\n    margin: 10,\n    marginTop: 100,\n    backgroundColor: '#e67e22',\n    borderRadius: 5\n  },\n  innerContainer: {\n    backgroundColor: '#d35400',\n    height: 50,\n    width: 150,\n    borderTopLeftRadius: 5,\n    borderBottomLeftRadius: 5\n  },\n  title: {\n    fontSize: 18,\n    fontWeight: '200',\n    color: '#fff',\n    position: 'absolute',\n    backgroundColor: 'transparent',\n    top: 12,\n    left: 10\n  },\n  subtitle: {\n    fontWeight: 'bold'\n  }\n});\n```\n\n6.  为了在其他文件中调用该组件，我们需要将它导出导出，如下：\n\n```js\nexport default MainApp;\n```\n\n7.  下一步就是将我们的新组件导入 `index.ios.js` 和 `index.android.js` 中。在这两个平台，代码都如下所示：\n\n```js\nimport React, { Component } from 'react';\nimport { AppRegistry } from 'react-native';\nimport MainApp from './src/MainApp';\nAppRegistry.registerComponent('ContainersText', () => MainApp);\n```\n\n8.  为了在模拟器中看到程序的变化，我们需要重新加载应用。在 `iOS`，使用 `Command + R` 即可，在`Android` 中，需要摇晃模拟器，呼出菜单按钮，然后点击刷新。\n\n### 原理分析\n\n让我们回顾下在上一个小节中，我们做了些什么。在步骤 3~6 中，我们创建了包含样式的组件。我们继续挖掘这些步骤中的细节。\n\n在步骤 3 中，我们引入了组件的依赖。我们会使用一个容器组件 `View`，如果您熟悉 Web 开发，那么可以把 `View` 理解为 `div`。可以在其他视图、文本、列表以及我们创建或者从三方库导入的自定义组件中嵌套 `View`。\n\n在步骤 4 中，我们定义了名为 `MainApp` 的组件。我们约定文件和组件应该使用相同的名称。`MainApp` 是不包含任何状态的无状态组件，它是一个纯函数且不支持任何生命周期函数。另外，我们定义了一个名为 `name` 的常量，实际应用中，这个属性应该通过 `props` 进行传递。在返回值中，我们用 `JSX` 定义了需要呈现的组件以及对样式的引用。\n\n每个组件都有一个名为 `style` 的属性，这个属性用来接收我们想附加给组件的样式对象。除文本组件外，样式并不会被继承，所以，我们需要会每个组件设置样式。\n\n在步骤 5 中，我们给组件定义了样式。可以看到，我们使用 `StyleSheet` API 来定义样式。如前所述，需要定义一个包含样式的对象；通过 `StyleSheet` API 来创建样式，有利于性能优化，因为样式将在每次渲染时被重用，而不是每次渲染时再创建一个对象。\n\n样式对象中的属性非常简单。如果你是一个 Web 开发者，那么更容易理解这一点，它和 CSS 属性很像。但要注意，并不是完全一样。在 `React Native` 的样式中，有 `margin padding width height borderWidth borderColor borderRadius`等等，要查看所有属性，建议查阅 `React Native` 文档。\n\n在步骤 7 中，我们导入了新实现的组件，并用来作为根组件来引导我们的 APP。\n\n### 更多\n\n注意步骤 5 中关于标题样式的定义。在 `title` 中，有一个 `backgroundColor=transparent` 的样式。我们注释掉这句代码，看看会发生什么：\n\n在 `iOS` 中，文本显示了一个非预期的橙色背景。为了解决这个问题，所以需要设置背景色为透明。但问题是，为什么会出现这种情况呢？原因是在 `React Native` 中，使用父元素的背景来作为文本的背景进行渲染，能够改善渲染性能，因为渲染引擎不需要计算文本每个字母周围的像素，同时也会渲染得更快。\n\n**注意**\n\n请认真考虑是否有必要将文本背景设置为透明。如果组件内容更新频繁，这将会带来一些性能问题，特别是文本特别长的时候。\n\n## 使用图像来模拟视频播放器\n\n### 前提\n\n请先通过 `React Native CLI` 程序创建一个名为 `LoadingImages` 的空白 App。如果您还不知道如何创建，请参考本章介绍中的说明进行操作。\n\n### 实现\n\n1.  首先，是创建 `src` 文件夹，并在这个文件夹中，创建 `MainApp.js` 文件和用来存储图标的图片目录。我们的项目结构如图所示：\n\n2.  在 `MainApp.js` 中，引入相关依赖：\n\n```js\nimport React from 'react';\nimport { StyleSheet, View, Image } from 'react-native';\n```\n\n3.  通过 `require` 来引入需要在组件中使用到的图片。一般情况下，通过定义一个常量来保存图片，可以让我们在不同的地方使用同一个图像。有时候，我们需要重新启动 package server 来正确加载图像，特别是在 `Windows` 下。\n\n```js\nconst playIcon = require('./images/play.png');\nconst volumeIcon = require('./images/sound.png');\nconst hdIcon = require('./images/hd-sign.png');\nconst fullScreenIcon = require('./images/full-screen.png');\nconst remoteImage = {\n  uri: 'https://s3.amazonaws.com/crysfel/public/book/new-york.jpg'\n};\n```\n\n4.  使用无状态组件来渲染 `JSX`。同时，将用到我们在前一步中申明的图片：\n\n```js\nconst MainApp = () => {\n  return (\n    <Image source={remoteImage} style={styles.fullscreen}>\n      <View style={styles.container}>\n        <Image source={playIcon} style={styles.icon} />\n        <Image source={volumeIcon} style={styles.icon} />\n        <View style={styles.progress}>\n          <View style={styles.progressBar} />\n        </View>\n        <Image source={hdIcon} style={styles.icon} />\n        <Image source={fullScreenIcon} style={styles.icon} />\n      </View>\n    </Image>\n  );\n};\n```\n\n5.  一旦有了要呈现的元素，则需要为每个元素定义样式：\n\n```js\nconst styles = StyleSheet.create({\n  fullscreen: {\n    flex: 1\n  },\n  container: {\n    position: 'absolute',\n    backgroundColor: '#202020',\n    borderRadius: 5,\n    flexDirection: 'row',\n    height: 50,\n    padding: 5,\n    paddingTop: 16,\n    bottom: 30,\n    right: 10,\n    left: 10,\n    borderWidth: 1,\n    borderColor: '#303030'\n  },\n  icon: {\n    tintColor: '#fff',\n    height: 16,\n    width: 16,\n    marginLeft: 5,\n    marginRight: 5\n  },\n  progress: {\n    backgroundColor: '#000',\n    borderRadius: 7,\n    flex: 1,\n    height: 14,\n    margin: 10,\n    marginTop: 2\n  },\n  progressBar: {\n    backgroundColor: '#bf161c',\n    borderRadius: 5,\n    height: 10,\n    margin: 2,\n    width: 80\n  }\n});\n```\n\n6.  为了使用该组件，需要先导出它。仅仅需要一行代码，如下：\n\n```js\nexport default MainApp;\n```\n\n7.  最后，将我们的新组件导入到 index.ios.js 和 index.android.js 中：\n\n```js\nimport React, { Component } from 'react';\nimport { AppRegistry } from 'react-native';\nimport MainApp from './src/MainApp';\nAppRegistry.registerComponent('LoadingImages', () => MainApp);\n```\n\n8.  完成！只需要刷新模拟器上的 App，就可以看到如下界面：\n\n### 原理分析\n\n在步骤 2 中，我们引入了用来渲染本地文件或者是远端服务器图片的 `Image` 组件。\n\n在步骤 3 中，我们引入了所有的图像。通过 `require` 加载所有的图像是一种很好的做法，我们仅仅需要引入一次，就可以在我们的组件中使用它们。同时，在每次渲染中，`React Native` 都将使用同一个图像。如果想显示来自远端的动态图像，那么就需要在每次渲染中去请求。\n\n`require` 方法接受图片路径作为参数，支持相对路径。如果是远程图片，则需要通过 `uri` 来指向图片地址。\n\n在步骤 4 中，定义了一个无状态组件。在该组件中，使用 `remoteImage` 作为背景图像。为了将该图片设置为背景，需要将其他元素定义在 `Image` 组件内部。正如 CSS 一样，并没有 `backgroundUrl` 属性。\n\n`Image` 的 `source` 属性接受一个对象来加载远程图片或图片应用。明确 `require` 每张图片，是非常重要的，因为当我们发布时，只要 `require` 的图片才会被自动加入到发布包中。所以，我们应该避免编写如下代码：\n\n```js\nconst iconName = playing ? 'pause' : 'play';\nconst icon = require(iconName);\n```\n\n上述代码的发布包中将不会包含相关图片。因此，当我们尝试访问这些图像时将会出错。反之，我们应该采用如下的代码来重构：\n\n```js\nconst pause = require('pause');\nconst play = require('playing');\nconst icon = playing ? pause : play;\n```\n\n这样，在打包应用程序时，将会包含这两个图片，同时，也能动态的选择展示哪个图片。\n\n在步骤 5 中，我们定义了样式。大多数属性都是不言自明的。`tintColor` 可能会有点特别，这个属性是用来设置图片的颜色的。在此处的代码是将图片颜色设置为白色。我们还在后续的内容中单独讲解 `flex` 布局，在这里，我们只需要知道 `flexDirection: 'row'` 表示图标水平对齐。\n\n在步骤 7 中，将 `MainApp` 导入到 `iOS` 和 `Android` 的引导程序中。之后，就可以在模拟器中运行我们的 App 了。\n\n### 更多\n\n在这个配方中，我们使用了 `flexbox` 布局来水平排列播放器控制按钮。如果你想了解更多有关 `Flexbox`的内容，请查看后续 `使用Flexbox创建个人资料页面`。对于更高级的内容，可以阅读第二章“实现复杂的用户界面”。\n\n## 创建一个切换按钮\n\n按钮是每个 App 中不可或缺的 UI 组件。按钮可以用于导航、触发 API 调用等。在本小节中，我们将创建一个切换按钮，默认是取消选中，当用户点击时，则通过修改样式使之看起来像是被选中的。本小节中，我们将学习如何检测点击事件，使用图片作为 UI，保持按钮状态，并根据组件状态添加样式。\n\n### 前提\n\n使用 `React Native CLI` 创建名为 `ButtonsAndEvents` 的 App。在本小节内容中，会使用到一个图片，请务必下载该小节所需的图片或者随意使用您自己的图片。\n\n### 实现\n\n1.  首先，是创建 `src` 文件夹来存储源码，并在这个文件夹中，创建 `MainApp.js` 文件和图片目录：\n\n2.  导入该类的依赖：\n\n```js\nimport React, { Component } from 'react';\nimport { StyleSheet, View, Image, Text, TouchableHighlight } from 'react-native';\nconst heartIcon = require('./images/plain-heart.png');\n```\n\n3.  在该小节中，我们需要在按下时保持按钮的状态。因此，需要创建一个继承自 `Component` 的类，如下：\n\n```js\nclass MainApp extends Component {\n  state = { liked: false };\n  _onPressBtn = () => {\n    // We will define the content on step 6\n  };\n  render() {\n    // We will define the content on step 4\n  }\n}\n```\n\n4.  在 `render` 中，定义组建的内容。此处，需要定义一个图片按钮和一个文本：\n\n```js\nrender() {\n  return (\n  <View style={styles.container}>\n    <TouchableHighlight\n    style={styles.btn}\n    underlayColor=\"#fefefe\"\n    >\n      <Image\n      source={heartIcon}\n      style={styles.icon}\n      />\n    </TouchableHighlight>\n    <Text style={styles.text}>Do you like this app?</Text>\n  </View>\n  );\n}\n```\n\n5.  定义一些样式来设置 `set dimensions, position, margins, colors` 等等：\n\n```js\nconst styles = StyleSheet.create({\n  container: {\n    marginTop: 50,\n    alignItems: 'center'\n  },\n  btn: {\n    borderRadius: 5,\n    padding: 10\n  },\n  icon: {\n    width: 180,\n    height: 180,\n    tintColor: '#f1f1f1'\n  },\n  liked: {\n    tintColor: '#e74c3c'\n  },\n  text: {\n    marginTop: 20\n  }\n});\n```\n\n6.  如果在模拟器中运行，应该能看到如下图所示内容：\n\n7.  为了响应 tap 事件，我们需要定义 `_onPressBtn` 的处理函数并将其作为回调函数分配给 `onPress` 属性：\n\n```js\nclass MainApp extends Component {\n  state = {\n    liked: false\n  };\n  _onPressBtn = () => {\n    this.setState({\n      liked: !this.state.liked\n    });\n  };\n  render() {\n    return (\n      <View style={styles.container}>\n        <TouchableHighlight onPress={this._onPressBtn} style={styles.btn} underlayColor=\"#fefefe\">\n          <Image source={heartIcon} style={styles.icon} />\n        </TouchableHighlight>\n        <Text style={styles.text}>Do you like this app?</Text>\n      </View>\n    );\n  }\n}\n```\n\n8.  如果此时按下按钮，就算组件状态改变成 `ON`，也不会在 UI 上看到任何更改。只有当我们给它添加不同状态的样式时，才会看到 UI 有响应：\n\n```js\nrender() {\n  const likedStyles = this.state.liked ? styles.liked : null;\n  return (\n    <View style={styles.container}>\n      <TouchableHighlight\n      onPress={this._onPressBtn}\n      style={styles.btn}\n      underlayColor=\"#fefefe\"\n      >\n        <Image\n        source={heartIcon}\n        style={[styles.icon, likedStyles]}\n        />\n      </TouchableHighlight>\n      <Text style={styles.text}>Do you like this app?</Text>\n    </View>\n  );\n}\n```\n\n9.  差不多完成这个类了，唯一缺少的是导出组件。使用如下代码进行导出：\n\n```js\nexport default MainApp;\n```\n\n10. 最后，更新 `index.ios.js` 以及 `index.android.js` 来导入和使用我们的新组件：\n\n```js\nimport React, { Component } from 'react';\nimport { AppRegistry } from 'react-native';\nimport MainApp from './src/MainApp';\nAppRegistry.registerComponent('ButtonsAndEvents', () => MainApp);\n```\n\n### 原理分析\n\n在步骤 2 中，导入了 `TouchableHighlight` 组件，用来处理触摸事件。\n\n当用户触摸活动区域时，区域内容将通过我们设置的 `underlayColor` 样式高亮显示。\n\n在步骤 3 中，定义了一个有状态组件。在本小节中，`state` 仅仅包含一个属性，但我们可以根据需要添加更多的状态。在第二章“实现复杂的用户界面”中，我们将在更复杂的场景中看到更多的状态处理。\n\n在步骤 6 中，使用 `setState` 来更新 `liked` 属性的值。这个方法正是从 `Component` 类中继承而来。\n\n在步骤 7 中，根据当前 `state` 的 `liked` 属性来设置图片颜色为红色，通过返回 `null` 来避免应用任何样式。使用数组来组合多个对象样式对于应用样式非常方便，在内部，组件会将所有样式合并为一个对象。后定义的属性将会覆盖先定义的同名属性。\n\n### 更多\n\n在一个真实的 App 中，我们将可能使用多种按钮，图标左对齐，带标签，不同的尺寸和颜色等等。此时，强烈建议您创建可复用的组件，以避免在 App 中到处重复代码。在第二章“实现复杂的用户界面”中，我们将创建一个按钮来组件来处理其中的一些场景。\n\n## 显示列表元素\n\n**列表无处不在！**如用户的历史订单列表、商店中的可用商品、待播放的歌曲列表等；基本上，任何 App 都需要用到列表。\n\n在这个小节中，我们将使用列表组件显示几个 Item。先将一些数据定义成 JSON 文件，然后使用 `require` 来加载这个文件，最终将其渲染为一个漂亮又简洁的列表布局。\n\n### 前提\n\n首先，创建一个名为 `ListItems` 的空 App。为了在列表的每个 Item 中显示 icon 图标，请下载图片资源或使用您自己的 `.png` 图片资源。\n\n### 实现\n\n1.  在项目中创建 `src` 目录，然后在 `src` 目录中创建 `MainApp.js` 以及 `sales.json`：\n\n2.  我们将要显示的列表数据定义在 `sales.json` 中，示例数据如下：\n\n```json\n[{ \"items\": 5, \"address\": \"140 Broadway, New York, NY 11101\", \"total\": 38, \"date\": \"May 15, 2016\" }]\n```\n\n3.  为了避免占用大量篇幅，我只定义了一条数据，您可以往数据数组中加入更多内容。使用 `Ctrl+C Ctrl+V` 来创建更多元素，另外，可以修改其中的一些数值。\n\n4.  找到 `index.ios.js` 和 `index.android.js` 文件，移除掉已有的代码，添加如下代码来导入依赖和注册 App：\n\n```js\nimport React, { Component } from 'react';\nimport { AppRegistry } from 'react-native';\nimport MainApp from './src/MainApp';\nAppRegistry.registerComponent('ListItems', () => MainApp);\n```\n\n5.  在上一步中，虽然导入了 `MainApp`，但实际上并没有定义。打开 `src/MainApp.js`，然后导入如下依赖：\n\n```js\nimport React, { Component } from 'react';\nimport { StyleSheet, View, ListView, Image, Text } from 'react-native';\nimport data from './sales.json';\nconst basketIcon = require('./images/basket.png');\n```\n\n6.  现在需要创建一个类来渲染列表。将销售数据放到 `state` 中，可以让我们很轻松的插入或删除数据：\n\n```js\nclass MainApp extends Component {\n  constructor(props) {\n    super(props);\n    var ds = new ListView.DataSource({\n      rowHasChanged: (r1, r2) => r1 !== r2\n    });\n    this.state = {\n      dataSource: ds.cloneWithRows(data)\n    };\n  }\n  renderRow(record) {\n    // Defined on step 8\n  }\n  render() {\n    // Defined on step 7\n  }\n}\nexport default MainApp;\n```\n\n7.  我们需要在 `render` 方法中，使用 `ListView` 组件，并使用 `renderRow` 方法来渲染每个单独的 Item。`dataSource` 属性定义了需要在列表中呈现的数组元素：\n\n```js\nrender() {\n  return (\n    <View style={styles.mainContainer}>\n      <Text style={styles.title}>Sales</Text>\n      <ListView\n        dataSource={this.state.dataSource}\n        renderRow={this.renderRow}/>\n    </View>\n  );\n}\n```\n\n8.  现在，我们来补充 `renderRow` 方法的内容。这个方法接收包含所需信息单个对象。我们将要显示三个数据列。第一列中，显示 Icon 图标，第二列中每次销售的商品数量和该订单的发货地址，第三列中将售出日期和总价：\n\n```js\nrenderRow(record) {\n  return (\n    <View style={styles.row}>\n      <View style={styles.iconContainer}>\n        <Image source={basketIcon} style={styles.icon} />\n      </View>\n      <View style={styles.info}>\n        <Text style={styles.items}>{record.items} Items</Text>\n        <Text style={styles.address}>{record.address}</Text>\n      </View>\n      <View style={styles.total}>\n        <Text style={styles.date}>{record.date}</Text>\n        <Text style={styles.price}>${record.total}</Text>\n      </View>\n    </View>\n  );\n}\n```\n\n9.  当我们定义好 JSX 之后，是时候添加样式了。首先，需要为主容器、标题以及行容器定义颜色、外边距、内边距等等。为了为每个列创建三列布局，需要使用到 `flexDirection` 中的 `row` 属性。我们将在本章其他小节中学到更多关于该属性的知识：\n\n```js\nconst styles = StyleSheet.create({\n  mainContainer: {\n    flex: 1,\n    backgroundColor: '#fff'\n  },\n  title: {\n    backgroundColor: '#0f1b29',\n    color: '#fff',\n    fontSize: 18,\n    fontWeight: 'bold',\n    padding: 10,\n    paddingTop: 40,\n    textAlign: 'center'\n  },\n  row: {\n    borderColor: '#f1f1f1',\n    borderBottomWidth: 1,\n    flexDirection: 'row',\n    marginLeft: 10,\n    marginRight: 10,\n    paddingTop: 20,\n    paddingBottom: 20\n  }\n});\n```\n\n10. 当我们刷新模拟器时，我们可以看到如下一些截图：\n\n11. 现在，在 `StyleSheet` 的定义中，我们来给 icon 图标添加样式。添加一个黄色圆圈作为背景，并将图标的颜色设定为白色：\n\n```js\niconContainer: {\n  alignItems: 'center',\n  backgroundColor: '#feb401',\n  borderColor: '#feaf12',\n  borderRadius: 25,\n  borderWidth: 1,\n  justifyContent: 'center',height: 50,\n  width: 50,\n},\nicon: {\n  tintColor: '#fff',\n  height: 22,\n  width: 22,\n},\n```\n\n12. 应用这些变更后，我们可以每行的左侧有一个好看的 icon 图标，如下图所示：\n\n13. 最后，为文本设置样式。需要设置颜色、字体、字号、内边距以及其他一些样式：\n\n```js\ninfo: {\n  flex: 1,\n  paddingLeft: 25,\n  paddingRight: 25,\n},\nitems: {\n  fontWeight: 'bold',\n  fontSize: 16,\n  marginBottom: 5,\n},\naddress: {\n  color: '#ccc',\n  fontSize: 14,\n},\ntotal: {\n  width: 80,\n},\ndate: {\n  fontSize: 12,\n  marginBottom: 5,\n},\nprice: {\n  color: '#1cad61',\n  fontSize: 25,\n  fontWeight: 'bold',\n},\n```\n\n14. 最终结果应该类似下图所示：\n\n### 原理分析\n\n在步骤 6 中，创建了数据源并添加到了 `state` 中。`ListView.DataSource` 为 `ListView` 组件提供了高性能的数据处理。`rowhHasChanged` 是一个必须的属性，它是一个用来比较数据元素是否变化的函数。\n\n我们需要调用 `cloneWithRows` 来将数据填充到数据源，并传递给组件。\n\n如果要添加更多数据，我们应该再次调用 `cloneWithRows` 包含旧数据和新数据。数据源将计算差异并在必要时重新渲染。\n\n在步骤 7 中，使用 `render` 方法来渲染列表。步骤 6 中的数据源以及 `renderRow` 方法是两个必备的属性。\n\n`renderRow` 是为每个行返回 JSX 的函数。\n\n### 更多\n\n我们使用 `flexbox` 创建了一个简单的布局；然而，在本章中还有另外的小节将深入的探讨使用 `flexbox` 的更多细节。\n\n一旦有了列表，我们就应该有机会去看到每个订单的细节。此时，我们应该使用 `TouchableHighlight` 组件作为每行的主容器，所以，请继续尝试。如果还不确定如何使用 `TouchableHighlight` 组件，请参考本章节中“创建一个切换按钮”。\n\n## 在视窗中添加选项卡\n\n`Tabs（选项卡）` 是一个非常常见的组件，特别是在 `iOS` App 中。在本小节中，我们来学习如何在 `iOS` 中使用选项卡组件。截止目前，还并不支持 `Android`，如果真的需要在 `Android` 中使用选项卡，请选用第三方组件库来添加类似功能。\n\n### 实现\n\n1.  首先，导入该组件的所有依赖项以及需要使用到的图标：\n\n```js\nimport React, { Component } from 'react';\nimport { StyleSheet, View, Image, Text, TabBarIOS } from 'react-native';\nconst homeIcon = require('./images/home.png');\nconst favIcon = require('./images/star.png');\nconst blogIcon = require('./images/notebook.png');\nconst profileIcon = require('./images/user.png');\n```\n\n2.  为了选中一个选项卡，应该在 `state` 中存储当前选中状态，因此我们需要使用类来定义组件，如下：\n\n```js\nclass MainApp extends Component {\n  state = {\n    selected: 'home'\n  };\n  selectTab(id) {\n    // Defined on step 5\n  }\n  renderTab(options) {\n    // Defined on step 4\n  }\n  render() {\n    // Defined on step 3\n  }\n}\n```\n\n3.  在 `render` 方法中，我们需要定义选项卡组件以及我们要展示的每个选项卡。此时，我们可以使用包含参数的 `renderTab` 方法来构建 JSX，这使得我们可以通过调用函数来复用代码：\n\n```js\nrender() {\n  return (\n    <TabBarIOS\n      tintColor=\"#42b49a\"\n      >\n      {this.renderTab({title: 'Home', id: 'home', icon: homeIcon})}\n      {this.renderTab({title: 'Favorites', id: 'favorites', icon: favIcon})}\n      {this.renderTab({title: 'Blog', id: 'blog', icon: blogIcon})}\n      {this.renderTab({title: 'Profile', id: 'profile', icon: profileIcon})}\n    </TabBarIOS>\n  );\n}\n```\n\n4.  对于 `renderTab` 方法，我们需要定义一些属性，如标签标题、图标、是否选中以及选中时的回调函数。现在，我们为每个标签设定相同的内容，实际应用中，需要将主要内容作为参数传递到 App 中。其中最重要的属性就是选中属性。因为只可以在选项卡中选中一个项，所以将当前选中的项保存在 `state` 中：\n\n```js\nrenderTab(options) {\n  return (\n    <TabBarIOS.Item\n      title={options.title}\n      selected={this.state.selected === options.id}\n      onPress={() => this.selectTab(options.id)}\n      icon={options.icon}\n      >\n      <View style={styles.container}>\n        <Image source={options.icon} style={styles.icon} />\n        <Text style={styles.title}>{options.title}</Text>\n      </View>\n    </TabBarIOS.Item>\n  );\n}\n```\n\n5.  在上一步中，在选项卡项按下时，将会调用 `selectTab` 方法。这里的考虑是，当用户按下选项卡项时，调用此函数把选中项 ID 保存到 `state` 中，用来设置当前选中状态：\n\n```js\nselectTab(id) {\n  this.setState({\n    selected: id,\n  });\n}\n```\n\n6.  接着，给屏幕上的内容添加一些样式，也为每个标签的 icon 设置合适的颜色。同时，我们也将组件进行导出，便于在其他任何地方使用：\n\n```js\nconst styles = StyleSheet.create({\n  container: {\n    flex: 1,\n    alignItems: 'center',\n    justifyContent: 'center'\n  },\n  title: {\n    fontSize: 20,\n    marginTop: 20\n  },\n  icon: {\n    width: 30,\n    height: 30,\n    tintColor: '#42b49a'\n  }\n});\nexport default MainApp;\n```\n\n7.  最后，更新 `index.ios.js`，导入我们新的类：\n\n```js\nimport React, { Component } from 'react';\nimport { AppRegistry } from 'react-native';\nimport MainApp from './src/MainApp';\nAppRegistry.registerComponent('TabsComponent', () => MainApp);\n```\n\n8.  在模拟器中查看最终效果，如下：\n\n## 使用 `flexbox` 创建个人资料页面\n\n## 设置导航器\n"
  },
  {
    "path": "React Native 开发笔记/RN Aspect-01-环境准备.md",
    "content": ""
  },
  {
    "path": "React Native 开发笔记/RN Aspect-02-Hello React Native.md",
    "content": ""
  },
  {
    "path": "React Native 开发笔记/RN Aspect-03-修改名称与icon.md",
    "content": ""
  },
  {
    "path": "React Native 开发笔记/RN Aspect-04-打包App.md",
    "content": ""
  },
  {
    "path": "React Native 开发笔记/React Native开发之多屏适配.md",
    "content": "---\ntitle: React Native开发之多屏适配\ndate: 2018-3-24 08:56:47\n---\n\n# 多屏适配\n\n# 字体不随系统字体缩放\n\n**针对 `iOS`**\n\n需要在 `node_modules/react-native` 中找到 `RCTFont.mm` 文件（在目录中搜索），在其中，可以找到如下代码：\n\n```js\nif (scaleMultiplier > 0.0 && scaleMultiplier != 1.0) {\n  fontSize = round(fontSize);\n}\n```\n\n将其注释掉，打包出来的 App，就不会受到系统字体尺寸变化的影响。\n\n**针对 `Android`**\n\n找到 `<souce>/android` 目录下的 `MainActivity.java` 文件。然后引入如下 Package：\n\n```java\nimport android.content.res.Configuration;\nimport android.content.res.Resources;\n```\n\n接着，在 Class 中，加入如下代码：\n\n```java\n@Override\npublic Resources getResources() {\n    Resources res = super.getResources();\n    Configuration config=new Configuration();\n    config.setToDefaults();\n    res.updateConfiguration(config,res.getDisplayMetrics() );\n    return res;\n}\n```\n"
  },
  {
    "path": "React面面观/JSX中的那些小细节.md",
    "content": "---\ntitle: JSX中的那些小细节\ndate: 2017-4-6 10:53:53\n---\n\n# 导言\n\n在学习 `React` 的过程中，我们无可厚非，需要接触到 `JSX`。在 `JSX` 中，有一些约定是我们需要遵守的，有一些细节也需要我们去牢记。\n\n本文就将我在学习过程中，了解到的约定与细节总结如下。\n\n### 1、自定义组件必须大写首字母 \n\n`JSX` 规定，只有大写的组件，才会被解析为自定义组件。\n\n### 2、如果我们使用了自定义组件，那么我们必须导入 `React`和 `组件对象`\n\nJSX会把组件编译为一个变量，所以就算在JSX没有直接使用，也需要进行导入，否则编译之后无法找到。\n\n### 3、属性的默认值为true\n\n如果传递属性时，没有给定值，那么默认访问属性，值将会是 `true`，和HTML属性一致。\n\n### 4、JSX不支持返回多个元素，当我们有多个元素时，需要用一个外层div来包裹\n\n### 5、布尔值，NULL和Undefinded将会被忽略（不会渲染）\n\n### 6、一些 `falsy values` 会被渲染，比如数字0\n\n### 7、想要条件显示某些组件，需要保证 `&&` 之前必须为 `true or false`\n\n### 8、如果要显示 `true, false, null, undefined`,需要先转换为字符串"
  },
  {
    "path": "React面面观/【译】参考手册-React组件.md",
    "content": "---\ntitle: 参考手册-React组件\ndate: 2017-4-7 13:27:19\nversion: 15.4.2\n---\n\n# React.Component\n\n组件能够让你将UI拆分为多个独立自治并可重用的部分。在 `React` 中提供了 `React.Component`。\n\n## 概述\n\n`React.Component` 是一个抽象基类，直接引用 `React.Component` 无太大意义。反而，我们会用子类来继承它，并至少定义一个 `render()` 方法。\n\n通常您将使用纯 [JavaScript class](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Classes) 来定义一个 `React` 组件：\n\n```javascript\nclass Greeting extends React.Component {\n  render() {\n    return <h1>Hello, {this.props.name}</h1>;\n  }\n}\n```\n\n如果您还没有使用ES6，可以使用 `React.createClass` 来替代。从 [Using React without ES6](https://facebook.github.io/react/docs/react-without-es6.html) 了解更多。\n\n## 组件生命周期\n\n每个组件都有几个 `生命周期方法`，您可以不同的阶段插入自己的逻辑代码。以 `will` 开头的方法将在事情发生前被调用，以 `did` 开头的方法将在事情发生后被调用。\n\n#### Mounting（挂载）\n\n以下的方法，将在组件实例被创建和插入到DOM前被调用：\n\n\n- [`constructor()`](#constructor)\n- [`componentWillMount()`](#componentwillmount)\n- [`render()`](#render)\n- [`componentDidMount()`](#componentdidmount)\n\n#### Updating（更新）\n\n属性或状态变化将会引发更新。以下方法将在重绘之前被调用：\n\n- [`componentWillReceiveProps()`](#componentwillreceiveprops)\n- [`shouldComponentUpdate()`](#shouldcomponentupdate)\n- [`componentWillUpdate()`](#componentwillupdate)\n- [`render()`](#render)\n- [`componentDidUpdate()`](#componentdidupdate)\n\n#### Unmounting（卸载）\n\n以下方法将在组件开始从DOM移除时被调用：\n\n- [`componentWillUnmount()`](#componentwillunmount)\n\n### 其他APIs\n\n每个组件也都会提供以下几个API：\n\n- [`setState()`](#setstate)\n- [`forceUpdate()`](#forceupdate)\n\n### 类属性\n\n- [`defaultProps`](#defaultprops)\n- [`displayName`](#displayname)\n\n### 实例属性 \n\n- [`props`](#props)\n- [`state`](#state)\n\n* * * \n\n## 引用\n\n### `render()`\n\n```javascript\nrender()\n```\n\n`render()` 方法是必须的。\n\n当调用 `render()` 方法时，将检查 `this.props` 和 `this.state` 并返回单个React元素。这个元素可以是原生DOM组件，如 `<div />`，也可以是你自定义的复合组件。\n\n如果你不想渲染任何内容，你可以返回 `null` 或者是 `false`。当返回 `null` 或 `false` 时， `ReactDOM.findDOMNode(this)` 也将返回 `null`。\n\n`render()` 函数应该是纯函数，这意味着你不应该修改组件状态，每次调用都应该返回同样的结果，同时也不要直接和浏览器交互（不要操作DOM）。如果你想要操作DOM，请使用 `componentDidMount()` 或其他生命周期方法来替代。使用纯粹的 `render()` 可以使组件易于理解。\n\n> **注意**\n>\n> `render()` 不会在 [`shouldComponentUpdate()`](#shouldcomponentupdate) 返回 `false` 的时候被调用。\n\n* * *\n\n### `constructor()`\n\n```javascript\nconstructor(props)\n```\n\nReact组件的构造函数会在挂载前被调用。当我们在 `React.Component` 的子类中实现构建函数时，我们应该在所有语句之前优先调用 `super(props)`。反之，在构造函数中访问 `this.props` 将会是未定义，这可能会导致bugs。\n\n构造函数是初始化状态的地方。如果你不需要初始化状态，且不绑定方法，那么你不必要实现构造函数。\n\n如果你确信，你可以使用 `props` 来初始化状态。以下是一个合法的 `React.Component` 子类构造函数：\n\n```js\nconstructor(props) {\n  super(props);\n  this.state = {\n    color: props.initialColor\n  };\n}\n```\n\n当心这种模式，它能够有效的 \"forks\" 属性且可能会导致bugs。你通常可以使用状态提升来替代同步属性到状态。\n\n如果你想使用 `state` 来 \"fork\" `props`，你还需要实现 [`componentWillReceiveProps(nextProps)`](#componentwillreceiveprops) 来保持状态是最新的。但是状态提升通常是更容易且不容易出错的。\n\n* * *\n\n### `componentWillMount()`\n\n```javascript\ncomponentWillMount()\n```\n\n`componentWillMount()` 将在挂载发生前，被立即调用。它会在 `render` 之前调用，因此在该方法中设置状态将不会触发重新渲染。避免在此方法中引入任何有副作用的行为或者订阅。\n\n这是在服务端渲染中唯一被调用的生命周期钩子。通常，我们用来替代 `constructor()`。\n\n* * *\n\n### `componentDidMount()`\n\n```javascript\ncomponentDidMount()\n```\n\n`componentDidMount()` 将在组件挂载之后立即被调用。需要使用 DOM 节点的初始化应该放在这里。如果你需要加载远端数据，这也是处理网络请求的好地方。在这个方法中设置状态将会触发重绘。\n\n* * *\n\n### `componentWillReceiveProps()`\n\n```javascript\ncomponentWillReceiveProps(nextProps)\n```\n\n`componentWillReceiveProps()` 将在已经挂载的组件接受到新属性前被调用。如果你需要更新状态来响应属性变化（例如，重置状态），你可以比较 `this.props` 和 `nextProps`，然后使用 `this.setState()` 来处理状态变化。\n\n请注意，就算属性没有变化，React也可能会调用此方法，因此如果你只想到处理有变更的情况，那么请确保比较当前值和下一次的值。这种情况在父组件变更导致你的组件重绘时可能会发生。\n\n在挂载阶段初始化属性React不会调用 `componentWillReceiveProps`。如果一些组件的属性可能会更新，它仅会调用该方法。调用 `this.setState` 通常不会触发 `componentWillReceiveProps`。\n\n* * *\n\n### `shouldComponentUpdate()`\n\n```javascript\nshouldComponentUpdate(nextProps, nextState)\n```\n\n使用 `shouldComponentUpdate` 让React知道组件的输出不会受到当前状态和属性变更的影响。默认行为是当每次状态变更时都会引发重绘，绝大多数情况下，您应该依赖默认行为。\n\n当收到新的状态或者属性时，将会调用 `shouldComponentUpdate()`。默认值是 `true`。在初次渲染或调用 `forceUpdate()` 时，`shouldComponentUpdate()` 将不会被调用。\n\n返回 `false` 也不会阻止子组件在它们自己的状态变化时重绘。\n\n目前，当 `shouldComponentUpdate()` 返回 `false`，[`componentWillUpdate()`](#componentwillupdate), [`render()`](#render), 和 [`componentDidUpdate()`](#componentdidupdate) 都不会被调用。请注意，未来React可能将 `shouldComponentUpdate()` 作为一个提示而不是一个强制指令，就算是返回 `false` 仍然有可能导致组件重绘。\n\n如果你在监控分析之后，发现特定组件确实非常缓慢，那么可以将其修改为实现了使用浅表属性和状态比较的 `shouldComponentUpdate()` 方法的 `React.PureComponent`。如果你有信心手动确认，你可以比较 `this.props` 与 `nextProps` 以及 `this.state` 与 `nextState` 的值来告诉React本次更新是可以跳过的。\n\n* * *\n\n### `componentWillUpdate()`\n\n```javascript\ncomponentWillUpdate(nextProps, nextState)\n```\n\n当接收到新的属性或者状态时，`componentWillUpdate()` 将会在重绘前被立即调用。可以在此处放置一些重绘前的处理逻辑。这个方法在初次绘制时不会被调用。\n\n注意你也不能在此处调用 `this.setState()`。如果你需要更新状态来响应属性变化，请使用 `componentWillReceiveProps()` 替代。\n\n> **注意**\n>\n> 当 `shouldComponentUpdate()` 返回 false 时，`componentWillUpdate()` 将不会被调用。\n\n* * *\n\n### `componentDidUpdate()`\n\n```javascript\ncomponentDidUpdate(prevProps, prevState)\n```\n\n在更新完成之后，`componentDidUpdate()` 会被立即调用。初次绘制不会调用该方法。\n\n在这里可以操作组件更新之后的DOM。在你比较了当前属性和上一次属性之后，这里也适合处理网络请求。（例如：如果属性没有变化，可能不需要一个网络请求。）\n\n> **注意**\n>\n> 当 `shouldComponentUpdate()` 返回 false 时，`componentWillUpdate()` 将不会被调用。\n\n* * *\n\n### `componentWillUnmount()`\n\n```javascript\ncomponentWillUnmount()\n```\n\n在组件卸载和释放之前，将会立即调用 `componentWillUnmount()`。可以在该方法中，执行一些清理动作，如无效的定时器，取消网络请求，或者是清理在 `componentDidMount()` 中创建的DOM元素。 \n\n* * *\n\n### `setState()`\n\n```javascript\nsetState(nextState, callback)\n```\n\n执行它会执行一次浅表复制，将 `nextState` 合并到当前状态上。这也是从事件处理函数和服务请求回调中触发UI更新的主要方法。\n\n第一个参数可以是一个对象（包含0个或多个要更新的key）或者是一个返回包含更新key的对象的函数（传入状态和属性）。\n\n简单对象用法如下：\n\n```javascript\nthis.setState({mykey: 'my new value'});\n```\n\n它也可能传递如下签名 `function(state, props) => newState` 的函数。这将引入一个原子更新，会在设置值之前先查看上一次的状态和属性。例如，我们想每次增加 `props.step` 指定的值：\n\n```javascript\nthis.setState((prevState, props) => {\n  return {myInteger: prevState.myInteger + props.step};\n});\n```\n\n第二个参数是一个可选的回调函数，它将在 `setState` 完成和组件重绘之后执行一次。通常，我们建议在 `componentDidUpdate()` 中处理这样的逻辑。\n\n`setState()` 不会立即更新 `this.state`，仅会创建一个挂起的状态转换。调用此方法之后访问 `this.state` 可能会返回现有值。\n\n不能确保 `setState` 是同步操作，多次调用 `setState` 可能会进行批处理以提高性能。\n\n`setState()` 将总是导致重绘，除非 `shouldComponentUpdate()` 返回 `false`。如果使用可变对象且不能在 `shouldComponentUpdate()` 中实现条件重绘逻辑，那么可以仅在当前状态与之前不同时，再调用 `setState()` 来避免不必要的重绘。\n\n* * *\n\n### `forceUpdate()`\n\n```javascript\ncomponent.forceUpdate(callback)\n```\n\n默认情况下，当你的组件状态和属性变化时，组件将会被重绘。如果你的 `render()` 方法中依赖一些其他的数据，你可以调用 `forceUpdate()` 来告诉React需要重绘。\n\n调用 `forceUpdate()` 将导致组件跳过 `shouldComponentUpdate()` 直接调用 `render()`。这也将触发子组件正常的生命周期方法，包括每个子组件的 `shouldComponentUpdate()`。React仍然将只更新标记变化的DOM元素。\n\n通常，你应该避免使用 `forceUpdate()`，仅在 `render()` 中只读取 `this.props` 和 `this.state`。\n\n\n* * *\n\n## 类属性\n\n### `defaultProps`\n\n能够在组件类自身上定义 `defaultProps` 属性，它将为属性设置默认值。当属性未定义时才会被设置为默认属性值，如果属性值被设置为null，则不会被设置为默认属性值。示例如下：\n\n```js\nclass CustomButton extends React.Component {\n  // ...\n}\n\nCustomButton.defaultProps = {\n  color: 'blue'\n};\n```\n\n当 `props.color` 没有提供时，它将被设置为默认值 `'blue'`:\n\n\n```js\n  render() {\n    return <CustomButton /> ; // props.color will be set to blue\n  }\n```\n\n如果 `props.color` 被设置为 null，它仍然是 null:\n\n```js\n  render() {\n    return <CustomButton color={null} /> ; // props.color will remain null\n  }\n```\n\n* * *\n\n### `displayName`\n\n`displayName` 字符串用于调试信息。JSX 会自动设置这个值；查看 [深入JSX](【译】高级指南-深入JSX.md) 了解更多。\n\n* * *\n\n### `propTypes`\n\n可以在组件类自身上定义 `propTypes` 属性，用来确定属性的类型。它是属性名称和定义在 `React.PropTypes` 中类型的一个映射。在开发模式中，如果碰到不合法的属性，那么控制台将会打印警告。产线模式下，属性类型检查基于性能考虑将会被跳过。\n\n例如，以下代码确保 `color` 属性是字符串：\n\n```\nclass CustomButton extends React.Component {\n  // ...\n}\n\nCustomButton.propTypes = {\n  color: React.PropTypes.string\n};\n```\n\n我们建议您尽可能的使用 [Flow](https://flow.org/)，以便使用编译时的类型检查，而不是运行时的类型检查。查看 [Flow 对 React 的内置支持](https://flow.org/en/docs/frameworks/react/) 来轻松的在 React 应用中使用静态分析。\n\n* * *\n\n## 实例属性\n\n### `props`\n\n`this.props` 包含了组件调用者定义的属性。查看 [组件和属性](【译】快速起步-组件与属性.md) 了解更多。\n\n`this.props.children` 是一个特别的属性，通常是由 JSX 表达式的子标签定义，而不是组件本身的标签。\n\n### `state`\n\n`state` 包含了组件特定的可能随时间变化而变化的数据。 `state` 是用户自定义的，它也是一个纯粹的JavaScript对象。\n\n不需要在 `render()` 中使用的数据，不必要放在 `state` 上。例如，您可以直接在实例上存储定时器ID。\n\n查看 [快速起步-状态和生命周期](【译】快速起步-状态和生命周期.md) 了解更多。\n\n调用 `setState()` 来替代直接修改 `this.state`。保持 `this.state` 是不可变的。"
  },
  {
    "path": "React面面观/【译】快速起步-JSX简介.md",
    "content": "---\ntitle: 快速起步-JSX简介\ndate: 2017-4-14 14:12:50\nreact version: 15.5.0\n---\n\n# 快速起步-JSX简介\n\n思考这个变量申明：\n\n```js\nconst element = <h1>Hello, world!</h1>;\n```\n\n这个有趣的标签语法既不是字符串也不是HTML。\n\n它被称之为 `JSX`，是 `JavaScript` 的语法扩展。我们建议使用它来定义React的UI展示。`JSX` 可能会让你想起模板语言，但它可以使用 `JavaScript` 的全部功能。\n\n`JSX` 用于编写 `React \"elements\"`。在 [下一节](【译】快速起步-渲染元素.md)，我们将探索如何将它们渲染到DOM中。下面，我们来了解下 `JSX` 的基础知识。\n\n### 在 `JSX` 中嵌入表达式\n\n你可以在 `JSX` 中通过 `{xxx}` 来嵌入 [JavaScript 表达式](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Expressions_and_Operators#Expressions)。\n\n例如，`2 + 2`, `user.firstName`, 和 `formatName(user)` 都是合法的表达式：\n\n```js{12}\nfunction formatName(user) {\n  return user.firstName + ' ' + user.lastName;\n}\n\nconst user = {\n  firstName: 'Harper',\n  lastName: 'Perez'\n};\n\nconst element = (\n  <h1>\n    Hello, {formatName(user)}!\n  </h1>\n);\n\nReactDOM.render(\n  element,\n  document.getElementById('root')\n);\n```\n\n[CodePen Demo](http://codepen.io/gaearon/pen/PGEjdG?editors=0010)\n\n为了可读性，我们将 `JSX` 分割为多行。虽然这不是必须的，但在这样做的时候，我们建议将它放在 `{}` 中，以避免 [自动补全分号](http://stackoverflow.com/q/2846283)。\n\n### JSX 也是一个表达式\n\n编译之后，JSX表达式会变成常规的 `JavaScript` 对象。\n\n这意味着你可以在 `if` 语句和 `for` 循环内部使用 `JSX`，也可以将其作为参数传递或用来作为返回值：\n\n```js{3,5}\nfunction getGreeting(user) {\n  if (user) {\n    return <h1>Hello, {formatName(user)}!</h1>;\n  }\n  return <h1>Hello, Stranger.</h1>;\n}\n```\n\n### JSX中指定属性\n\n您可以使用引号将字符串文本指定为属性：\n\n```js\nconst element = <div tabIndex=\"0\"></div>;\n```\n\n你也可以使用 `{}` 将 `JavaScript` 表达式作为属性：\n\n```js\nconst element = <img src={user.avatarUrl}></img>;\n```\n在属性中使用 `JavaScript` 表达式时，不要使用引号包裹大括号。否则，JSX会认为属性值是字符串而不是一个表达式。你可以对字符串使用双引号，对表达式使用花括号，但不能同时使用。\n\n### JSX中指定子集\n\n如果是空标签，可以像XML那样使用自闭合标签 `/>`：\n\n```js\nconst element = <img src={user.avatarUrl} />;\n```\n\nJSX 标签也可以包含子集：\n\n```js\nconst element = (\n  <div>\n    <h1>Hello!</h1>\n    <h2>Good to see you here.</h2>\n  </div>\n);\n```\n\n>**警告:**\n>\n>由于 JSX 更趋近于 JavaScript 而不是 HTML，React DOM 使用 `camelCase`（小驼峰） 属性命名约定而不是HTML的属性名称。\n>\n>例如：`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)。\n\n### JSX 防止注入攻击\n\n在 `JSX` 中嵌入用户输入是安全的：\n\n```js\nconst title = response.potentiallyMaliciousInput;\n// 安全的：\nconst element = <h1>{title}</h1>;\n```\n\n默认情况下，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) 攻击。\n\n### JSX 代表对象\n\n`Babel` 将 `JSX` 编译成 `React.createElement()` 调用。\n\n这两个例子是等同的：\n\n```js\nconst element = (\n  <h1 className=\"greeting\">\n    Hello, world!\n  </h1>\n);\n```\n\n```js\nconst element = React.createElement(\n  'h1',\n  {className: 'greeting'},\n  'Hello, world!'\n);\n```\n\n`React.createElement()` 会执行一些检查来帮助您编写无误的代码，但基本上，它是创建如下的对象：\n\n```js\n// 注意：以下是简单结构\nconst element = {\n  type: 'h1',\n  props: {\n    className: 'greeting',\n    children: 'Hello, world'\n  }\n};\n```\n\n这些对象称之为 \"React elements\". 你可以将它们视为您想要在屏幕上看到的内容。`React` 会读取这些对象，并使用它们来构造DOM且保持为最新状态。\n\n下一节我们将探索如何渲染 `React elements` 到DOM中。\n\n>**提示:**\n>\n>我们建议为编辑器选择 `Babel` 语法支持插件，以便 `ES6` 和 `JSX` 都能被高亮显示。"
  },
  {
    "path": "React面面观/【译】快速起步-事件处理.md",
    "content": "---\ntitle: 快速起步-事件处理\ndate: 2017-4-18 16:58:04\nreact version: 15.5.0\n---\n\n# 事件处理\n\n使用React元素处理事件和DOM元素上的事件非常相似。但还是有一些语法上的不同：\n\n* React 的事件是小驼峰命名的，不是全小写命名的。\n* 可以在JSX中传递函数作为事件处理器，而不是字符串。\n\n例如：\n\n```html\n<button onclick=\"activateLasers()\">\n  Activate Lasers\n</button>\n```\n\n在React中略微不同：\n\n```js{1}\n<button onClick={activateLasers}>\n  Activate Lasers\n</button>\n```\n\n另一个不同是，不能通过 `return false` 来阻止默认行为。必须要明确使用 `preventDefault` 。例如，在HTML中，要想阻止a标签的默认行为，可以如下写：\n\n```html\n<a href=\"#\" onclick=\"console.log('The link was clicked.'); return false\">\n  Click me\n</a>\n```\n\n但是在React中，你必须这样写：\n\n```js{2-5,8}\nfunction ActionLink() {\n  function handleClick(e) {\n    e.preventDefault();\n    console.log('The link was clicked.');\n  }\n\n  return (\n    <a href=\"#\" onClick={handleClick}>\n      Click me\n    </a>\n  );\n}\n```\n\n在这里， `e` 是一个合成事件。`React` 根据 [W3C 标准](https://www.w3.org/TR/DOM-Level-3-Events/) 定义了这些合成事件，所以你不需要担心浏览器兼容性问题。查看 [SyntheticEvent](https://facebook.github.io/react/docs/events.html) 了解更多。\n\n在使用 `React` 的时候，通常不需要调用 `addEventListener` 来给DOM元素添加事件。而是在元素最初渲染时提供一个监听器。\n\n当你在使用 [ES6 class](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Classes) 定义组件时，一个常见的模式是将事件处理程序定义为类上的一个原型方法。例如，`Toggle` 渲染了一个可以在 \"ON\" 和 \"OFF\" 之间切换的按钮：\n\n```js{6,7,10-14,18}\nclass Toggle extends React.Component {\n  constructor(props) {\n    super(props);\n    this.state = {isToggleOn: true};\n\n    // 必要的绑定，让 `this` 在回调中可用。\n    this.handleClick = this.handleClick.bind(this);\n  }\n\n  handleClick() {\n    this.setState(prevState => ({\n      isToggleOn: !prevState.isToggleOn\n    }));\n  }\n\n  render() {\n    return (\n      <button onClick={this.handleClick}>\n        {this.state.isToggleOn ? 'ON' : 'OFF'}\n      </button>\n    );\n  }\n}\n\nReactDOM.render(\n  <Toggle />,\n  document.getElementById('root')\n);\n```\n\n[CodePen Demo](http://codepen.io/gaearon/pen/xEmzGg?editors=0010)\n\n您必须要注意JSX回调中 `this` 的含义。在JavaScript中，默认情况下，类方法不是 [bound](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_objects/Function/bind) 的。如果你传递处理函数给 `onClick` 时忘记了绑定 `this` 给 `this.handleClick`，那么当函数被调用时，`this` 将会是 `undefined`。\n\n这不是React特定的行为，它是 [JavaScript中函数如何工作](https://www.smashingmagazine.com/2014/01/understanding-javascript-function-prototype-bind/) 的一部分。通常，如果你引用了没有 `()` 的方法（方法引用），比如 `onClick={this.handleClick}`，那么你需要先 `bind(this)`。\n\n如果调用 `bind` 让你觉得很繁琐，还有两种方法可以解决这个问题。如果你在使用实现性质的语法 [属性初始化](https://babeljs.io/docs/plugins/transform-class-properties/)，你可以使用属性初始化来正确的绑定回调的 `this`：\n\n```js{2-6}\nclass LoggingButton extends React.Component {\n  // This syntax ensures `this` is bound within handleClick.\n  // Warning: this is *experimental* syntax.\n  handleClick = () => {\n    console.log('this is:', this);\n  }\n\n  render() {\n    return (\n      <button onClick={this.handleClick}>\n        Click me\n      </button>\n    );\n  }\n}\n```\n\n在 [Create React App](https://github.com/facebookincubator/create-react-app) 中，这个语法是默认启用的。\n\n如果你不想使用属性初始化语法，你还可以在回调中使用 [箭头函数](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Functions/Arrow_functions)：\n\n```js{7-9}\nclass LoggingButton extends React.Component {\n  handleClick() {\n    console.log('this is:', this);\n  }\n\n  render() {\n    // 这个语法确保 `this` 在 handleClick中指向正确\n    return (\n      <button onClick={(e) => this.handleClick(e)}>\n        Click me\n      </button>\n    );\n  }\n}\n```\n这个语法的问题是，每次 `LoggingButton` 渲染时，都会创建一个不同的回调函数。大多数情况下，这都没啥问题。但是，如果这个回调函数会传递给层次更低的组件，那么可能会导致这些组件进行额外的渲染。我们通常建议在构造函数中使用绑定，或者使用属性初始化语法来避免这个问题。"
  },
  {
    "path": "React面面观/【译】快速起步-列表与KEY.md",
    "content": "---\ntitle: 快速起步-数组与KEY\ndate: 2017-4-20 13:34:37\nreact version: 15.5.0\n---\n\n# 数组与KEY\n\n首先，我们来看看如何在 `JavaScript` 中转换列表。\n\n如下代码中，我们使用 `map()` 方法来让我们的数字数组值翻倍。我们通过 `map()` 方法得到了一个新数组，并打印：\n\n```javascript{2}\nconst numbers = [1, 2, 3, 4, 5];\nconst doubled = numbers.map((number) => number * 2);\nconsole.log(doubled);\n```\n代码将会在控制台上打印：`[2, 4, 6, 8, 10]`。\n\n在 `React` 中，将数组转换为 [elements](【译】快速起步-渲染元素.md) 列表，几乎和上面的代码一样。\n\n### 渲染多个组件\n\n你可以构建元素集合，并使用 `{}` 将它们包含在 `JSX` 中。\n\n下面，我们使用 [`map()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map) 来循环一个数字数组。并对每个项目都返回一个 `<li>` 元素。最终将结果赋值给 `listItems`：\n\n```javascript{2-4}\nconst numbers = [1, 2, 3, 4, 5];\nconst listItems = numbers.map((number) =>\n  <li>{number}</li>\n);\n```\n\n我们将整个 `listItems` 包含在 `<ul>` 元素中，并渲染到DOM中：\n\n```javascript{2}\nReactDOM.render(\n  <ul>{listItems}</ul>,\n  document.getElementById('root')\n);\n```\n\n[Try it on CodePen.](https://codepen.io/gaearon/pen/GjPyQr?editors=0011)\n\n这个代码显示了 1-5 的数字列表。\n\n### 基础列表组件\n\n通常你会在 [组件](【译】快速起步-组件与属性.md) 中显示列表。\n\n我们将上一个示例重构到一个组件中，这个组件接收一个数字数组，并输出一个无序的元素列表。\n\n```javascript{3-5,7,13}\nfunction NumberList(props) {\n  const numbers = props.numbers;\n  const listItems = numbers.map((number) =>\n    <li>{number}</li>\n  );\n  return (\n    <ul>{listItems}</ul>\n  );\n}\n\nconst numbers = [1, 2, 3, 4, 5];\nReactDOM.render(\n  <NumberList numbers={numbers} />,\n  document.getElementById('root')\n);\n```\n\n当你运行这段代码时，你会得到 `a key should be provided for list items（应该为列表提供一个key）` 这样的警告。\"Key\" 是创建元素列表时，需要包含的特殊字符串属性。我们接下来将会讨论为什么 “Key” 对于数组是非常重要的。\n\n我们可以在 `numbers.map()` 方法中给列表项传递一个 `key` 属性，这样可以修复该问题。\n\n```javascript{4}\nfunction NumberList(props) {\n  const numbers = props.numbers;\n  const listItems = numbers.map((number) =>\n    <li key={number.toString()}>\n      {number}\n    </li>\n  );\n  return (\n    <ul>{listItems}</ul>\n  );\n}\n\nconst numbers = [1, 2, 3, 4, 5];\nReactDOM.render(\n  <NumberList numbers={numbers} />,\n  document.getElementById('root')\n);\n```\n\n[Try it on CodePen.](https://codepen.io/gaearon/pen/jrXYRR?editors=0011)\n\n## Keys\n\nKeys 会帮助 React 识别哪些项是修改、新增或者是移除掉的。Keys  会给数组元素一个固定的标识：\n\n```js{3}\nconst numbers = [1, 2, 3, 4, 5];\nconst listItems = numbers.map((number) =>\n  <li key={number.toString()}>\n    {number}\n  </li>\n);\n```\n\n最好的选择 Key 的方式是使用一个唯一字符串来标识列表项。最常用的做法是使用数据项的ID作为 Key：\n\n```js{2}\nconst todoItems = todos.map((todo) =>\n  <li key={todo.id}>\n    {todo.text}\n  </li>\n);\n```\n\n当需要渲染的项没有固定的ID时，你可以把项的索引当成 key：\n\n```js{2,3}\nconst todoItems = todos.map((todo, index) =>\n  // Only do this if items have no stable IDs\n  <li key={index}>\n    {todo.text}\n  </li>\n);\n```\n我们并不建议使用索引来作为 key，这可能会很慢。如果有兴趣，可以阅读 [深入理解Key的必要性](https://facebook.github.io/react/docs/reconciliation.html#recursing-on-children)。\n\n### 带Key的组件拆分\n\nKeys 仅仅在数组上下文才有意义。\n\n例如，如果你拆分出一个 `ListItem` 组件，你需要将 Key 设置在 `<ListItem />` 元素上，而不是放在 `ListItem` 组件本身的 `<li>` 上。（PS：设置在循环的ITEM上）\n\n**示例: 不正确的Key用法**\n\n```javascript{4,5,14,15}\nfunction ListItem(props) {\n  const value = props.value;\n  return (\n    // 错误，这里并不需要指定key：\n    <li key={value.toString()}>\n      {value}\n    </li>\n  );\n}\n\nfunction NumberList(props) {\n  const numbers = props.numbers;\n  const listItems = numbers.map((number) =>\n    // 错误，这个需要指定key：\n    <ListItem value={number} />\n  );\n  return (\n    <ul>\n      {listItems}\n    </ul>\n  );\n}\n\nconst numbers = [1, 2, 3, 4, 5];\nReactDOM.render(\n  <NumberList numbers={numbers} />,\n  document.getElementById('root')\n);\n```\n\n**示例: 正确的 Key 用法**\n\n```javascript{2,3,9,10}\nfunction ListItem(props) {\n  // 正确，此处不需要指定key：\n  return <li>{props.value}</li>;\n}\n\nfunction NumberList(props) {\n  const numbers = props.numbers;\n  const listItems = numbers.map((number) =>\n    // 正确，应该在这里指定key：\n    <ListItem key={number.toString()}\n              value={number} />\n  );\n  return (\n    <ul>\n      {listItems}\n    </ul>\n  );\n}\n\nconst numbers = [1, 2, 3, 4, 5];\nReactDOM.render(\n  <NumberList numbers={numbers} />,\n  document.getElementById('root')\n);\n```\n\n[Try it on CodePen.](https://codepen.io/rthor/pen/QKzJKG?editors=0010)\n\n一个很好的经验法则就是 `map()` 方法内的元素需要key。\n\n### Keys 必须在兄弟节点中唯一\n\n数组元素中指定的 key 应该在当前列表中是独立无二的，但不需要全局唯一。我们可以在不同的数组中，使用相同的 key:\n\n```js{2,5,11,12,19,21}\nfunction Blog(props) {\n  const sidebar = (\n    <ul>\n      {props.posts.map((post) =>\n        <li key={post.id}>\n          {post.title}\n        </li>\n      )}\n    </ul>\n  );\n  const content = props.posts.map((post) =>\n    <div key={post.id}>\n      <h3>{post.title}</h3>\n      <p>{post.content}</p>\n    </div>\n  );\n  return (\n    <div>\n      {sidebar}\n      <hr />\n      {content}\n    </div>\n  );\n}\n\nconst posts = [\n  {id: 1, title: 'Hello World', content: 'Welcome to learning React!'},\n  {id: 2, title: 'Installation', content: 'You can install React from npm.'}\n];\nReactDOM.render(\n  <Blog posts={posts} />,\n  document.getElementById('root')\n);\n```\n\n[Try it on CodePen.](https://codepen.io/gaearon/pen/NRZYGN?editors=0010)\n\nKeys 为 React 提供了一些隐藏信息，但不会传递给您的组件。如果你的组件中需要相同的值，请通过其他不同的属性名称进行传递：\n\n```js{3,4}\nconst content = posts.map((post) =>\n  <Post\n    key={post.id}\n    id={post.id}\n    title={post.title} />\n);\n```\n\n以上这个例子，`Post` 组件能够读取到 `props.id`，但读取不到 `props.key`。\n\n### 在 JSX 中嵌入 `map()`\n\n在上面的例子中，我们定义了一个独立的 `listItems` 变量，并在 JSX 中进行了使用：\n\n```js{3-6}\nfunction NumberList(props) {\n  const numbers = props.numbers;\n  const listItems = numbers.map((number) =>\n    <ListItem key={number.toString()}\n              value={number} />\n  );\n  return (\n    <ul>\n      {listItems}\n    </ul>\n  );\n}\n```\nJSX 允许在 `{}` 中嵌入表达式，所以我们内联 `map()` 的结果：\n\n```js{5-8}\nfunction NumberList(props) {\n  const numbers = props.numbers;\n  return (\n    <ul>\n      {numbers.map((number) =>\n        <ListItem key={number.toString()}\n                  value={number} />\n      )}\n    </ul>\n  );\n}\n```\n\n[Try it on CodePen.](https://codepen.io/gaearon/pen/BLvYrB?editors=0010)\n\n有时这会让代码更清晰，但这种风格也可能被滥用。像JavaScript一样，是否值得提取为变量是由你决定的。请记住，如果 `map()` 嵌套太多，那么是时候进行组件拆分了。"
  },
  {
    "path": "React面面观/【译】快速起步-条件渲染.md",
    "content": "---\ntitle: 快速起步-条件渲染\ndate: 2017-4-20 15:38:23\nreact version: 15.5.0\n---\n\n# 条件渲染\n\n在 `React` 中，你可以创建不同的组件来封装你需要的行为。然后，你可以通过应用程序的状态来决定渲染它们中的一部分。\n\n`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。\n\n思考下面两个组件：\n\n```js\nfunction UserGreeting(props) {\n  return <h1>Welcome back!</h1>;\n}\n\nfunction GuestGreeting(props) {\n  return <h1>Please sign up.</h1>;\n}\n```\n我们创建一个 `Greeting` 组件，它将根据用户的登录状态，显示以上两个组件中的一个：\n\n```javascript{3-7,11,12}\nfunction Greeting(props) {\n  const isLoggedIn = props.isLoggedIn;\n  if (isLoggedIn) {\n    return <UserGreeting />;\n  }\n  return <GuestGreeting />;\n}\n\nReactDOM.render(\n  // 尝试设置 isLoggedIn={true}:\n  <Greeting isLoggedIn={false} />,\n  document.getElementById('root')\n);\n```\n\n[Try it on CodePen.](https://codepen.io/gaearon/pen/ZpVxNq?editors=0011)\n\n这个例子将根据 `isLoggedIn` 属性的值来渲染不同的问候语。\n\n### 元素变量\n\n你可以使用变量来存储元素。这可以帮助您有条件的渲染组件的一部分，而其他的部分则不会更改。\n\n思考下面两个表示注销和登录按钮的新组件：\n\n```js\nfunction LoginButton(props) {\n  return (\n    <button onClick={props.onClick}>\n      Login\n    </button>\n  );\n}\n\nfunction LogoutButton(props) {\n  return (\n    <button onClick={props.onClick}>\n      Logout\n    </button>\n  );\n}\n```\n\n在上面的例子中，我们将会创建一个名为 `LoginControl` 的有状态组件。\n\n根据它自身的状态，它会渲染 `<LoginButton />` 或者 `<LogoutButton />` 中的一个。它也会渲染上一个例子中的 `<Greeting />`：\n\n```javascript{20-25,29,30}\nclass LoginControl extends React.Component {\n  constructor(props) {\n    super(props);\n    this.handleLoginClick = this.handleLoginClick.bind(this);\n    this.handleLogoutClick = this.handleLogoutClick.bind(this);\n    this.state = {isLoggedIn: false};\n  }\n\n  handleLoginClick() {\n    this.setState({isLoggedIn: true});\n  }\n\n  handleLogoutClick() {\n    this.setState({isLoggedIn: false});\n  }\n\n  render() {\n    const isLoggedIn = this.state.isLoggedIn;\n\n    let button = null;\n    if (isLoggedIn) {\n      button = <LogoutButton onClick={this.handleLogoutClick} />;\n    } else {\n      button = <LoginButton onClick={this.handleLoginClick} />;\n    }\n\n    return (\n      <div>\n        <Greeting isLoggedIn={isLoggedIn} />\n        {button}\n      </div>\n    );\n  }\n}\n\nReactDOM.render(\n  <LoginControl />,\n  document.getElementById('root')\n);\n```\n\n[Try it on CodePen.](https://codepen.io/gaearon/pen/QKzAgB?editors=0010)\n\n虽然，申明一个变量，并使用 `if` 语句是进行条件渲染的好办法，但有时你可能想使用更短的语法。在 `JSX` 中有几种内联条件的写法，如下。\n\n### 逻辑 `&&` 操作符实现内联if\n\n`JSX` 允许在 `{}` 中嵌入表达式。这包含JS中的 `&&` 操作符。它有助于条件渲染一个元素：\n\n```js{6-10}\nfunction Mailbox(props) {\n  const unreadMessages = props.unreadMessages;\n  return (\n    <div>\n      <h1>Hello!</h1>\n      {unreadMessages.length > 0 &&\n        <h2>\n          You have {unreadMessages.length} unread messages.\n        </h2>\n      }\n    </div>\n  );\n}\n\nconst messages = ['React', 'Re: React', 'Re:Re: React'];\nReactDOM.render(\n  <Mailbox unreadMessages={messages} />,\n  document.getElementById('root')\n);\n```\n\n[Try it on CodePen.](https://codepen.io/gaearon/pen/ozJddz?editors=0010)\n\n它可以正常工作是因为在 `JavaScript`中，`true && expression` 总是执行 `expression`，且 `false && expression` 总是执行 `false`。（PS：短路表达式）\n\n因此，如果条件是 `true`, `&&` 右侧的元素将会被渲染。如果为 `false`, `React` 将会跳过右侧的元素。\n\n### 三目表达式实现内联if...else\n\n另一个内联条件渲染的方法是使用 `JavaScript` 中的三目运算符 [`condition ? true : false`](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Operators/Conditional_Operator)。\n\n在下面的例子中，我们使用三目运算来条件渲染一小段文本。\n\n```javascript{5}\nrender() {\n  const isLoggedIn = this.state.isLoggedIn;\n  return (\n    <div>\n      The user is <b>{isLoggedIn ? 'currently' : 'not'}</b> logged in.\n    </div>\n  );\n}\n```\n它也可以用于更大的表达式，虽然不够清晰：\n\n```js{5,7,9}\nrender() {\n  const isLoggedIn = this.state.isLoggedIn;\n  return (\n    <div>\n      {isLoggedIn ? (\n        <LogoutButton onClick={this.handleLogoutClick} />\n      ) : (\n        <LoginButton onClick={this.handleLoginClick} />\n      )}\n    </div>\n  );\n}\n```\n\n就像在 `JavaScript` 中一样，你可以根据您和您的团队的规范来选择更合适的风格。还需要记住，当条件变得越来越复杂的时候，那就应该进行组件拆分了。\n\n### 阻止组件渲染\n\n在极少数情况下，您可能希望组件隐藏自身，即使它是由另一个组件呈现的。为此，我们可以在 `render()` 中返回 `null` 来替代返回渲染元素。\n\n在下面的示例中，`<WarningBanner />` 依赖 `warn` 属性的值来进行渲染。如果属性值是 `false`, 组件将不会被渲染：\n\n```javascript{2-4,29}\nfunction WarningBanner(props) {\n  if (!props.warn) {\n    return null;\n  }\n\n  return (\n    <div className=\"warning\">\n      Warning!\n    </div>\n  );\n}\n\nclass Page extends React.Component {\n  constructor(props) {\n    super(props);\n    this.state = {showWarning: true}\n    this.handleToggleClick = this.handleToggleClick.bind(this);\n  }\n\n  handleToggleClick() {\n    this.setState(prevState => ({\n      showWarning: !prevState.showWarning\n    }));\n  }\n\n  render() {\n    return (\n      <div>\n        <WarningBanner warn={this.state.showWarning} />\n        <button onClick={this.handleToggleClick}>\n          {this.state.showWarning ? 'Hide' : 'Show'}\n        </button>\n      </div>\n    );\n  }\n}\n\nReactDOM.render(\n  <Page />,\n  document.getElementById('root')\n);\n```\n\n[Try it on CodePen.](https://codepen.io/gaearon/pen/Xjoqwm?editors=0010)\n\n从组件的 `render` 方法中返回 `null`，不会影响组件生命周期方法的触发。例如，`componentWillUpdate` 和 `componentDidUpdate` 仍然会被调用。"
  },
  {
    "path": "React面面观/【译】快速起步-渲染元素.md",
    "content": "---\ntitle: 快速起步-渲染元素\ndate: 2017-4-14 14:10:50\nreact version: 15.5.0\n---\n\n# 渲染元素\n\n\n`Elements` 是 `React` 应用中的最小单元。\n\n一个元素定义了你会在屏幕上看到什么：\n\n```js\nconst element = <h1>Hello, world</h1>;\n```\n\nReact 元素是很容易创建的纯对象，不同于浏览器的DOM元素。React DOM 负责从 React元素来更新DOM。\n\n>**注意：**\n>\n>人们可能会将元素与更广为人知的组件混淆。我们将在 [下一节](【译】快速起步-组件与属性.md) 讨论组件。组件是由元素构成的，请不要跳过阅读本章节。\n\n## 渲染元素到DOM中\n\n在您的HTML文件中有一个 `<div>` ：\n\n```html\n<div id=\"root\"></div>\n```\n\n我们称它为根节点，因为它的内容都将被React DOM管理。\n\n使用 `React` 构建的应用程序通常拥有单个根节点。如果你是整合 `React` 到已经存在的应用中，你可以以你喜欢的方式使用多个独立的根节点。\n\n通过 `ReactDOM.render()` 来将 `React 元素` 渲染到 根节点中：\n\n```js\nconst element = <h1>Hello, world</h1>;\nReactDOM.render(\n  element,\n  document.getElementById('root')\n);\n```\n\n[CodePen Demo](http://codepen.io/gaearon/pen/rrpgNB?editors=1010)\n\n它会在页面上显示 \"Hello, world\"。\n\n## 更新渲染完成的元素\n\nReact的元素是 [不可变的](https://en.wikipedia.org/wiki/Immutable_object). 当你创建好一个元素后，你就不能改变它的子集和它的属性。一个元素就代表了某个时间点的UI，就像电影中的帧一样。\n\n到目前我们所知，更新UI的唯一方式就是创建一个新元素，并传递给 `ReactDOM.render()`。\n\n以滴答作响的时钟为例：\n\n```js{8-11}\nfunction tick() {\n  const element = (\n    <div>\n      <h1>Hello, world!</h1>\n      <h2>It is {new Date().toLocaleTimeString()}.</h2>\n    </div>\n  );\n  ReactDOM.render(\n    element,\n    document.getElementById('root')\n  );\n}\n\nsetInterval(tick, 1000);\n```\n\n[CodePen Demo](http://codepen.io/gaearon/pen/gwoJZk?editors=0010)\n\n通过 [`setInterval()`](https://developer.mozilla.org/en-US/docs/Web/API/WindowTimers/setInterval) 的回调函数，每一秒都会调用 `ReactDOM.render()`。\n\n>**注意：**\n>\n>在实际运用中，许多React应用都仅仅只会调用一次 `ReactDOM.render()`。在下一节，我们将会学习如何将这些代码封装为 [有状态组件](【译】快速起步-状态和生命周期.md)。\n>\n>我们建议不要跳过某些主题，因为它们彼此之间具有联系。\n\n## React 只更新有必要的部分\n\nReact DOM将元素及其子元素与上一个进行比较，然后仅将变化了的内容更新到指定DOM上。\n\n可以通过浏览器DOM检查工具来验证 [Demo](http://codepen.io/gaearon/pen/gwoJZk?editors=0010) 的局部更新，![DOM inspector 展示具体更新](https://facebook.github.io/react/img/docs/granular-dom-updates.gif)。\n\n即使我们每时每刻都创建一个表示整个UI树的元素，也只有内容有变化的文本节点才会被React DOM更新。\n\n以我们的经验，考虑UI在某个时刻的展现而不是考虑如何随时间去改变UI，可能会避免很多bug。"
  },
  {
    "path": "React面面观/【译】快速起步-状态和生命周期.md",
    "content": "---\ntitle: 快速起步-状态和生命周期\ndate: 2017-4-13 16:43:28\nversion: 15.4.2\n---\n\n# 状态和生命周期\n\n到目前为止，我们仅学到了一种更新UI的方法。\n\n我们通过调用 `ReactDOM.render()` 来改变渲染输出：\n\n```js{8-11}\nfunction tick() {\n  const element = (\n    <div>\n      <h1>Hello, world!</h1>\n      <h2>It is {new Date().toLocaleTimeString()}.</h2>\n    </div>\n  );\n  ReactDOM.render(\n    element,\n    document.getElementById('root')\n  );\n}\n\nsetInterval(tick, 1000);\n```\n\n[CodePen地址](http://codepen.io/gaearon/pen/gwoJZk?editors=0010)\n\n在本节中，我们将学习如何使得 `Clock` 组件是可重用的和封装的。 它将创建自己的计时器并每秒更新一次。\n\n我们可以用如下方式进行封装：\n\n```js{3-6,12}\nfunction Clock(props) {\n  return (\n    <div>\n      <h1>Hello, world!</h1>\n      <h2>It is {props.date.toLocaleTimeString()}.</h2>\n    </div>\n  );\n}\n\nfunction tick() {\n  ReactDOM.render(\n    <Clock date={new Date()} />,\n    document.getElementById('root')\n  );\n}\n\nsetInterval(tick, 1000);\n```\n\n[CodePen地址](http://codepen.io/gaearon/pen/dpdoYR?editors=0010)\n\n然而，它缺失了一个关键要求：在 `Clock` 组件的具体实现中，需要能创建它自己的定时器，并能每秒更新UI。\n\n理想情况下，我们只需要写一次即可实现自更新的 `Clock`：\n\n```js{2}\nReactDOM.render(\n  <Clock />,\n  document.getElementById('root')\n);\n```\n\n想要实现这个目标，我们需要给 `Clock` 组件添加 `state`。\n\n`state` 和 `props` 类似，但它是组件内部管理的的私有变量。\n\n我们 [之前提到](https://facebook.github.io/react/docs/components-and-props.html#functional-and-class-components)，当使用 `Class` 定义组件时，可以使用一些附加的特性。本地状态就是这样的特性，仅在用 `Class` 定义组件时可用。\n\n## 将 `Function` 转换为 `Class`\n\n使用以下五步，可以将组件 `Clock` 从函数定义方式转换到类定义方式：\n\n1. 创建一个同名的继承自 `React.Component` 的 [ES6 class](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Classes)。\n\n2. 在类中添加空的 `render()`方法。\n\n3. 将函数体内的代码复制到 `render()` 方法中。\n\n4. 在 `render()` 方法中使用 `this.props` 来替代 `props`。\n\n5. 删除多余的空函数定义。\n\n```js\nclass Clock extends React.Component {\n  render() {\n    return (\n      <div>\n        <h1>Hello, world!</h1>\n        <h2>It is {this.props.date.toLocaleTimeString()}.</h2>\n      </div>\n    );\n  }\n}\n```\n\n[CodePen地址](http://codepen.io/gaearon/pen/zKRGpo?editors=0010)\n\n现在 `Clock` 组件是用类定义的，而不是函数定义的。\n\n这让我们可以使用一些附加特性，如本地状态和生命周期钩子。\n\n## 添加本地状态到类\n\n通过以下三个步骤，我们将会把 `date` 从 `props` 移动到 `state` 中：\n\n1) 在 `render()` 方法中，使用 `this.state.date` 来替代 `this.props.date` ：\n\n```js{6}\nclass Clock extends React.Component {\n  render() {\n    return (\n      <div>\n        <h1>Hello, world!</h1>\n        <h2>It is {this.state.date.toLocaleTimeString()}.</h2>\n      </div>\n    );\n  }\n}\n```\n\n2) 添加 [class constructor](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Classes#Constructor) 并初始化 `this.state`：\n\n```js{4}\nclass Clock extends React.Component {\n  constructor(props) {\n    super(props);\n    this.state = {date: new Date()};\n  }\n\n  render() {\n    return (\n      <div>\n        <h1>Hello, world!</h1>\n        <h2>It is {this.state.date.toLocaleTimeString()}.</h2>\n      </div>\n    );\n  }\n}\n```\n\n注意我们是如何传递 `props` 到基类构造函数的：\n\n```js{2}\n  constructor(props) {\n    super(props);\n    this.state = {date: new Date()};\n  }\n```\n\n类组件应始终传递 `props` 并调用基类构造函数。\n\n3) 从 `<Clock />` 元素中移除 `date` 属性：\n\n```js{2}\nReactDOM.render(\n  <Clock />,\n  document.getElementById('root')\n);\n```\n\n稍后，我们将定时器代码块添加到组件本身上。\n\n代码看起来如下：\n\n```js{2-5,11,18}\nclass Clock extends React.Component {\n  constructor(props) {\n    super(props);\n    this.state = {date: new Date()};\n  }\n\n  render() {\n    return (\n      <div>\n        <h1>Hello, world!</h1>\n        <h2>It is {this.state.date.toLocaleTimeString()}.</h2>\n      </div>\n    );\n  }\n}\n\nReactDOM.render(\n  <Clock />,\n  document.getElementById('root')\n);\n```\n\n[CodePen地址](http://codepen.io/gaearon/pen/KgQpJd?editors=0010)\n\n接下来，我们将使 `Clock` 启动自己的定时器，并每秒更新一次。\n\n## 将生命周期方法添加到类\n\n在包含多个组件的应用程序中，在组件释放时，销毁（清理）组件所占用的资源是非常重要的。\n\n我们希望当 `Clock` 渲染到DOM时，第一时间 [启动定时器](https://developer.mozilla.org/en-US/docs/Web/API/WindowTimers/setInterval)。这个阶段在 `React` 中称之为 `mounting`。\n\n我们也希望当 `Clock` 产生的DOM被移除时 [清除定时器](https://developer.mozilla.org/en-US/docs/Web/API/WindowTimers/clearInterval) 。这个阶段在 `React` 中称之为 `unmounting`。\n\n在组件的 `mounting` 和 `unmounting` 阶段，我们可以定义特定的方法来运行我们的代码：\n\n```js{7-9,11-13}\nclass Clock extends React.Component {\n  constructor(props) {\n    super(props);\n    this.state = {date: new Date()};\n  }\n\n  componentDidMount() {\n\n  }\n\n  componentWillUnmount() {\n\n  }\n\n  render() {\n    return (\n      <div>\n        <h1>Hello, world!</h1>\n        <h2>It is {this.state.date.toLocaleTimeString()}.</h2>\n      </div>\n    );\n  }\n}\n```\n\n这些方法就被称之为生命周期钩子（lifecycle hooks）。\n\n`componentDidMount()` 钩子在组件渲染到DOM之后运行，这是启动定时器的好地方：\n\n```js{2-5}\n  componentDidMount() {\n    this.timerID = setInterval(\n      () => this.tick(),\n      1000\n    );\n  }\n```\n\n**注意我们是如何在 `this` 上保存定时器ID的** \n\n`this.props` 是由 `React` 自身管理的，`this.state` 也具有特定的意义。当您需要存储与渲染无关的内容时，也可以手动的在类中添加其他字段。\n\n与 `render()` 无关的内容，没必要存储在 `state` 中。\n\n我们将会在 `componentWillUnmount` 生命周期钩子中清除定时器：\n\n```js{2}\n  componentWillUnmount() {\n    clearInterval(this.timerID);\n  }\n```\n\n最后，我们将实现每秒运行的 `tick()` 方法。\n\n它将使用 `this.setState()` 来实现组件本地状态的更新：\n\n```js{18-22}\nclass Clock extends React.Component {\n  constructor(props) {\n    super(props);\n    this.state = {date: new Date()};\n  }\n\n  componentDidMount() {\n    this.timerID = setInterval(\n      () => this.tick(),\n      1000\n    );\n  }\n\n  componentWillUnmount() {\n    clearInterval(this.timerID);\n  }\n\n  tick() {\n    this.setState({\n      date: new Date()\n    });\n  }\n\n  render() {\n    return (\n      <div>\n        <h1>Hello, world!</h1>\n        <h2>It is {this.state.date.toLocaleTimeString()}.</h2>\n      </div>\n    );\n  }\n}\n\nReactDOM.render(\n  <Clock />,\n  document.getElementById('root')\n);\n```\n\n[CodePen地址](http://codepen.io/gaearon/pen/amqdNA?editors=0010)\n\n现在 `Clock` 会每秒跳动。\n\n我们来简单回顾下发生了什么以及调用方法的顺序：\n\n1) 当 `<Clock />` 传递给 `ReactDOM.render()` 时，`React` 调用了 `Clock` 组件的构造函数。由于 `Clock` 需要显示当前时间，我们通过一个包含当前时间的对象来初始化 `this.state`。稍后，我们将更新这个 `state`。\n\n2) 接下来，`React` 调用 `Clock` 组件的 `render()` 方法。这决定了 `React` 将会在屏幕上显示什么。接着，React 将组件的输出更新到DOM上。\n\n3) 当 `Clock` 组件被添加到DOM之后，React会调用 `componentDidMount()` 钩子函数。 在它的内部， `Clock` 组件启动浏览器定时器，并每秒执行一次 `tick()`。\n\n4) 每一秒，浏览器都会调用 `tick()` 方法。在它的内部，`Clock` 组件将包含当前时间的对象传递给 `this.setState()`，并以此来调度UI变更。当调用 `setState()` 方法，`React` 知道了 `state` 变化，然后就调用 `render()` 来确定要在屏幕上渲染的内容。这个时候, `render()` 中的 `this.state.date` 将和之前不同，会产生包含当前时间的新的输出。于是，React则对应的更新DOM。\n\n5) 当 `Clock` 组件从DOM移除时，`React` 将调用 `componentWillUnmount()` 来停止定时器。\n\n## 正确的使用 `state`\n\n关于 `setState()`，您需要知道以下三点：\n\n### 不要直接修改 `state`\n\n如下代码将不会重新渲染组件：\n\n```js\n// Wrong\nthis.state.comment = 'Hello';\n```\n\n请使用 `setState()` 替代：\n\n```js\n// Correct\nthis.setState({comment: 'Hello'});\n```\n\n唯一可以给 `this.state` 赋值的地方是构造函数。\n\n### 状态更新可能是异步的\n\n为了提高性能，React可能会在单个更新中批量处理多个 `setState()`。\n\n由于 `this.props` 和 `this.state` 可能是异步更新的，您不应该依靠它们的值来计算下一个状态。\n\n以下代码可能不会正确更新计数器：\n\n```js\n// Wrong\nthis.setState({\n  counter: this.state.counter + this.props.increment,\n});\n```\n\n要解决这个问题，可以 `setState()` 的第二种形式，接受一个函数。这个函数的第一个参数是上一个状态，第二个参数是当前属性：\n\n```js\n// Correct\nthis.setState((prevState, props) => ({\n  counter: prevState.counter + props.increment\n}));\n```\n\n以上代码我们使用了 [arrow function](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Functions/Arrow_functions) ，但常规函数也是可以正常使用的：\n\n```js\n// Correct\nthis.setState(function(prevState, props) {\n  return {\n    counter: prevState.counter + props.increment\n  };\n});\n```\n\n### 状态是合并更新\n\n当你调用 `setState()` 时，React 会将您传递的对象合并到当前状态。\n\n您的状态可能包含几个独立变量，如下：\n\n```js{4,5}\n  constructor(props) {\n    super(props);\n    this.state = {\n      posts: [],\n      comments: []\n    };\n  }\n```\n\n您可以通过 `setState()` 更新其中一个：\n\n```js{4,10}\n  componentDidMount() {\n    fetchPosts().then(response => {\n      this.setState({\n        posts: response.posts\n      });\n    });\n\n    fetchComments().then(response => {\n      this.setState({\n        comments: response.comments\n      });\n    });\n  }\n```\n\n这个合并是浅复制，所以 `this.setState({comments})` 完全不影响 `this.state.posts` ，但会整个替换 `this.state.comments`。\n\n## 数据流向\n\n父组件和子组件都不必要知道某个组件是有状态的还是无状态的，并且它们不应该关心它是否被定义函数或类。\n\n这也是为什么 `state` 通常被称为本地状态或封装状态。因为除了组件本身之外，其他组件都是不能直接访问组件状态的。\n\n父组件可以选择将状态通过子组件的属性进行传递：\n\n```js\n<h2>It is {this.state.date.toLocaleTimeString()}.</h2>\n```\n\nThis also works for user-defined components:\n\n```js\n<FormattedDate date={this.state.date} />\n```\n\n`FormattedDate` 组件将通过它自身的 `props` 接收到 `date`，不需要知道 `date` 是来自哪里（`Clock` 组件的状态或属性，乃至手动输入）：\n\n```js\nfunction FormattedDate(props) {\n  return <h2>It is {props.date.toLocaleTimeString()}.</h2>;\n}\n```\n\n[CodePen地址](http://codepen.io/gaearon/pen/zKRqNB?editors=0010)\n\n这通常被称作 `从上到下` 或 `单向` 数据流。任何 `state` 始终由某个特定组件拥有，并且从该 `state` 导出的任何数据或UI都只会影响子集。\n\n如果把组件树想象为瀑布，每个组件的状态都是一个额外的水源，它可以从任一点汇入，但都向下流动。\n\n为了表明组件都是相互隔离的，我们可以创建一个包含三个 `Clock` 组件的 `App` 组件：\n\n```js{4-6}\nfunction App() {\n  return (\n    <div>\n      <Clock />\n      <Clock />\n      <Clock />\n    </div>\n  );\n}\n\nReactDOM.render(\n  <App />,\n  document.getElementById('root')\n);\n```\n\n[CodePen地址](http://codepen.io/gaearon/pen/vXdGmd?editors=0010)\n\n每个 `React` 有自己的定时器，并独立更新（并不会相互影响）。\n\n在 `React` 应用中，组件有无状态被认为是可变的，它是组件的具体实现细节。你可以在有状态组件中使用无状态组件，反之亦然。"
  },
  {
    "path": "React面面观/【译】快速起步-状态提升.md",
    "content": "---\ntitle: 快速起步-状态提升\ndate: 2017-4-25 17:27:42\nversion: 15.5.0\n---\n\n# 状态提升\n\n通常，多个组件会响应同样的变化数据。我们建议将这种共享的状态提升到最近的共同的祖先。让我们看看它是如何工作的。\n\n在这一节中，我们会创建一个温度计算器用来计算水是否在给定的温度下沸腾。\n\n我们先创建一个 `BoilingVerdict`（沸腾判断）组件。它有一个叫 `celsius` （摄氏温度）的属性，并会打印水是否沸腾：\n\n```js{3,5}\nfunction BoilingVerdict(props) {\n  if (props.celsius >= 100) {\n    return <p>The water would boil.</p>;\n  }\n  return <p>The water would not boil.</p>;\n}\n```\n\n下一步，我们创建一个叫 `Calculator`（计算器）的组件。它会渲染一个可让你输入摄氏温度的输入框，并将该输入框的值绑定到 `this.state.temperature` 上。\n\n另外，它也会通过输入框的值渲染 `BoilingVerdict` 组件。\n\n```js{5,9,13,17-21}\nclass Calculator extends React.Component {\n  constructor(props) {\n    super(props);\n    this.handleChange = this.handleChange.bind(this);\n    this.state = {temperature: ''};\n  }\n\n  handleChange(e) {\n    this.setState({temperature: e.target.value});\n  }\n\n  render() {\n    const temperature = this.state.temperature;\n    return (\n      <fieldset>\n        <legend>Enter temperature in Celsius:</legend>\n        <input\n          value={temperature}\n          onChange={this.handleChange} />\n        <BoilingVerdict\n          celsius={parseFloat(temperature)} />\n      </fieldset>\n    );\n  }\n}\n```\n\n[Try it on CodePen.](http://codepen.io/valscion/pen/VpZJRZ?editors=0010)\n\n## 添加第二个输入\n\n我们的新需求是，我们不仅要提供摄氏温度输入，还需要提供华氏温度输入，并要自动进行转换。\n\n我们从 `Calculator` 提取出一个叫 `TemperatureInput` 的组件，并运行传入 `\"c\"` 或者 `\"f\"` 给它的属性 `scale`：\n\n```js{1-4,19,22}\nconst scaleNames = {\n  c: 'Celsius',\n  f: 'Fahrenheit'\n};\n\nclass TemperatureInput extends React.Component {\n  constructor(props) {\n    super(props);\n    this.handleChange = this.handleChange.bind(this);\n    this.state = {temperature: ''};\n  }\n\n  handleChange(e) {\n    this.setState({temperature: e.target.value});\n  }\n\n  render() {\n    const temperature = this.state.temperature;\n    const scale = this.props.scale;\n    return (\n      <fieldset>\n        <legend>Enter temperature in {scaleNames[scale]}:</legend>\n        <input value={temperature}\n               onChange={this.handleChange} />\n      </fieldset>\n    );\n  }\n}\n```\n\n现在我们来更新 `Calculator`，让它渲染两个独立的温度输入组件：\n\n```js{5,6}\nclass Calculator extends React.Component {\n  render() {\n    return (\n      <div>\n        <TemperatureInput scale=\"c\" />\n        <TemperatureInput scale=\"f\" />\n      </div>\n    );\n  }\n}\n```\n\n[Try it on CodePen.](http://codepen.io/valscion/pen/GWKbao?editors=0010)\n\n现在，我们有两个输入框了，但是当我们在其中一个输入值时，另外一个并不会更新。这违背了我们的需求，我们想让它们保持同步。\n\n此时，我们也不能从 `Calculator` 中展示 `BoilingVerdict`。 因为 `Calculator` 并不知道当前的温度，温度是隐藏在 `TemperatureInput` 组件内部的。\n\n## 编写转换功能\n\n首先，我们先编写两个函数来对华氏温度和摄氏温度进行转换：\n\n```js\nfunction toCelsius(fahrenheit) {\n  return (fahrenheit - 32) * 5 / 9;\n}\n\nfunction toFahrenheit(celsius) {\n  return (celsius * 9 / 5) + 32;\n}\n```\n\n这两个函数将会转换不同的温度。我们还需要编写另一个函数，它将字符串 `temperature`（温度）和一个转换器作为参数，并返回一个字符串。我们将使用它来根据一个输入计算另外一个输入。\n\n当 `temperature`（温度）不合法时，它会返回一个空字符串，它也会四舍五入到小数点后第三位：\n\n```js\nfunction tryConvert(temperature, convert) {\n  const input = parseFloat(temperature);\n  if (Number.isNaN(input)) {\n    return '';\n  }\n  const output = convert(input);\n  const rounded = Math.round(output * 1000) / 1000;\n  return rounded.toString();\n}\n```\n\n比如，`tryConvert('abc', toCelsius)` 返回空字符串，`tryConvert('10.22', toFahrenheit)` 返回 `'50.396'`。\n\n## 状态提升\n\n目前，所有的 `TemperatureInput` 组件都将值作为本地状态保存在组件内部：\n\n```js{5,9,13}\nclass TemperatureInput extends React.Component {\n  constructor(props) {\n    super(props);\n    this.handleChange = this.handleChange.bind(this);\n    this.state = {temperature: ''};\n  }\n\n  handleChange(e) {\n    this.setState({temperature: e.target.value});\n  }\n\n  render() {\n    const temperature = this.state.temperature;\n```\n\n然而，我们希望它们能够相互同步这个值。当我们更新摄氏温度时，华氏温度也会自动显示，反之亦然。\n\n在 `React` 中，共享状态是通过将状态移动的最近的共同祖先来实现的。这被称之为 “状态提升”。我们将从 `TemperatureInput` 组件中移除本地状态，并在 `Calculator` 组件中保存状态。\n\n如果 `Calculator` 拥有共享状态，它就成为了多个温度输入组件的的 “source of truth”（译者理解：唯一来源）。它可以让输入组件具有彼此一致的值。自从将 `TemperatureInput` 组件的属性提取到共同的父组件 `Calculator` 后，它们的输入值会总是同步的。\n\n我们看看看它是如何一步步开始工作的。\n\n首先，我们会在 `TemperatureInput` 组件中，使用 `this.props.temperature` 来替换 `this.state.temperature`。现在，先假设 `this.props.temperature` 总是存在的。将来，我们会在 `Calculator` 组件中传递给它：\n\n```js{3}\n  render() {\n    // Before: const temperature = this.state.temperature;\n    const temperature = this.props.temperature;\n```\n\n我们知道 [属性是只读的](【译】快速起步-组件与属性.md)。当 `temperature` 是本地状态时，`TemperatureInput` 组件通过 `this.setState()` 来改变它。然而，`temperature` 成为了父组件的属性，`TemperatureInput` 组件就没有该属性的控制权了。\n\n在 `React` 中，通常让组件 `controlled` 来解决该问题。就像DOM中的 `<input>` 接收一个 `value` 和 `onChange` 属性，自定义的 `TemperatureInput` 会从 `Calculator`（父组件）中接收 `temperature` 和 `onTemperatureChange`。\n\n现在，当 `TemperatureInput` 想更新它的温度，可以调用 `this.props.onTemperatureChange`：\n\n```js{3}\n  handleChange(e) {\n    // Before: this.setState({temperature: e.target.value});\n    this.props.onTemperatureChange(e.target.value);\n```\n\n注意自定义组件中的 `temperature` 和 `onTemperatureChange` 没有特殊的含义。我们也可以用其他名称代替，比如 `value` 和 `onChange` 这种常用的惯例。\n\n组件中的 `onTemperatureChange` 属性将会通知父组件 `Calculator` 温度变化。父组件将通过修改自己的本地状态来处理更新，并为两个输入组件提供新的值。我们将很快看到新的 `Calculator` 实现。\n\n在更新 `Calculator` 之前，我们先更新 `TemperatureInput`。先移除它的本地状态，使用 `this.props.temperature` 来替代`this.state.temperature`。同时，我们调用 `Calculator` 提供的 `this.props.onTemperatureChange()` 来替代自身的 `this.setState()`：\n\n```js{8,12}\nclass TemperatureInput extends React.Component {\n  constructor(props) {\n    super(props);\n    this.handleChange = this.handleChange.bind(this);\n  }\n\n  handleChange(e) {\n    this.props.onTemperatureChange(e.target.value);\n  }\n\n  render() {\n    const temperature = this.props.temperature;\n    const scale = this.props.scale;\n    return (\n      <fieldset>\n        <legend>Enter temperature in {scaleNames[scale]}:</legend>\n        <input value={temperature}\n               onChange={this.handleChange} />\n      </fieldset>\n    );\n  }\n}\n```\n\n现在，我们切换到 `Calculator` 组件。\n\n我们将当前输入组件的 `temperature` 和 `scale` 存储到本地状态中。这是从输入组件中提升的状态，并为输入组件提供真实的值。为了呈现两个输入，我们需要知道所有数据的最小表示。\n\n例如，我们在摄氏温度中输入了 37 ，`Calculator` 组件的状态将会是：\n\n```js\n{\n  temperature: '37',\n  scale: 'c'\n}\n```\n\n如果我们之后在华氏温度中输入了 212，`Calculator` 组件的状态将会是：\n\n```js\n{\n  temperature: '212',\n  scale: 'f'\n}\n```\n\n我们可以存储两个输入的值，但实际上是不必要的。存储最近更改的输入值，以及它所代表的比例就足够了。我们可以基于当前的温度（temperature）和温度类别（scale）来推断另一个输入的值。\n\n这将是输入保持同步，因为它们的值是从相同的状态计算出来的：\n\n```js{6,10,14,18-21,27-28,31-32,34}\nclass Calculator extends React.Component {\n  constructor(props) {\n    super(props);\n    this.handleCelsiusChange = this.handleCelsiusChange.bind(this);\n    this.handleFahrenheitChange = this.handleFahrenheitChange.bind(this);\n    this.state = {temperature: '', scale: 'c'};\n  }\n\n  handleCelsiusChange(temperature) {\n    this.setState({scale: 'c', temperature});\n  }\n\n  handleFahrenheitChange(temperature) {\n    this.setState({scale: 'f', temperature});\n  }\n\n  render() {\n    const scale = this.state.scale;\n    const temperature = this.state.temperature;\n    const celsius = scale === 'f' ? tryConvert(temperature, toCelsius) : temperature;\n    const fahrenheit = scale === 'c' ? tryConvert(temperature, toFahrenheit) : temperature;\n\n    return (\n      <div>\n        <TemperatureInput\n          scale=\"c\"\n          temperature={celsius}\n          onTemperatureChange={this.handleCelsiusChange} />\n        <TemperatureInput\n          scale=\"f\"\n          temperature={fahrenheit}\n          onTemperatureChange={this.handleFahrenheitChange} />\n        <BoilingVerdict\n          celsius={parseFloat(celsius)} />\n      </div>\n    );\n  }\n}\n```\n\n[Try it on CodePen.](http://codepen.io/valscion/pen/jBNjja?editors=0010)\n\n现在，无论你是在哪个温度输入组件中输入，`Calculator` 组件中的 `this.state.temperature` 和 `this.state.scale` 都会更新。其中一个输入值，另一个的输入值总是基于它重新计算，所以随便输入哪个，值都会生效。\n\n让我们回顾一下编辑输入框时会发生什么：\n\n* React会调用DOM元素 `<input>` 上的 `onChange` 函数。在我们的示例中， 是 `TemperatureInput` 组件的 `handleChange` 方法。\n* `TemperatureInput` 组件的 `handleChange` 方法会调用 `this.props.onTemperatureChange()` 并传入新的期望值。这是由父组件 `Calculator` 传递来的属性。\n* 在渲染呈现之前，如果在 `TemperatureInput` 组件中输入摄氏温度，`onTemperatureChange` 将会调用父组件的 `handleCelsiusChange` 方法，如果是输入的华氏温度，`onTemperatureChange` 将会调用父组件的 `handleFahrenheitChange` 方法。 因此，会根据具体的输入，调用这两个方法中的一个。\n* 在这些方法内部，`Calculator` 组件通过 `this.setState()` 通知 `React` 使用最新的输入值来重新渲染它自己。\n* React 调用 `Calculator` 组件的 `render` 方法来确定需要展示的UI。所有输入框的值，都将通过当前温度和温度类型来重新计算。温度就在这个阶段进行转换的。\n* 当 `Calculator` 传递给 `TemperatureInput` 的属性发生变化时，React调用 `TemperatureInput` 组件的 `render()` 方法来确定如何渲染。\n* 最终，React DOM 使用期望的输入值，来更新DOM。在我们更新一个的时候，另一个输入框也就同步更新。\n\n每个更新都会执行相同的步骤，以便输入保持同步。\n\n## 经验教训\n\n对于在 `React` 中更改的任何数据，应该有一个唯一的来源。通常，状态首先会被添加到使用它的组件中。然后，当其他组件也需要它的时候，可以将它提升到最近的公共祖先上，而不是尝试同步这些状态。我们应该使用 [单向数据流](【译】快速起步-状态和生命周期.md)。\n\n提升状态会比双向绑定编写更多的样板代码，但它有一个好处，不容易制造bug。提升状态到单一组件后，仅有该组件可以修改它，这导致错误的可能性大大降低。此外，您可以实现任何自定义的逻辑来拒绝或者转换用户的输入。\n\n如果一些东西可以通过 `props` 或者 `state` 得到，那么他可能并不需要放在共享状态中。例如，我们并没有存储 `celsiusValue` 和 `fahrenheitValue`，而是存储最后编辑的 `temperature` 和 `scale`。其他的值总是可以在 `render()` 中被计算出来。这使得我们可以清理或者四舍五入到其他字段，而不会在用户输入中丢失精度。\n\n当你在UI中看到错误时，您可以使用 [React Developer Tools](https://github.com/facebook/react-devtools) 来检查属性，并向上查找组件树直至到负责更新的组件上。这可以让你跟踪这些错误来源：\n\n<img src=\"https://facebook.github.io/react/img/docs/react-devtools-state.gif\" alt=\"Monitoring State in React DevTools\" width=\"100%\">"
  },
  {
    "path": "React面面观/【译】快速起步-组件与属性.md",
    "content": "---\ntitle: 快速起步-组件与属性\ndate: 2017-4-14 16:01:04\nversion: 15.5.0\n---\n\n# 组件与属性\n\n组件允许您将UI拆分为多个独立的，可重用的部分。\n\n概念上，组件就类似于 `JavaScript` 函数。它们接收任意的输入（`props`），返回用于描述屏幕显示内容的 `React elements`。\n\n## 函数式组件和类组件\n\n最简单定义组件的方式，是编写一个 `JavaScript` 函数：\n\n```js\nfunction Welcome(props) {\n  return <h1>Hello, {props.name}</h1>;\n}\n```\n这个函数是一个合法的组件，因为它接收包含数据的单个 `props` 对象参数，返回了一个 `React elements`。我们称这种用JavaScript函数定义的组件为函数式组件。\n\n你也可以使用 [ES6 class](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Classes) 来定义组件：\n\n```js\nclass Welcome extends React.Component {\n  render() {\n    return <h1>Hello, {this.props.name}</h1>;\n  }\n}\n```\n\n上述两个组件在视图展示上是等同的。\n\n在 [下一章节](【译】快速起步-状态和生命周期.md) 我们会讨论类组件的一些附加特性。在这之前，我们先使用函数式组件来简化演示。\n\n## 渲染组件\n\n之前，我们仅仅遇到表示DOM标签的 `React elements`：\n\n```js\nconst element = <div />;\n```\n\n然而，元素也可以表示用户定义的组件：\n\n```js\nconst element = <Welcome name=\"Sara\" />;\n```\n当 `React` 发现元素表示一个用户定义的组件时，它将 `JSX` 属性作为单个对象传递给组件。我们称之为 `props`。\n\n例如，以下代码在页面上渲染 \"Hello, Sara\"：\n\n```js{1,5}\nfunction Welcome(props) {\n  return <h1>Hello, {props.name}</h1>;\n}\n\nconst element = <Welcome name=\"Sara\" />;\nReactDOM.render(\n  element,\n  document.getElementById('root')\n);\n```\n\n[CodePen Demo](http://codepen.io/gaearon/pen/YGYmEG?editors=0010)\n\n我们回顾下这个例子中发生了什么:\n\n1. 使用 `<Welcome name=\"Sara\" />` 元素调用 `ReactDOM.render()`。\n2. React 将  `{name: 'Sara'}` 作为属性调用 `Welcome` 组件。\n3. `Welcome` 组件将 `<h1>Hello, Sara</h1>` 元素作为结果返回。\n4. React DOM 使用 `<h1>Hello, Sara</h1>` 来更新DOM。\n\n>**警告:**\n>\n>总是用大写字母开头命名自定义组件\n>\n>例如, `<div />` 表示一个DOM标签，但是 `<Welcome />` 表示一个组件，且需要作用域中存在 `Welcome` 变量。\n\n## 复合组件\n\n组件可以在其输出中引用其他组件。这使我们可以对不同级别的实现抽象为相同的组件。在 `React` 中，按钮、表单、对话框，屏幕都通常被抽象为组件。\n\n例如，我们可以创建 `App` 组件来渲染多个 `Welcome` 组件：\n\n```js{8-10}\nfunction Welcome(props) {\n  return <h1>Hello, {props.name}</h1>;\n}\n\nfunction App() {\n  return (\n    <div>\n      <Welcome name=\"Sara\" />\n      <Welcome name=\"Cahal\" />\n      <Welcome name=\"Edite\" />\n    </div>\n  );\n}\n\nReactDOM.render(\n  <App />,\n  document.getElementById('root')\n);\n```\n\n[CodePen Demo](http://codepen.io/gaearon/pen/KgQKPr?editors=0010)\n\n通常，新的React应用程序在最顶层都会有一个 `App` 组件。然而，如果你集成 `React` 到一个现有的应用程序，你可以在内部先使用像 `Button` 这样的小部件，然后逐步的替换到最顶层。\n\n>**警告:**\n>\n>组件必须返回单个根元素。这是为什么我们用 `<div>` 来包含多个 `<Welcome />` 组件。\n\n## 提取组件\n\n不要害怕将组件分成更小的组件。\n\n例如，思考这个 `Comment` 组件：\n\n```js\nfunction Comment(props) {\n  return (\n    <div className=\"Comment\">\n      <div className=\"UserInfo\">\n        <img className=\"Avatar\"\n          src={props.author.avatarUrl}\n          alt={props.author.name}\n        />\n        <div className=\"UserInfo-name\">\n          {props.author.name}\n        </div>\n      </div>\n      <div className=\"Comment-text\">\n        {props.text}\n      </div>\n      <div className=\"Comment-date\">\n        {formatDate(props.date)}\n      </div>\n    </div>\n  );\n}\n```\n\n[CodePen Demo](http://codepen.io/gaearon/pen/VKQwEo?editors=0010)\n\n它接收 `author`(对象类型)，`text`(字符串)和 `date`(日期) 作为属性，然后在社交网站上呈现一个评论。\n\n由于多层嵌套，这个组件很难变化，且很难重用它其中的各个部分。我们可以从中提取几个组件。\n\n首先，我们提取 `Avatar` 组件：\n\n```js{3-6}\nfunction Avatar(props) {\n  return (\n    <img className=\"Avatar\"\n      src={props.user.avatarUrl}\n      alt={props.user.name}\n    />\n  );\n}\n```\n\n`Avatar` 不需要知道谁会渲染它。这也是为什么我们使用 `user` 这个更通用的属性名称来替代 `author`。\n\n我们建议从组件自身的角度来命名属性，而不是它的使用者。\n\n我们现在已经稍微简化了一下 `Comment` 组件：\n\n```js{5}\nfunction Comment(props) {\n  return (\n    <div className=\"Comment\">\n      <div className=\"UserInfo\">\n        <Avatar user={props.author} />\n        <div className=\"UserInfo-name\">\n          {props.author.name}\n        </div>\n      </div>\n      <div className=\"Comment-text\">\n        {props.text}\n      </div>\n      <div className=\"Comment-date\">\n        {formatDate(props.date)}\n      </div>\n    </div>\n  );\n}\n```\n\n下一步，我们将提取 `UserInfo` 组件，用于在用户名旁边显示 `Avator`：\n\n```js{3-8}\nfunction UserInfo(props) {\n  return (\n    <div className=\"UserInfo\">\n      <Avatar user={props.user} />\n      <div className=\"UserInfo-name\">\n        {props.user.name}\n      </div>\n    </div>\n  );\n}\n```\n\n这让我们进一步简化了 `Comment` 组件：\n\n```js{4}\nfunction Comment(props) {\n  return (\n    <div className=\"Comment\">\n      <UserInfo user={props.author} />\n      <div className=\"Comment-text\">\n        {props.text}\n      </div>\n      <div className=\"Comment-date\">\n        {formatDate(props.date)}\n      </div>\n    </div>\n  );\n}\n```\n\n[CodePen Demo](http://codepen.io/gaearon/pen/rrJNJY?editors=0010)\n\n提取组件可能是看起来很繁琐的工作，但是在较大的应用程序中，可重用的组件能够付出更小的代价。\n如果您的UI的一部分使用了几次（按钮，面板，头像）或者它自身就很复杂（App, Comment），它们则是拆分出可重用组件的最佳候选人，这是一个相当实用的经验法则。\n\n## 属性是只读的\n\n当你定义一个组件时，它不应该修改自己的 `props`。思考下面的 `sum` 函数。\n\n```js\nfunction sum(a, b) {\n  return a + b;\n}\n```\n\n这些不会修改输入参数，且当输入参数相同时，总是返回相同结果的函数被称之为 [纯函数](https://en.wikipedia.org/wiki/Pure_function)。\n\n相比之下，以下的函数是不纯的，因为它改变了自己的输入：\n\n```js\nfunction withdraw(account, amount) {\n  account.total -= amount;\n}\n```\n\n`React` 非常灵活，但是也有一个严格的规则：\n\n**所有的React组件必须像纯函数那样使用 `props`（也就是不要在组件内部修改 `props`）**\n\n当然，应用程序的UI是动态的，可以随时间的推移而变化。在 [下一节](【译】快速起步-状态和生命周期.md) 中，我们将介绍新的概念 `state`。`state` 允许React组件根据用户操作，网络响应以及其他随时间推移产生的变化来修改组件输出，而不会违背该规则。"
  },
  {
    "path": "React面面观/【译】快速起步-组合与继承.md",
    "content": "---\ntitle: 快速起步-组合与继承\ndate: 2017-4-26 13:19:51\nversion: 15.5.0\n---\n\n# 组合 VS. 继承\n\n`React` 具有很强大的组合模型，我们推荐使用组合而不是继承来重用组件之间的代码。\n\n在这一章节中，我们将思考一些React新手通常遇到的问题，并展示如何使用组合来解决它们。\n\n## 容器\n\n一些组件并不能提前知道它们的子集。这在通用盒子组件，如 `Sidebar` 或者 `Dialog` 上尤其常见。\n\n我们推荐这类组件使用特定的 `children` 属性，将子元素传递到其输出中：\n\n```js{4}\nfunction FancyBorder(props) {\n  return (\n    <div className={'FancyBorder FancyBorder-' + props.color}>\n      {props.children}\n    </div>\n  );\n}\n```\n\n这允许其他组件通过嵌套JSX传递任意的子集到组件内部：\n\n```js{4-9}\nfunction WelcomeDialog() {\n  return (\n    <FancyBorder color=\"blue\">\n      <h1 className=\"Dialog-title\">\n        Welcome\n      </h1>\n      <p className=\"Dialog-message\">\n        Thank you for visiting our spacecraft!\n      </p>\n    </FancyBorder>\n  );\n}\n```\n\n[Try it on CodePen.](http://codepen.io/gaearon/pen/ozqNOV?editors=0010)\n\n任何JSX标签 `<FancyBorder>` 内部的内容，都会作为 `children` 属性传递给 `FancyBorder` 组件。由于 `FancyBorder` 会在 `<div>` 中渲染 `{props.children}`，传递的元素将会出现在最终输出中。\n\n虽然这不太常见，但有时您可能需要在组件中设置多个“孔”（holes，译者注：插槽）。在这种情况下，您可以采用您自己的约定，而不是使用 `children`：\n\n```js{5,8,18,21}\nfunction SplitPane(props) {\n  return (\n    <div className=\"SplitPane\">\n      <div className=\"SplitPane-left\">\n        {props.left}\n      </div>\n      <div className=\"SplitPane-right\">\n        {props.right}\n      </div>\n    </div>\n  );\n}\n\nfunction App() {\n  return (\n    <SplitPane\n      left={\n        <Contacts />\n      }\n      right={\n        <Chat />\n      } />\n  );\n}\n```\n\n[Try it on CodePen.](http://codepen.io/gaearon/pen/gwZOJp?editors=0010)\n\n像 `<Contacts />` 和 `<Chat />` 这样的React元素只是对象，所以你可以像传递其他数据一样将其作为属性传递。\n\n## 具象化\n\n有时我们认为某些组件是其他组件的“特殊情况”。比如，我们认为 `WelcomeDialog` 是一个特殊的 `Dialog`。\n\n在React中，这也可以通过组合来实现，其中更“特殊”的组件会渲染更“通用”的组件，并且使用属性来配置通用组件：\n\n```js{5,8,16-18}\nfunction Dialog(props) {\n  return (\n    <FancyBorder color=\"blue\">\n      <h1 className=\"Dialog-title\">\n        {props.title}\n      </h1>\n      <p className=\"Dialog-message\">\n        {props.message}\n      </p>\n    </FancyBorder>\n  );\n}\n\nfunction WelcomeDialog() {\n  return (\n    <Dialog\n      title=\"Welcome\"\n      message=\"Thank you for visiting our spacecraft!\" />\n  );\n}\n```\n\n[Try it on CodePen.](http://codepen.io/gaearon/pen/kkEaOZ?editors=0010)\n\n组合对应定义为类的组件同样适用：\n\n```js{10,27-31}\nfunction Dialog(props) {\n  return (\n    <FancyBorder color=\"blue\">\n      <h1 className=\"Dialog-title\">\n        {props.title}\n      </h1>\n      <p className=\"Dialog-message\">\n        {props.message}\n      </p>\n      {props.children}\n    </FancyBorder>\n  );\n}\n\nclass SignUpDialog extends React.Component {\n  constructor(props) {\n    super(props);\n    this.handleChange = this.handleChange.bind(this);\n    this.handleSignUp = this.handleSignUp.bind(this);\n    this.state = {login: ''};\n  }\n\n  render() {\n    return (\n      <Dialog title=\"Mars Exploration Program\"\n              message=\"How should we refer to you?\">\n        <input value={this.state.login}\n               onChange={this.handleChange} />\n        <button onClick={this.handleSignUp}>\n          Sign Me Up!\n        </button>\n      </Dialog>\n    );\n  }\n\n  handleChange(e) {\n    this.setState({login: e.target.value});\n  }\n\n  handleSignUp() {\n    alert(`Welcome aboard, ${this.state.login}!`);\n  }\n}\n```\n\n[Try it on CodePen.](http://codepen.io/gaearon/pen/gwZbYa?editors=0010)\n\n## 那么继承呢？\n\n在Facebook，我们在数以千计的组件中使用React，并且我们没有发现任何用例会要求我们创建组件继承层次结构。\n\n当你需要用安全的方式定制组件的外观和形式时，属性和组合给了你所有的灵活性。 请记住，组件接受任意props，包括原始值、React元素或函数。\n\n如果你想在组件之中重用非UI的功能，我们建议您将其提取到单独的 `JavaScript` 模块中。组件可以导入它，并在不扩展它的情况下，使用哪些函数，对象或者是类。"
  },
  {
    "path": "React面面观/【译】快速起步-表单.md",
    "content": "---\ntitle: 快速起步-表单\ndate: 2017-4-28 08:35:09\nversion: 15.5.0\n---\n\n# 表单\n\nHTML的表单元素和React中的表单元素有所不同，因为表单元素会保持一些内部状态。例如，以下是一个接收单个name的纯HTML表单：\n\n```html\n<form>\n  <label>\n    Name:\n    <input type=\"text\" name=\"name\" />\n  </label>\n  <input type=\"submit\" value=\"Submit\" />\n</form>\n```\n\n当用户提交表单时，该表单具有打开新页面的默认行为。这种用法放在React中也是可用的。但是，大多数时候，我们希望使用JavaScript函数处理表单提交并能方便的访问用户输入的表单数据。实现这一点的标准方式是使用一种称之为受控组件（controlled components）的技术。\n\n## 受控组件\n\n在HTML中，诸如 `<input>`, `<textarea>`, 和 `<select>` 这样的表单元素通常自己维护自身的状态，并根据用户输入自动更新。在React中，可变的状态通常保存在组件的 `state` 中，并仅仅只能通过 [`setState()`](【译】参考手册-React组件.md)来更新。\n\n我们使用React的 `state` 作为单一数据源来结合这两者。然后，React组件在渲染表单时，也能响应用户的输入。值被React以这种方式控制的表单输入元素被称作受控组件。\n\n例如，如果我们想使上一个例子在提交时记录name，那么我们可以编写如下的受控组件表单：\n\n```javascript{4,10-12,24}\nclass NameForm extends React.Component {\n  constructor(props) {\n    super(props);\n    this.state = {value: ''};\n\n    this.handleChange = this.handleChange.bind(this);\n    this.handleSubmit = this.handleSubmit.bind(this);\n  }\n\n  handleChange(event) {\n    this.setState({value: event.target.value});\n  }\n\n  handleSubmit(event) {\n    alert('A name was submitted: ' + this.state.value);\n    event.preventDefault();\n  }\n\n  render() {\n    return (\n      <form onSubmit={this.handleSubmit}>\n        <label>\n          Name:\n          <input type=\"text\" value={this.state.value} onChange={this.handleChange} />\n        </label>\n        <input type=\"submit\" value=\"Submit\" />\n      </form>\n    );\n  }\n}\n```\n\n[Try it on CodePen.](https://codepen.io/gaearon/pen/VmmPgp?editors=0010)\n\n自从我们如上设置了表单元素的 `value` 属性，显示的value将总是 `this.state.value`, React的state是唯一数据源。`handleChange` 将在每次敲键盘之后执行，用于更新React的 `state`，并将显示值更新为用户输入。\n\n使用受控组件，每个状态的变化都具有相关的处理函数。这使得可以直接修改或者验证用户的输入。例如，如果我们要强制name属性全大写，我们可以如下编写 `handleChange`，如下：\n\n```javascript{2}\nhandleChange(event) {\n  this.setState({value: event.target.value.toUpperCase()});\n}\n```\n\n## TextArea标签\n\n在HTML中，`<textarea>` 标签使用它的子集来定义它显示的文本：\n\n```html\n<textarea>\n  Hello there, this is some text in a text area\n</textarea>\n```\n\n在React中，`<textarea>` 使用 `value` 来替代。这样，使用 `<textarea>` 就非常类似于用于单行输入的表单：\n\n```javascript{4-6,12-14,26}\nclass EssayForm extends React.Component {\n  constructor(props) {\n    super(props);\n    this.state = {\n      value: 'Please write an essay about your favorite DOM element.'\n    };\n\n    this.handleChange = this.handleChange.bind(this);\n    this.handleSubmit = this.handleSubmit.bind(this);\n  }\n\n  handleChange(event) {\n    this.setState({value: event.target.value});\n  }\n\n  handleSubmit(event) {\n    alert('An essay was submitted: ' + this.state.value);\n    event.preventDefault();\n  }\n\n  render() {\n    return (\n      <form onSubmit={this.handleSubmit}>\n        <label>\n          Name:\n          <textarea value={this.state.value} onChange={this.handleChange} />\n        </label>\n        <input type=\"submit\" value=\"Submit\" />\n      </form>\n    );\n  }\n}\n```\n\n注意，`this.state.value` 需要在构造函数中初始化，以便设置 `textarea` 的初始值。\n\n## Select标签\n\n在HTML中, `<select>` 创建了一个下拉列表。例如，下面的HTML创建了一个关于味道的下拉列表：\n\n```html\n<select>\n  <option value=\"grapefruit\">葡萄柚味</option>\n  <option value=\"lime\">柠檬味</option>\n  <option selected value=\"coconut\">椰子味</option>\n  <option value=\"mango\">芒果味</option>\n</select>\n```\n\n注意，`<select>` 初始选中了椰子味，是因为 `<option>` 上的 `seleceted` 属性。在React中，在根 `select` 标签上用 `value` 属性替代了 `select` 属性。这样在受控组件中更方便，因为您只需要更新一个位置。例如：\n\n```javascript{4,10-12,24}\nclass FlavorForm extends React.Component {\n  constructor(props) {\n    super(props);\n    this.state = {value: 'coconut'};\n\n    this.handleChange = this.handleChange.bind(this);\n    this.handleSubmit = this.handleSubmit.bind(this);\n  }\n\n  handleChange(event) {\n    this.setState({value: event.target.value});\n  }\n\n  handleSubmit(event) {\n    alert('Your favorite flavor is: ' + this.state.value);\n    event.preventDefault();\n  }\n\n  render() {\n    return (\n      <form onSubmit={this.handleSubmit}>\n        <label>\n          Pick your favorite La Croix flavor:\n          <select value={this.state.value} onChange={this.handleChange}>\n            <option value=\"grapefruit\">Grapefruit</option>\n            <option value=\"lime\">Lime</option>\n            <option value=\"coconut\">Coconut</option>\n            <option value=\"mango\">Mango</option>\n          </select>\n        </label>\n        <input type=\"submit\" value=\"Submit\" />\n      </form>\n    );\n  }\n}\n```\n\n[Try it on CodePen.](https://codepen.io/gaearon/pen/JbbEzX?editors=0010)\n\n总的来说，`<input type=\"text\">`, `<textarea>`, 和 `<select>` 都能很简单的运行，它们都接受一个 `value` 属性，所以您能使用它来实现受控组件。\n\n## 处理多个输入\n\n当你需要处理多个受控的 `input` 元素，你可以添加一个 `name` 属性当这些元素上，并可以在处理函数中，通过 `event.target.name` 来决定执行什么。\n\n例如：\n\n```javascript{15,18,28,37}\nclass Reservation extends React.Component {\n  constructor(props) {\n    super(props);\n    this.state = {\n      isGoing: true,\n      numberOfGuests: 2\n    };\n\n    this.handleInputChange = this.handleInputChange.bind(this);\n  }\n\n  handleInputChange(event) {\n    const target = event.target;\n    const value = target.type === 'checkbox' ? target.checked : target.value;\n    const name = target.name;\n\n    this.setState({\n      [name]: value\n    });\n  }\n\n  render() {\n    return (\n      <form>\n        <label>\n          Is going:\n          <input\n            name=\"isGoing\"\n            type=\"checkbox\"\n            checked={this.state.isGoing}\n            onChange={this.handleInputChange} />\n        </label>\n        <br />\n        <label>\n          Number of guests:\n          <input\n            name=\"numberOfGuests\"\n            type=\"number\"\n            value={this.state.numberOfGuests}\n            onChange={this.handleInputChange} />\n        </label>\n      </form>\n    );\n  }\n}\n```\n\n[Try it on CodePen.](https://codepen.io/gaearon/pen/wgedvV?editors=0010)\n\n注意，如何使用ES6 [computed property name](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Operators/Object_initializer#Computed_property_names) 语法来更新动态的name属性：\n\n```js{2}\nthis.setState({\n  [name]: value\n});\n```\n\n它等价于如下的ES5代码：\n\n```js{2}\nvar partialState = {};\npartialState[name] = value;\nthis.setState(partialState);\n```\n\n由于 `setState()` 会自动 [合并状态到当前状态](【译】快速起步-状态和生命周期.md)，我们仅需要传入有变化的部分。\n\n## 受控组件的替代方案\n\n使用受控组件有时可能很繁琐，因为您需要为每个可以变化的数据编写一个事件处理程序，并通过一个React组件管理所有的输入状态。当您将预先存在的代码转换为React，或者将React与非React程序集成时，这可能会变得特别麻烦。在这种情况下，你可能需要尝试 [不受控组件](【译】高级指南-不受控组件.md)，一种实现输入表单的替代技术。"
  },
  {
    "path": "React面面观/【译】高级指南-不受控组件.md",
    "content": "---\ntitle: 高级指南-不受控组件\ndate: 2017-4-28 10:47:03\nversion: 15.5.0\n---\n\n# 不受控组件\n\n在很多时候，我们推荐使用 [受控组件](【译】快速起步-表单.md) 来实现表单。在受控组件中，React组件会处理表单数据。 反方向则是不受控组件，由DOM自身来处理表单数据。\n\n您可以 [使用 `ref`](【译】高级指南-Ref和DOM.md) 来从DOM上获取表单数据，而不是为每个状态更新编写事件处理器，这就是不受控组件。\n\n例如，在不受控组件中接收单个name值：\n\n```javascript{8,17}\nclass NameForm extends React.Component {\n  constructor(props) {\n    super(props);\n    this.handleSubmit = this.handleSubmit.bind(this);\n  }\n\n  handleSubmit(event) {\n    alert('A name was submitted: ' + this.input.value);\n    event.preventDefault();\n  }\n\n  render() {\n    return (\n      <form onSubmit={this.handleSubmit}>\n        <label>\n          Name:\n          <input type=\"text\" ref={(input) => this.input = input} />\n        </label>\n        <input type=\"submit\" value=\"Submit\" />\n      </form>\n    );\n  }\n}\n```\n\n[Try it on CodePen.](https://codepen.io/gaearon/pen/WooRWa?editors=0010)\n\n由于不受控组件将数据来源保存在DOM中，因此使用不受控组件与非React程序集成时会更为容易。如果你想更快速或变更，这样也能稍微减少一些代码。否则，您应该使用受控组件。\n\n如果你仍然不清楚你应该为特定场景选择哪种类型的组件，你可以在 [受控组件 VS. 不受控组件](http://goshakkk.name/controlled-vs-uncontrolled-inputs-react/) 找到帮助。\n\n### 默认值\n\n在React的生命周期中，表单元素上的 `value` 属性将会覆盖DOM上的值。在不受控组件中，你通常想要在React中指定初始值，但不再控制后续更新。要处理这个场景，你需要使用特定的 `defaultValue` 属性来替代 `value`。\n\n```javascript{7}\nrender() {\n  return (\n    <form onSubmit={this.handleSubmit}>\n      <label>\n        Name:\n        <input\n          defaultValue=\"Bob\"\n          type=\"text\"\n          ref={(input) => this.input = input} />\n      </label>\n      <input type=\"submit\" value=\"Submit\" />\n    </form>\n  );\n}\n```\n\n同样的，`<input type=\"checkbox\">` 和 `<input type=\"radio\">` 中使用 `defaultChecked`，`<select>` 和 `<textarea>` 中使用 `defaultValue`。"
  },
  {
    "path": "React面面观/【译】高级指南-深入JSX.md",
    "content": "---\ntitle: 高级指南-深入JSX\ndate: 2017-4-5 17:13:09\nversion: 15.4.2\n---\n\n# 深入JSX\n\n从根本上来讲，`JSX` 仅仅是提供 `React.createElement(component, props, ...children)` 方法的语法糖。\n\nJS代码：\n\n```js\n<MyButton color=\"blue\" shadowSize={2}>\n  Click Me\n</MyButton>\n```\n\n将会被编译为：\n\n```js\nReact.createElement(\n  MyButton,\n  {color: 'blue', shadowSize: 2},\n  'Click Me'\n)\n```\n\n如果它们（组件/元素）没有子元素，也可以使用自闭合的标签形式，如：\n\n```js\n<div className=\"sidebar\" />\n```\n\n将会被编译为：\n\n```\nReact.createElement(\n  'div',\n  {className: 'sidebar'},\n  null\n)\n```\n\n如果你要测试特定的 `JSX` 将会转换为怎样的 `JavaScript` 代码，可以使用：[the online Babel compiler](https://babeljs.io/repl/#?babili=false&evaluate=true&lineWrap=false&presets=es2015%2Creact%2Cstage-0&targets=&browsers=&builtIns=false&code=function%20hello()%20%7B%0A%20%20return%20%3Cdiv%3EHello%20world!%3C%2Fdiv%3E%3B%0A%7D).\n\n## 指定React元素类型\n\n`JSX` 标签的第一部分，决定了该元素的类型。\n\n大写开头的 `JSX标签` 是表示 `React` 组件。这些标签将被编译为对命名变量的直接引用。所以，如果要使用 `<Foo />` 表达式，必须要保证 `Foo` 必须在作用域内。\n\n### React必须在作用域内\n\n由于 `JSX` 编译为调用 `React.createElement`，那么 `React` 库也必须在 `JSX 代码` 所在的作用域内。\n\n例如，即使 `React` 和 `CustomButton` 没有直接在 `JavaScript` 中被引用，顶部的 `import` 语句也是必须的：\n\n```js{1,2,5}\nimport React from 'react';\nimport CustomButton from './CustomButton';\n\nfunction WarningButton() {\n  // return React.createElement(CustomButton, {color: 'red'}, null);\n  return <CustomButton color=\"red\" />;\n}\n```\n\n即使你没有使用打包工具，且是从 script 标签引入 `React`，`React` 也是以全局变量的形式存在于作用域内。\n\n### 对JSX类型使用点分割\n\n你们同样可以使用点表示法来引入 `React` 组件。如果您有一个导出了多个组件的模块，这样做会很方便。例如，如果 `MyComponents.DatePicker` 是一个组件，我们可以采用如下方式引入：\n\n```js{10}\nimport React from 'react';\n\nconst MyComponents = {\n  DatePicker: function DatePicker(props) {\n    return <div>Imagine a {props.color} datepicker here.</div>;\n  }\n}\n\nfunction BlueDatePicker() {\n  return <MyComponents.DatePicker color=\"blue\" />;\n}\n```\n\n### 用户定义的组件必须大写\n\n当一个元素类型是以小写字母开头的，它指的是内置组件，如 `<div>` 或 `<span>`，并将字符串 `'div'` 或 `'span'` 传递给 `React.createElement`。以大写开头的类型如 `<Foo />` 将被编译为 `React.createElement(Foo)`，对应在 `JavaScript` 中定义或导入的组件。\n\n我们建议使用大写字母开头来命名组件。如果您的组件以小写字母开头，请在使用 `JSX` 之前将其赋值给大写的变量。\n\n例如，以下代码不会如预期运行：\n\n```js{3,4,10,11}\nimport React from 'react';\n\n// 错误！组件应该大写开头\nfunction hello(props) {\n  // 正确！div是一个合法的html标签，如下使用是可行的：\n  return <div>Hello {props.toWhat}</div>;\n}\n\nfunction HelloWorld() {\n  // 错误！ React 认为 <hello /> 是一个HTML标签，因为它不是大写开头的：\n  return <hello toWhat=\"World\" />;\n}\n```\n\n要想解决该问题，我们必须将 `hello` 重命名为 `Hello`，并使用 `<Hello />` 来引用它：\n\n```js{3,4,10,11}\nimport React from 'react';\n\n// 正确！组件大写开头：\nfunction Hello(props) {\n  // 正确！\n  return <div>Hello {props.toWhat}</div>;\n}\n\nfunction HelloWorld() {\n  // 正确！React 认为 <Hello /> 是一个组件。\n  return <Hello toWhat=\"World\" />;\n}\n```\n\n### 在运行时选择类型\n\n你不能使用一个常规表达式作为 `React元素` 的类型。如果你想用常规表达式来表明元素的的类型，请先将它赋值给一个大写变量。当您要根据属性的变化来渲染不同的组件时，将会遇到此场景：\n\n```js{10,11}\nimport React from 'react';\nimport { PhotoStory, VideoStory } from './stories';\n\nconst components = {\n  photo: PhotoStory,\n  video: VideoStory\n};\n\nfunction Story(props) {\n  // 错误！JSX类型不能是一个表达式。\n  return <components[props.storyType] story={props.story} />;\n}\n```\n\n要想解决这个问题，我们可以将值赋给一个大写变量。\n\n```js{9-11}\nimport React from 'react';\nimport { PhotoStory, VideoStory } from './stories';\n\nconst components = {\n  photo: PhotoStory,\n  video: VideoStory\n};\n\nfunction Story(props) {\n  // 正确！大写开头的变量能够被正确识别。\n  const SpecificStory = components[props.storyType];\n  return <SpecificStory story={props.story} />;\n}\n```\n\n## JSX 中的属性（Props）\n\n在 `JSX` 中，有多种不同的方式来指定属性。\n\n### JavaScript表达式\n\n你可以将 `JavaScript` 表达式传递为属性，使用 `{}` 包裹。例如：\n\n```js\n<MyComponent foo={1 + 2 + 3 + 4} />\n```\n\n对于 `MyComponent`， `props.foo` 的值将会是 `10`，因为表达式 `1+2+3+4` 会被计算。\n\n`if` 语句和 `for` 循环语句在 `JavaScript` 中不是表达式，所以不能直接在 `JSX` 中使用。相反，你可以将它们放在代码中使用，例如：\n\n```js{3-7}\nfunction NumberDescriber(props) {\n  let description;\n  if (props.number % 2 == 0) {\n    description = <strong>even</strong>;\n  } else {\n    description = <i>odd</i>;\n  }\n  return <div>{props.number} is an {description} number</div>;\n}\n```\n\n### 字符串\n\n你可以将字符串传递给属性，以下两种方式是等效的：\n\n```js\n<MyComponent message=\"hello world\" />\n\n<MyComponent message={'hello world'} />\n```\n\n当你传递字符串时，它的值是未转义的。所以，以下两种方式也是等效的：\n\n```js\n<MyComponent message=\"&lt;3\" />\n\n<MyComponent message={'<3'} />\n```\n\n这种行为通常是不相关的，在这里提到只是说我们可以这样去用。\n\n### 属性默认值为\"True\"\n\n当我们传递一个无值的属性，它的默认值是`true`。以下两个语法也是等价的：\n\n```js\n<MyTextBox autocomplete />\n\n<MyTextBox autocomplete={true} />\n```\n\n一般来说，我们不建议这样使用，因为它可能与[ES6对象简写](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Operators/Object_initializer#New_notations_in_ECMAScript_2015) `{foo: foo}` 简写为 `{foo}` 混淆。这种形式之所以存在，是因为它符合HTML的规则。\n\n### 属性扩散（Spread Attributes）\n\n如果你已经有了一个对象 `props`，你又想将它传递到 `JSX`，那么可以使用 `...` 来实现扩散操作，将整个 `props` 对象进行传递。以下两个组件也是等价的：\n\n```js{7}\nfunction App1() {\n  return <Greeting firstName=\"Ben\" lastName=\"Hector\" />;\n}\n\nfunction App2() {\n  const props = {firstName: 'Ben', lastName: 'Hector'};\n  return <Greeting {...props} />;\n}\n```\n\n当你构建通用的容器时，属性扩散是非常有用的。然而，它们也非常容易传递给组件很多不相关的属性，从而导致代码变得凌乱。我们建议谨慎的使用此语法。\n\n## JSX中的子元素\n\n在包含开始和结束标签的JSX表达式中，标签之间的内容将传递给特定属性 `props.children`。有几种不同的方法来传递子元素：\n\n### 字符串\n\n你可以将字符串放在开始和结束标签中，此时 `props.children` 就只是该字符串。这对于很多内置的HTML元素很有用。例如：\n\n```js\n<MyComponent>Hello world!</MyComponent>\n```\n这是一个合法的JSX，`MyComponent` 中的 `props.children` 是一个简单的字符串 `Hello world!`。HTML是未转义的，所以同样可以使用如下方式书写：\n\n```html\n<div>This is valid HTML &amp; JSX at the same time.</div>\n```\n\nJSX在行首和行尾移除空格。它也移除空行。与标签向邻的新行会被删除，文本字符串之间的新行会被压缩为一个空格。如下以下的几种方式都是等同的：\n\n```js\n<div>Hello World</div>\n\n<div>\n  Hello World\n</div>\n\n<div>\n  Hello\n  World\n</div>\n\n<div>\n\n  Hello World\n</div>\n```\n\n### JSX Children\n\n你能够提供多个JSX元素做为子元素。这对于显示嵌套组件非常有用：\n\n```js\n<MyContainer>\n  <MyFirstComponent />\n  <MySecondComponent />\n</MyContainer>\n```\n\n你也能够组合多种类型的子元素，所以你可以一起使用字符串和JSX组件。这是JSX的类型与HTML的一种形式，这样JSX和HTML都是有效的。\n\n\n```html\n<div>\n  Here is a list:\n  <ul>\n    <li>Item 1</li>\n    <li>Item 2</li>\n  </ul>\n</div>\n```\n\n一个 `React` 组件不能返回多个React元素，但是单个的 `JSX表达式` 能够有多个子元素，所以如果你想一个组件包含多个子元素，你可以使用 `div` 来包装，如上。\n\n### JavaScript表达式\n\n你也可以传递 JavaScript表达式 来作为子元素，将其包含在 `{}` 内。例如，以下的表达式是等价的：\n\n```js\n<MyComponent>foo</MyComponent>\n\n<MyComponent>{'foo'}</MyComponent>\n```\n这对于展示任意长度的列表是非常有用的。例如，渲染HTML列表：\n\n```js{2,9}\nfunction Item(props) {\n  return <li>{props.message}</li>;\n}\n\nfunction TodoList() {\n  const todos = ['finish doc', 'submit pr', 'nag dan to review'];\n  return (\n    <ul>\n      {todos.map((message) => <Item key={message} message={message} />)}\n    </ul>\n  );\n}\n```\nJavaScript表达式也可以和其他类型的子元素组合。通常用于替代字符串模板：\n\n```js{2}\nfunction Hello(props) {\n  return <div>Hello {props.addressee}!</div>;\n}\n```\n\n### 函数作为子元素\n\n通常，JSX中的JavaScript表达式将会被计算为字符串，React元素或者是这些内容的一个列表。然而，`props.children` 就像其他属性一样工作，它可以传递任何类型的数据，而不仅仅是React知道如何渲染的类型。例如，如果你有一个自定义组件，你可以将它作为 `props.children` 进行回调：\n\n```js{4,13}\n// Calls the children callback numTimes to produce a repeated component\nfunction Repeat(props) {\n  let items = [];\n  for (let i = 0; i < props.numTimes; i++) {\n    items.push(props.children(i));\n  }\n  return <div>{items}</div>;\n}\n\nfunction ListOfTenThings() {\n  return (\n    <Repeat numTimes={10}>\n      {(index) => <div key={index}>This is item {index} in the list</div>}\n    </Repeat>\n  );\n}\n```\n\n传递给自定义组件的子元素可以是任何东西，只要在该组件呈现之前被转换成React能够理解的东西。这种用法并不常见，但如果你想扩展JSX，它也是可以工作的。\n\n### 布尔值，NULL和Undefined将被忽略\n\n`false, null, undefined, true` 都是合法的子元素。它们根本不会被渲染。以下的JSX表达式都会渲染出同样的结果：\n\n\n```js\n<div />\n\n<div></div>\n\n<div>{false}</div>\n\n<div>{null}</div>\n\n<div>{undefined}</div>\n\n<div>{true}</div>\n```\n\n这有助于条件渲染其他React元素，以下JSX 当 `showHeader` 为 `true` 时，才仅仅渲染 `<Header />`：\n\n\n```js{2}\n<div>\n  {showHeader && <Header />}\n  <Content />\n</div>\n```\n\n值得注意的是有一些 [\"falsy\" values](https://developer.mozilla.org/en-US/docs/Glossary/Falsy)（一些JS中转换为false的值），如数字0，仍然会被React渲染。例如，以下代码不会像你预期的那样工作，当 `props.messages` 为空数组时，也会打印0出来：\n\n```js{2}\n<div>\n  {props.messages.length &&\n    <MessageList messages={props.messages} />\n  }\n</div>\n```\n\n想解决这个问题，确保 `&&` 之前总是布尔值：\n\n\n```js{2}\n<div>\n  {props.messages.length > 0 &&\n    <MessageList messages={props.messages} />\n  }\n</div>\n```\n\n反之，如果你想渲染 `false, true, null, undefined` 等值，你可以先将它们转换为字符串：\n\n```js{2}\n<div>\n  My JavaScript variable is {String(myVariable)}.\n</div>\n```"
  },
  {
    "path": "React面面观/【译】高级指南-高阶组件.md",
    "content": "---\ntitle: 高级指南-高阶组件\ndate: 2017-4-6 13:11:48\nversion: 15.4.2\n---\n\n# 高阶组件\n\n高阶组件（HOC）是React中重用组件逻辑的高级技术。HOCs本身不是 `React` API的一部分。它们是从 `React` 的组成性质中呈现出的一种模式。\n\n**具体来说，高级组件就是一个传入一个组件，并返回一个新组件的函数。**\n\n```js\nconst EnhancedComponent = higherOrderComponent(WrappedComponent);\n```\n\n组件是将属性转换成UI，高阶组件是将组件转换为另一个组件。\n\n`HOCs` 在第三方库中很常见，如 `Redux` 的 `connect` 和 `Relay` 的 `createContainer`。\n\n本文将会讨论为什么高阶组件是有用的和如何编写高阶组件。\n\n## 使用高阶组件来分离关注点（Cross-Cutting Concerns）\n\n> **注意：**\n>\n> 我们曾经建议使用mixins来作为分离关注点的方式。我们已经意识到，使用mixin比它们创造的价值制造了更多的麻烦。[查看这里](https://facebook.github.io/react/blog/2016/07/13/mixins-considered-harmful.html) 来了解我们如何从mixin迁移和如何转换现有的组件。\n\n组件是 `React` 中代码重用的主要单元。然而，你会发现一些模式对于传统的组件来说，并不容易实现。\n\n例如，你有一个 `CommentList` 组件通过订阅外部数据源来渲染评论列表：\n\n```js\nclass CommentList extends React.Component {\n  constructor() {\n    super();\n    this.handleChange = this.handleChange.bind(this);\n    this.state = {\n      // \"DataSource\" 是一些全局数据源\n      comments: DataSource.getComments()\n    };\n  }\n\n  componentDidMount() {\n    // 订阅变化\n    DataSource.addChangeListener(this.handleChange);\n  }\n\n  componentWillUnmount() {\n    // 取消订阅\n    DataSource.removeChangeListener(this.handleChange);\n  }\n\n  handleChange() {\n    // 当数据变化时，更新组件\n    this.setState({\n      comments: DataSource.getComments()\n    });\n  }\n\n  render() {\n    return (\n      <div>\n        {this.state.comments.map((comment) => (\n          <Comment comment={comment} key={comment.id} />\n        ))}\n      </div>\n    );\n  }\n}\n```\n\n之后，您编写了一个订阅单个博文的组件，它遵循类似的模式：\n\n```js\nclass BlogPost extends React.Component {\n  constructor(props) {\n    super(props);\n    this.handleChange = this.handleChange.bind(this);\n    this.state = {\n      blogPost: DataSource.getBlogPost(props.id)\n    };\n  }\n\n  componentDidMount() {\n    DataSource.addChangeListener(this.handleChange);\n  }\n\n  componentWillUnmount() {\n    DataSource.removeChangeListener(this.handleChange);\n  }\n\n  handleChange() {\n    this.setState({\n      blogPost: DataSource.getBlogPost(this.props.id)\n    });\n  }\n\n  render() {\n    return <TextBlock text={this.state.blogPost} />;\n  }\n}\n```\n\n`CommentList` 和 `BlogPost` 是不一样的，它们调用了不一样的 `DataSource` 方法，同时它们也渲染不同的结果。但它们的大部分实现是相同的：\n\n* 在 `mount` 的时候，添加一个数据变化监听器到 `DataSource`\n* 在监听器内部，当数据源变化时，调用 `setState`\n* 在 `unmount` 的时候，移除监听器\n\n你可以想象，在一个大型的应用程序中，同样的订阅 `DataSource` 和 调用 `setState` 的模式将会经常发生。我们想实现一个抽象实现，允许我们在单一的地方定义这个逻辑，并共享到多个组件中。这就是高阶组件的优点。\n\n我们可以编写一个函数来创建组件，像 `CommentList` 和 `BlogPost`，订阅到 `DataSource`。这个函数将接受一个子组件，并将订阅数据作为一个属性返回。我们来调用函数 `withSubscription`:\n\n```js\nconst CommentListWithSubscription = withSubscription(\n  CommentList,\n  (DataSource) => DataSource.getComments()\n);\n\nconst BlogPostWithSubscription = withSubscription(\n  BlogPost,\n  (DataSource, props) => DataSource.getBlogPost(props.id)\n});\n```\n\n第一个参数是包装组件。第二个参数通过给定的 `DataSource` 和当前属性来检索数据。\n\n当 `CommentListWithSubscription` 和 `BlogPostWithSubscription` 被渲染时， `CommentList` 和 `BlogPost` 将获取到 `DataSource` 的当前数据并传递到 `data` 属性：\n\n```js\n// 需要一个包装组件\nfunction withSubscription(WrappedComponent, selectData) {\n  // 返回新的组件\n  return class extends React.Component {\n    constructor(props) {\n      super(props);\n      this.handleChange = this.handleChange.bind(this);\n      this.state = {\n        data: selectData(DataSource, props)\n      };\n    }\n\n    componentDidMount() {\n      // 订阅数据变化\n      DataSource.addChangeListener(this.handleChange);\n    }\n\n    componentWillUnmount() {\n      DataSource.removeChangeListener(this.handleChange);\n    }\n\n    handleChange() {\n      this.setState({\n        data: selectData(DataSource, this.props)\n      });\n    }\n\n    render() {\n      // 使用最新的组件渲染包装组件\n      // 注意，这里传递了一些附加属性\n      return <WrappedComponent data={this.state.data} {...this.props} />;\n    }\n  };\n}\n```\n\n注意 `HOC` 并没有修改传入的组件，也没有使用继承来复制它的行为。相反的是，高阶组件通过使用容器组件包裹的方式来组合原始组件。`HOC` 是具有零副作用的纯函数。\n\n就是这样！包装组件从容器组件处接收所有的属性，也包含一个新的属性 `data`，并使用它来呈现输出。高阶组件不关心数据怎么用，为什么用；包装组件也不关心数据来自那儿。\n\n因为 `withSubscription` 是一个常规的方法，所以你可以添加任意数量的参数。例如，你可能想使用数据属性的名称可配置，以便进一步的隔离高阶组件与包装组件。或者您可以接受配置 `shouldComponentUpdate` 或配置数据源的参数。这些都是可以做到的，因为高阶组件完全控制了组件的定义。\n\n和组件一样， `withSubscription` 与包装组件的联系完全是基于属性的。这样就可以很轻松的将一个高阶组件包装为不同的高阶组件，只需要它们为包装组件提供相同的属性。例如，这对于更改数据获取方式是相当有用的。\n\n## 不要改变原始组件。使用组合。\n\n避免在高阶组件内部修改组件原型（或其他方式修改组件）\n\n\n```js\nfunction logProps(InputComponent) {\n  InputComponent.prototype.componentWillReceiveProps(nextProps) {\n    console.log('Current props: ', this.props);\n    console.log('Next props: ', nextProps);\n  }\n  // 实际上，我们返回了一个已经被改变的原始组件\n  return InputComponent;\n}\n\n// EnhancedComponent 将在接收props时记录日志\nconst EnhancedComponent = logProps(InputComponent);\n```\n\n这样会有一些问题。一个是输入组件不能与增强组件分开重用。更关键的是，如果你将另一个也修改了 `componentWillReceiveProps` 的高阶组件也应用于 `EnhancedComponent`，第一个高阶组件的功能将会被覆盖。这个高阶组件也不适用于没有生命周期方法的功能组件。\n\n变更包装组件的高阶组件是一种不好的实现 -- 消费者必须知道它们是如何实现的，以避免和其他高阶组件冲突。\n\n高阶组件应该通过容器组件包装输入组件这种组合的方式来代替直接更改：\n\n```js\nfunction logProps(WrappedComponent) {\n  return class extends React.Component {\n    componentWillReceiveProps(nextProps) {\n      console.log('Current props: ', this.props);\n      console.log('Next props: ', nextProps);\n    }\n    render() {\n      // 使用容器组件来包装输入组件，而不是直接修改，非常棒！\n      return <WrappedComponent {...this.props} />;\n    }\n  }\n}\n```\n这样的高阶组件与直接修改的组件功能一样，同时还能够避免发生冲突。它和类或者功能组件一样工作良好。同时，因为它是一个纯函数，它还可以同其他高阶组件，甚至是自己进行组合。\n\n您可能已经注意到高阶组件和容器组件模式之间的相似之处。容器组件是职责分离策略低级别与高级别之间的一部分。容器管理如订阅和状态之类的东西，并将属性传递给处理渲染UI之类的组件。高阶组件将容器作为其实现的一部分。您可以将高阶组件理解为参数化的容器组件定义。\n\n## 惯例：通过包裹组件传递不相关的属性\n\n高阶组件给组件添加特性。它们不应该大幅度修改它们之间的联系。预期从高阶组件返回的组件与包装组件应具有相似的界面效果。 \n\n高阶组件应通过属性传递非具体的关注点。许多高阶组件包含以下的渲染方法：\n\n```js\nrender() {\n  // Filter out extra props that are specific to this HOC and shouldn't be\n  // passed through\n  const { extraProp, ...passThroughProps } = this.props;\n\n  // Inject props into the wrapped component. These are usually state values or\n  // instance methods.\n  const injectedProp = someStateOrInstanceMethod;\n\n  // Pass props to wrapped component\n  return (\n    <WrappedComponent\n      injectedProp={injectedProp}\n      {...passThroughProps}\n    />\n  );\n}\n```\n\n这个惯例有助于确保高阶组件尽可能的灵活和可重用。\n\n## 惯例：最大化组合度\n\n不是所有的高阶组件都像之前那样。有时它们仅仅接受单个包装组件参数：\n\n```js\nconst NavbarWithRouter = withRouter(Navbar);\n```\n\n通常，高阶组件会接受额外的参数。在来自 `Relay` 的示例中，配置对象用于指定组件数据的依赖关系：\n\n```js\nconst CommentWithRelay = Relay.createContainer(Comment, config);\n```\n\n高阶组件最常见的签名如下：\n\n```js\n// React Redux's `connect`\nconst ConnectedComment = connect(commentSelector, commentActions)(Comment);\n```\n\n什么？！如果你把它分开看，就更容易看明白发生了什么。\n\n```js\n// connect 是一个返回另一个函数的函数\nconst enhance = connect(commentListSelector, commentListActions);\n// 返回的函数就是高阶组件，返回一个关联到 Redus store 的组件\nconst ConnectedComment = enhance(CommentList);\n```\n\n换句话说，`connect` 是一个高阶函数，返回一个高阶组件。\n\n单参数高阶组件就像是具有 `Component => Component` 签名的 `connect` 函数返回的高阶组件。同样输入输出的函数很容易组合在一起。\n\n\n```js\n// 不建议这样做\nconst EnhancedComponent = connect(commentSelector)(withRouter(WrappedComponent))\n\n// 你可以使用通用函数compose\n// compose(f, g, h) 等价于 (...args) => f(g(h(...args)))\nconst enhance = compose(\n  // 这些都是单参数高阶组件\n  connect(commentSelector),\n  withRouter\n)\nconst EnhancedComponent = enhance(WrappedComponent)\n```\n\n（在 `connect` 和 `enhancer-style` 的高阶函数中，也允许使用装饰器来使用同样的属性，一个试验性质的JavaScript提案）\n\n`compose` 通用函数在很多三方库中都有提供，如：`lodash` [lodash.flowRight](https://lodash.com/docs/4.17.4#flowRight)，[Redux](http://redux.js.org/docs/api/compose.html)，和 [Ramda](http://ramdajs.com/docs/#compose)。\n\n## 惯例：包装显示名称方便调试\n\n高阶组件创建的容器组件在 [React Developer Tools](https://github.com/facebook/react-devtools) 中和其他组件一样的显示。为了方便调试，选择一个显示名称来表明它是一个高阶组件。\n\n最常见的技术就是包装包装组件的显示名称。所以，如果你的高阶组件名称是 `withSubscription`，你的包装组件名称是 `CommentList`，那么请使用显示名称 `WithSubscription(CommentList)`:\n\n```js\nfunction withSubscription(WrappedComponent) {\n  class WithSubscription extends React.Component {/* ... */}\n  WithSubscription.displayName = `WithSubscription(${getDisplayName(WrappedComponent)})`;\n  return WithSubscription;\n}\n\nfunction getDisplayName(WrappedComponent) {\n  return WrappedComponent.displayName || WrappedComponent.name || 'Component';\n}\n```\n\n## 警告\n\n高阶函数有一些警告。如果你是React新手，则可能不容易发现。\n\n### 不要在渲染方法中使用高阶组件\n\n`React` 的 `diff算法` 使用组件标识来确定它是该更新现有的子树，还是将其丢弃，并安装新的子树。 如果从 `render` 返回的组件和前一个渲染的组件相同，则 `React` 通过将其与最新的子对比来递归更新子树。如果它们不等，以前的子树将被卸载。\n\n通常情况下，你不需要考虑这个。但这对于高阶组件来说很重要，这意味着您不能将高阶组件用于render方法中的组件。\n\n```js\nrender() {\n  // 每次render，将会创建一个新版本的EnhancedComponent组件\n  // EnhancedComponent1 !== EnhancedComponent2\n  const EnhancedComponent = enhance(MyComponent);\n  // 这会导致整个子树都会被卸载和重新挂载\n  return <EnhancedComponent />;\n}\n```\n这里导致的问题不仅仅是性能问题 -- 重新挂载组件会导致该组件及所有的子组件状态丢失。\n\n应该将高阶组件应用于组件定义之外，以便只需要创建一次组件。然后，它们在渲染时的标识将会是一致的。这才是你需要的。\n\n在少许需要动态使用高阶函数的情况下，您还可以在组件的生命周期方法或构造函数中执行此操作。\n\n### 必须复制静态方法\n\n有时候在 `React` 组件上定义静态方法会很有用。例如，`Relay` 容器公开了一个静态方法 `getFragment` 来方便 `GraphQL` 片段的组合。\n\n当你应用高阶组件时，是通过容器组件来包裹包装组件。这意味着新组件没有任何原始组件的静态方法。\n\n```js\n// 定义静态方法\nWrappedComponent.staticMethod = function() {/*...*/}\n// 使用高阶组件\nconst EnhancedComponent = enhance(WrappedComponent);\n\n// 新的组件没有原组件的静态方法\ntypeof EnhancedComponent.staticMethod === 'undefined' // true\n```\n\n要解决这个问题，你需要在返回组件组件前，将静态方法拷贝到容器组件上。\n\n```js\nfunction enhance(WrappedComponent) {\n  class Enhance extends React.Component {/*...*/}\n  // 必须要知道那个方法需要被拷贝:(\n  Enhance.staticMethod = WrappedComponent.staticMethod;\n  return Enhance;\n}\n```\n\n但是，这需要您准确的知道那些方法需要被拷贝。你可以使用 [hoist-non-react-statics](https://github.com/mridgway/hoist-non-react-statics) 来自动拷贝所有非React的静态方法。\n\n```js\nimport hoistNonReactStatic from 'hoist-non-react-statics';\nfunction enhance(WrappedComponent) {\n  class Enhance extends React.Component {/*...*/}\n  hoistNonReactStatic(Enhance, WrappedComponent);\n  return Enhance;\n}\n```\n\n另一个解决方案是将静态方法和组件本身独立导出。\n\n```js\n// 不这样做\nMyComponent.someFunction = someFunction;\nexport default MyComponent;\n\n// 单独导出静态方法\nexport { someFunction };\n\n// 并在消费模块中导入两者\nimport MyComponent, { someFunction } from './MyComponent.js';\n```\n\n### 不要使用 `ref` 传递\n\n虽然高阶组件的惯例是将所有属性传递给包装组件，但尽可能的不要传递 `ref`。那是因为 `ref` 并不是一个真正的属性 -- 就像 `key`，它是由React特别处理的。如果你添加了一个引用到一个包含高阶组件返回的结果组件的元素上，这个引用将指向外部容器的实例，而不是包装组件。\n\n如果您发现自己面临这个问题，最好的办法是找出如何避免使用 `ref`。有时候，React的新手会在更适合 `prop` 的场景下使用 `ref`。\n\nThat said, there are times when refs are a necessary escape hatch — React wouldn't support them otherwise. \n对于直接控制组件，给输入框设置焦点是一个很好的例子。在这种情况下，使用不同的名称把ref的回调当成常规 `prop` 传递是一个解决方法。\n\n```js\nfunction Field({ inputRef, ...rest }) {\n  return <input ref={inputRef} {...rest} />;\n}\n\n// 使用高阶组件包装 Field\nconst EnhancedField = enhance(Field);\n\n// Inside a class component's render method...\n<EnhancedField\n  inputRef={(inputEl) => {\n    // This callback gets passed through as a regular prop\n    this.inputEl = inputEl\n  }}\n/>\n\n// 现在，你可以调用命令式方法\nthis.inputEl.focus();\n```\n\n这不是一个完美的解决办法。更好的做法是将 `refs` 作为类库的关注点，而不是要求您手动处理它们。我们正在探索如何解决它从而让高阶组件是不可观察的。"
  },
  {
    "path": "RxJS小记/02_RxJS之Observable.md",
    "content": "---\ntitle: 02_RxJS之Observable\ndate: 2017-3-31 17:25:52\n---\n\n# 0x0、Observable（可观察对象）\n\n`Observable` 含义是可观察对象，那什么是可观察对象呢？这就涉及到 `Observer Pattern`（观察者模式） 和 `Iterator Pattern`（迭代模式）了。\n\n我们先来实现一个简单的观察者：\n\n```javascript\nclass Subject {\n  constructor() {\n    this.listeners = [];\n  }\n\n  // 添加观察者\n  addListener(listener) {\n    this.listeners.push(listener);\n    return true;\n  }\n\n  // 移除观察者\n  removeListener(listener) {\n    let listenerIdx = this.listeners.indexOf(listener);\n    if (listenerIdx >= 0) {\n      this.listeners.splice(listenerIdx, 1);\n      return true;\n    }\n    return false;\n  }\n\n  // 通知\n  notify(msg) {\n    this.listeners.forEach(listener => listener(msg));\n  }\n}\n```\n\n如何用？\n\n```javascript\nconst sub = new Subject();\nsub.addListener(msg => console.log('观察者1', msg));\nsub.addListener(msg => console.log('观察者2', msg));\n\nsub.notify('Subject变化了');\n```\n\n当有变更时，会通知到所有的观察者。这就是简单的观察者模式实现。\n\n接着，我们来看一下迭代器模式在JS的使用：\n\n```javascript\nlet arr = [1, 2, 3];\n// 数组实现迭代器，我们可以通过如下方式获取到\nlet arrIterator = arr[Symbol.iterator]();\narrIterator.next(); // {value: 1, done: false}\narrIterator.next(); // {value: 2, done: false}\narrIterator.next(); // {value: 3, done: false}\narrIterator.next(); // {value: undefined, done: true}\n```\n\n当我们手动 `next()` 的时候，才会返回给我们结果，当迭代完成后，始终返回 `{value: undefined, done: true}`。\n\n再来看 `Obsrevable`，其实就是这两者的结合。能够主动向订阅者推送，通过又能有顺序的推送（当next被调用的时候进行推送）。\n\n# 0x1、简单实现一个可观察对象\n\n```javascript\nclass Observable {\n  constructor(fn) {\n    this.observer = {};\n    fn(this.observer);\n  }\n  subscribe(next) {\n    this.observer.next = next;\n  }\n}\n```\n\n调用代码如下：\n\n```javascript\nvar ob = new Observable(ob => {\n  setInterval(() => {\n    ob.next(new Date());\n  }, 5000);\n});\n\nob.subscribe(msg => console.log('订阅者1', msg));\n```\n\n执行代码，我们可以看到，每间隔5s就会输出订阅者和对应的消息（当前时间）。由于 `Observable` 的类别有很多，我们就不在一一模拟。\n\n# 0x2、RxJs之Observable\n\n在 `RxJS` 中，核心就是 `Observable` ，当然，肯定不会像以上实现的那么简单。\n\n我们接着就来看下在 `RxJS` 中如何创建 `Observable` 对象。\n\n能够创建 `Observable` 的操作有如下几种：\n\n* create\n* of\n* from\n* fromEvent\n* fromEventPattern\n* fromPromise\n* never\n* empty\n* throw\n* interval\n* timer\n\n### 2.1 使用 `create` 创建 `Observable` 实例\n\n```javascript\nlet source = Rx.Observable.create(observer => {\n  observer.next(1);\n  observer.next(2);\n  observer.next(3);\n  observer.complete(); // 当调用complete之后，就不会再输出之后的next了。\n  observer.next(4);\n});\n\nsource.subscribe(v => {\n  console.log(v);\n});\n```\n\n### 2.2 使用 `fromEvent` 创建 `Observable` 实例\n\n可以用如下方式，来简化对原生事件的使用。当点击页面时，会执行订阅的方法。\n\n```javascript\nvar source = Rx.Observable.fromEvent(document, 'click');\n\nvar sub =source.subscribe(e => {\n  console.log(e);\n});\n\nsub.unsubscribe();\n```\n\n### 2.3 使用 `fromPromise` 创建 `Observable` 实例\n\n此时只需要传入一个 `Promise` 对象即可，用法如下：\n\n```javascript\nvar p = Promise.resolve('ab');\n\nRx.Observable.fromPromise(p)\n  .subscribe(v => {\n    console.log('当Promise resolve的时候执行', v);\n  }, reason => {\n    console.log('当Promise reject的时候执行', reason);\n  });\n```\n\n### 2.4 使用 `interval` 创建 `Observable` 实例\n\n顾名思义，就是和setTimeout类似，但是可以用 `Observable` 统一的 `subscribe` 和 `unscbscribe`。\n\n```javascript\nRx.Observable.interval(1000)\n  .subscribe(idx => {\n    console.log('我每秒都会输出', idx); // idx是下标，从0开始\n  });\n```\n\n### 2.5 使用 `timer` 创建 `Observable` 实例\n\n当给 `timer` 传递1个整数时，表示等待多少秒之后执行，当传递两个整数时，表示等待多少秒后执行，然后间隔多少秒后再次执行。\n\n```javascript\nRx.Observable.timer(5000, 2000)\n  .subscribe(idx => {\n    console.log('我每秒都会输出', idx); // idx是下标，从0开始\n  });\n```\n\n### 2.6 使用 `from` 创建 `Observable` 实例\n\n`from` 是一个超级强大的创建方式，可以接受任意可枚举的参数，还能接受字符串。\n\n```javascript\nRx.Observable.from('我是字符串');\nRx.Observable.from([1, 2, 3, 4, 5]);\nRx.Observable.from(new Set());\nRx.Observable.from(new Map());\nRx.Observable.from(new Promise(resolve, reject){\n  resolve('abc');\n});\n```\n\n### 2.6 使用 `never, empty, throw` 创建 `Observable` 实例\n\n这三者都是创建一个比较特殊的流。 \n\n`never` 表示没有结束，无法收到任何效应。\n\n`empty` 表示一个空的 `observable`，订阅的话，会立即打印执行完成。\n\n```javascript\nvar source = Rx.Observable.empty();\nsource.subscribe(v => {\n  console.log(v);\n}, err => {\n  console.log(err);\n}, c => {\n  console.log('complete'); // 会输出该行\n});\n```\n\n`throw` 就很明显，订阅就会抛出错误。\n\n```javascript\nvar source = Rx.Observable.throw(new Error('Error'));\nsource.subscribe(v => {\n  console.log(v);\n}, err => {\n  console.log(err); // 执行这行。\n}, c => {\n  console.log('complete');\n});\n```\n\n那这些有啥用呢？当然是为了组合其他 `observable` 进行操作了。\n\n### 2.7 使用 `of` 创建 `Observable` 实例\n\n`of` 主要是接收 `List` 类型的参数，主要是 `Array`\n\n```javascript\nvar source = Rx.Observable.of([1, 2, 3, 4, 5]);\n```\n\n### 2.8 使用 `fromEventPattern` 创建 `Observable` 实例\n\n`fromEventPattern` 是用于给 `类Event（有addListener, removeListener类似的API）` 来使用的。\n\n```javascript\nvar subject = {\n  listeners: [],\n  addListener(fn) {\n    this.listeners.push(fn);\n  },\n  removeListener(fn) {\n    let idx = this.listeners.indexOf(fn);\n    if (idx >= 0) {\n      this.listeners.splice(idx, 1);\n    }\n  },\n  notify(msg) {\n    this.listeners.forEach(listener => listener(msg));\n  }\n};\n\nRx.Observable.fromEventPattern(subject.addListener.bind(subject), subject.removeListener.bind(subject))\n  .subscribe(console.log); // 输出 'hello, observable'\nsubject.notify('hello, observable');\n```\n\n**至此，我们已经了解如何创建 observable 对象了。**"
  },
  {
    "path": "Sass学习之路/01_Sass学习之路：Sass、Compass安装与命令行.md",
    "content": "---\ntitle: 01_Sass学习之路：Sass、Compass安装与命令行\ndate: 2017/02/21 14:47:10\n---\n\n## 导言\n\nCSS不是一门真正意义上的编程语言，很多编程语言理所当然的特性（比如变量），都不被支持。同时再开发模块化的web项目的时候，也要避免相互干扰。为了弥补CSS的这些不足，就产生了**[CSS预处理器]()**，Sass则是其中的佼佼者。\n\n## 什么是Sass\n**[Sass](http://sass-lang.com/)**是最成熟、稳定、强大、专业的CSS扩展语言（官方解释）。直白点，Sass就是一个非常好用的CSS预处理器，为css引入部分编程语言的特性。\n\nSass在现阶段，有两种编码的语法，一个是兼容CSS语法的Scss格式文件，一个是Haml、Ruby类似语法的Sass格式文件。一般情况下，我们选用第一种兼容Css语法的Scss文件格式\n\n## 什么是Compass\n**[Compass](http://compass-style.org/)**是基于Sass的一个css创作框架，其实就是基于Sass提供了很多非常实用的函数，有点类库的概念。\n\n## 如何安装\nSass是基于Ruby写的，安装Sass需要先安装Ruby：[https://www.ruby-lang.org/zh_cn/downloads/](https://www.ruby-lang.org/zh_cn/downloads/)。\n\n在Windows上安装Ruby，需要借助RubyInstall工具：[http://rubyinstaller.org/](http://rubyinstaller.org/)\n\n安装好Ruby只有，可以使用cmd：ruby -v 查看ruby的版本，如果有输出，表示ruby安装成功。这个时候，则可以使用``ruby gem sass``来安装Sass，``ruby gem compass``来安装Compass。\n\n***注意事项**：由于gem仓库被墙了，如果想使用的话，需要切换镜像地址，国内可以采用淘宝的ruby镜像：``http://ruby.taobao.org``。可以通过如下命令实现：*\n\n    //移除官方gems\n\tgem sources --remove https://rubygems.org/\n\t//添加淘宝的gems   \n\tgem sources -a https://ruby.taobao.org/\n\t//查看现有的gems\n\tgem sources -l\n\n## 命令行\n\n\t//编译Sass\n\tsass <sass file> <css file>\n\t\n\t//Sass与Scss相互转换\n\tsass-convert <.sass file> <.scss file>\n\tsass-convert <.scss file> <.sass file>\n\t\n\t//监视Sass文件变更，自动编译(可选输出css的风格，参数为style)\n\tsass --watch <sass file>:<css file> [--style [nested|expanded|compact|compressed]]\n\t\n\t//监视文件夹中Sass文件变更，自动编译\n\tsass --watch <sass folder>:<ouput css folder>\n\t\n\t//----------------------Compass--------------\n\t\n\t//创建Sass工程\n\tcompass create\n\t\n\t//编译sass文件\n\tcompass compile\n\t\n\t//监视Sass工程下sass文件变更(可选输出css的风格，参数为output-style)\n\tcompass --watch [--output-style [nested|expaned|compact|compressed]]\n\n## 更简单的使用方式\n\n看了以上这么多的步骤，是不是感觉头疼？\n\n**老夫看你天资聪慧，将来必成大器，特为你带来一本秘籍，祝你早日功成！**\n\n在真正的开发环境中，我们一般这么用，结果node+gulp等构建工具。\n\n首先，需要安装node,然后使用npm安装gulp和gulp-sass。\n\n接着，编写一个基于gulp的构建脚本，如果是其他构建工具，那么编写对应的脚本即可，gulpfile.js代码如下：\n\n\tvar gulp = require('gulp'),\n\t  sass = require('gulp-sass');\n\t\n\tgulp.task('default', ['sass', 'watch'], function () {\n\t  console.log('Begin watching...');\n\t});\n\t\n\tgulp.task('sass', function () {\n\t  return gulp.src('./*.scss')\n\t    .pipe(sass({\n\t      outputStyle: 'expanded'\n\t    }).on('error', sass.logError))\n\t    .pipe(gulp.dest('./css/'));\n\t});\n\t\n\tgulp.task('watch', function () {\n\t  return gulp.watch('./*.scss', ['sass']);\n\t});\n\n最后，使用控制台，启动gulp，然后畅快的编写sass代码吧。\n"
  },
  {
    "path": "Sass学习之路/02_Sass学习之路：注释、变量以及导入.md",
    "content": "---\ntitle: 02_Sass学习之路：注释、变量以及导入\ndate: 2017/02/21 14:47:10\n---\n\n## 前言\n\n由于.sass不兼容CSS代码，所以以下内容完全使用.scss的语法。\n\n## Sass注释\n\nSass中除了提供CSS中的标准注释之外，还提供了一种静默注释：\n\n\t/* 我是标准注释 */\n\t// 我是静默注释\n\n标准注释大多数情况下（**一种例外：设置输出风格为compressed**）是会生成到最终的CSS中的，而静默注释的话，只是Sass的注释，是不会被生成到CSS文件中的。\n\n如果想，就算设置输出风格为compressed也要加入特定注释呢？那么可以采用**重要注释**写法，如下：\n\n\t/*! 我的重要注释 */\n\n也就是在标准注释的基础上，将注释的第一个字符设置为感叹号。\n\n## Sass变量\n\nSass作为一个CSS预处理器，那么最基本的编程语言特性**变量**自然也是必不可少的。Sass中变量系统相对比较丰富，包含局部变量，全局变量，默认变量，特殊变量（变量作用在属性上），多值变量。\n\n**Sass变量以$开头，用:分割变量名与变量值，以;结尾。如： $color: red;**\n\n**Sass变量，不区分中横线和下划线，比如定义变量为$bg-color，那么使用$bg_color也能访问到哦！**\n\n### 局部变量\n\n同大多数编程语言一样，变量的作用域是它本身的这个块，以及所有的子集。如:\n\n\tbody{\n\t  $color: blue;\n\t  color: $color;\n\t  .container{\n\t    background-color: $color;    \n\t  }\n\t}\n\n将被编译为：\n\n\tbody {\n\t  color: blue;\n\t}\n\t\n\tbody .container {\n\t  background-color: blue;\n\t}\n\n同时，需要注意：针对以上代码，如果内部修改了$color的值，也会同时影响到外部的变量值。\n\n### 全局变量\n\n如果定义在局部的变量，与它同级的选择器中是无法使用的：\n\n\tbody{\n\t  $color: blue;\n\t  color: $color;\n\t  .container{\n\t    background-color: $color;    \n\t  }\n\t}\n\t\n\t// 会出现错误，未定义的变量\n\tfooter{\n\t  color: $color;\n\t}\n\n这个时候，就需要提升$color为全局变量，**在变量值之后，加上!global**,示例如下：\n\n\tbody{\n\t  $color: blue !global;\n\t  color: $color;\n\t  .container{\n\t    background-color: $color;    \n\t  }\n\t}\n\t\n\t// 会出现错误，未定义的变量\n\tfooter{\n\t  color: $color;\n\t}\n\n这个的话，就能够正常编译了，因为$color被提升为全局变量了。\n\n### 默认变量\n\n在Sass中，可以通过在**变量值之后加入!default**来让变量称为默认变量，如果有对该变量的赋值，不管前后，那么变量的默认值都会被替换掉，示例如下：\n\n\t$color: red;\n\tbody{\n\t  $color: blue !default;\n\t  color: $color;\n\t  .container{\n\t    background-color: $color;    \n\t  }\n\t}\n\tfooter{\n\t  color: $color;\n\t}\n\n可以通过注释掉!default来查看生成的CSS的异同。\n\n### 特殊变量\n\nSass的变量，还可以用在属性中，此时需要使用#{变量名}来引用，示例如下：\n\n\t$containerId: c1;\n\t\n\t##{$containerId}{\n\t  color: red;\n\t}\n\n此时，生成的CSS为：\n\n\t#c1 {\n\t  color: red;\n\t}\n\n### 多值变量\n\n强大的Sass，还提供了一种特殊的变量，即为多值变量，在一个变量中，可以定义多个值，然后通过制定的函数访问，示例如下：\n\n\t// List类型的多值变量 \n\t$px: 5px 10px 15px 20px;\n\t\n\t// Map类型的多值变量 \n\t$map: (id1: test, id2: testGrid, color: red);\n\t\n\tbody{\n\t  // 此处需要注意，索引是从1开始，不是从0哦。\n\t  margin-left: nth($px, 1);\n\t\n\t  // 使用map-get访问，当心key的使用，指定了不存在的key会导致生成的css异常 \n\t  #{map-get($map, id2)}{\n\t    color: red;\n\t  }\n\t\n\t  #id{\n\t    color: map-get($map, color);\n\t  }\n\t}\n\n生成的CSS如下：\n\n\tbody {\n\t  margin-left: 5px;\n\t}\n\t\n\tbody testGrid {\n\t  color: red;\n\t}\n\t\n\tbody #id {\n\t  color: red;\n\t}\n\n## 导入\n\n体现可维护性的重要指标就是文件似乎可以单一职责，那么在Sass中，主要体现在导入上。由于Sass中的导入指令和CSS的导入指令是同样的关键字，那么就需要按照一定的规则来判别了，满足以下任意一条规则的导入，使用CSS的原生导入：\n\n1. 被导入的文件名以.css结尾\n2. 被导入的文件是一个在线的url地址\n3. 以@import url(...)方式去导入文件\n\n同时，编写局部的sass文件，建议使用下划线开头,如： _a.scss，这样就不会生成多余的_a.css了。\n\n\t//_a.scss\n\t#id2{\n\t  color: red;\n\t}\n\n\t//1.scss\n\t@import \"a.scss\";\n\t#id1{\n\t  color: blue;\n\t}\n\n\tbody{\n\t\t@import \"a.scss\";\n\t}\n\n最终生成的结果为:\n\n\t#id2 {\n\t  color: red;\n\t}\n\t\n\t#id1 {\n\t  color: blue;\n\t}\n\t\n\tbody #id2 {\n\t  color: red;\n\t}\n\n## 结尾\n\n欲知后事如何，请听下回分解！\n\n\n\n"
  },
  {
    "path": "Vue实践之路/01_认识Vue.md",
    "content": "---\ntitle: 01_认识Vue\ndate: 2017/02/21 14:47:10\n---\n\n# 0、关于Vue\n\n[Vue](https://vuejs.org.cn/) 是当前非常流行的一款前端 MV* 库（国人开发），结合 vue-router, vue-resource, vuex 等等，就形成了一套比较完善的前端 MV* 开发框架。\n\n与它非常相似的框架是 [Avalon](http://avalonjs.coding.me/) ，都借鉴了当前流行的前端 MV* 库、框架，都是基于 ES5 getter setter 实现双向绑定。\n\n与 ``Avalon`` 相比， ``Vue`` 在稳定性和文档方面更胜一筹。\n\n但 ``Avalon`` 利用 ``VBScript`` 在IE6+上实现了双向绑定，不过文档和稳定性稍微有些欠缺。\n\n那么如果没有IE9-的兼容性要求，有需要产线环境，那么Vue就是非常合适的一个东西了。\n\n# 1、配套工具\n\n``Vue`` 本身仅仅是一个双向绑定，组件化的库，要实现一个完整的应用，那么还需要其他的一些配套工具。\n\n实际上，在 ``Vue`` 发布到流行的这几年间，它的配套工具已经相当完善了。特别是 [vue-cli](https://github.com/vuejs/vue-cli)，更是极大的减少了环境搭建的成本。\n\n其他的路由，Ajax，数据流， [vuejs组织](https://github.com/vuejs) 也都提供了相应的package来支撑。\n\n1. 路由  vue-router\n2. Ajax/HTTP vue-resource\n3. 数据流 vuex\n\n# 2、开始一个项目\n\n如果有Angular等前端框架使用经验，那么可以很轻松的切入vue的使用，因为有太多的似曾相识。\n\n另外，通过vue-cli，我们可以不用自己去搭建开发环境，几条命令就足以让我们能够看到一个能跑起来的完整项目。特别是还自带热更新，自动编译，自动刷新等强大的构建功能。\n\n来领略一下vue的魅力吧：\n\n```\n// 全局安装vue-cli命令行工具\n$ npm install -g vue-cli  \n\n// 在当前目录中创建一个demo1目录，并创建一个基于webpack的开发环境。\n//（该命令是向导式方式，可以设置一些属性）\n$ vue init webpack demo1 // vue-init <template-name> [project-name]\n\n// 进入真正的项目目录\n$ cd demo1 \n\n// 安装依赖，你懂的~\n$ npm install \n\n// 运行开发环境（注意：默认端口是8080，执行该命令后不会自动打开页面，\n// 需要打开浏览器手动输入 http://localhost:8080 ）\n$ npm run dev \n```\n\n仅仅有开发环境，你满足了么？反正我是不满足的，``vue-cli`` 还提供了完善的命令，能够直接打包产线所需要的资源。\n\n```\n$ npm run build // 生产环境打包\n```\n\n# 3、认识.vue\n\n``.vue`` 是vue特有的一种文件格式，一个 ``.vue`` 文件也是一个独立的组件。\n\n它把内部内容分为三块，样式，模板和逻辑（JS），逻辑部分原生支持 ``ES2015``，简单的示例如下：\n\n```\n<style>\n  <!-- 用于放置组件样式 -->\n</style>\n<template>\n  <!-- 用于放置组件模板 -->\n</template>\n<script>\n  // 用于放置组件逻辑代码\n</script>\n```\n\n其中的 style 标记，还可以使用 ``scoped`` 标记来生成模块化CSS，也可以使用 ``lang=\"<lang>\"`` 来选择使用一个CSS预处理器。\n\n我喜欢的方式如下：\n\n```html\n<style scoped lang=\"stylus\"></style>\n```\n\n**注意：如果选择了使用预处理器，那么需要安装特定的预处理器loader，如使用 ``npm install stylus-loader`` 来增加对 ``stylus`` 的支持。 **\n\n# 4、其他\n\n主流MVVM框架性能比较\n\n![主流MVVM框架性能比较](http://avalonjs.coding.me/styles/performance.jpg)"
  },
  {
    "path": "Vue实践之路/02_Vue组件（上）.md",
    "content": "---\ntitle: 02_Vue组件（上）\ndate: 2017/02/21 14:47:10\n---\n\n# 0、关于Vue组件\n\n组件是 ``Vue`` 中最强大的功能之一，Vue组件也和angular的组件比较类似，可以扩展HTML元素。在较高层面上，也是自定义元素。\n\n在原生HTML元素上附加功能，``Vue`` 的做法是通过 ``is`` 特性扩展， ``ng2`` 中则称之为属性指令。\n\n# 1、定义组件\n\n在 ``Vue`` 中定义组件是一个很轻松的方式，代码如下：\n\n```javascript\n// 定义组件构造器\nvar Component1 = Vue.extend({\n\n});\n\n// 全局注册组件\nVue.component('component-1', Component1);\n\n// 也可局部注册组件\n// 局部注册意味着该组件仅能在包含它的组件中运行，如示例的Parent组件\nvar Parent = Vue.extend({\n  template: '',\n  components: {\n    'component-1': Component1\n  }\n});\n```\n\n为了足够简单，``Vue`` 还提供了一个语法糖写法如下：\n\n```javascript\n// 直接注册组件（不在需要组件构造器）\nVue.component('component1', {\n\n});\n// 局部注册同理\nvar Parent = Vue.extend({\n  template: '',\n  components: {\n    'component-1': {\n\n    }\n  }\n})\n```\n\n该语法实际上是 ``Vue`` 在背后自动调用 ``Vue.extend()``。\n\n除此之外，如果使用 ``.vue`` 格式的组件，我们定义组件的方式如下：\n\n```javascript\n<script>\n  // 定义一个组件\n  export default {\n\n  };\n</script>\n```\n\n## 组件选项\n\n所谓组件选项，就是在定义组件时，传递给 ``Vue.extend()`` 的参数。\n\n定义组件是相当简单的事情，我们更多的需要去关注组件选项！\n\n先来一个完整的组件选项压压惊：\n\n```javascript\nvar Component1 = Vue.extend({\n  data: {} or fn // 数据对象\n  props: [] or {} // 组件可配置的属性\n  propsData: {} // 在创建实例时，给属性赋值（用于测试）\n  computed: {} // 实例计算属性\n  methods: {} // 实例方法\n  watch: {} // 监控属性\n\n  el: string or HtmlElement or fn //挂载元素，将组件实例挂载到那儿\n  template: string // 组件模板\n  replace: boolean // 是否替换挂载元素，和template一起使用，默认true\n\n  init: fn // 生命周期钩子，初始化时调用，此时数据观察、事件和watcher都没初始化\n  created: fn // 组件参数已经解析完毕，但还未开始DOM编译\n  beforeCompile: fn //开始编译DOM\n  compiled: fn // DOM编译完成，数据辩护已经可以触发DOM更新了，但不保证$el已经插入文档\n  ready: fn // 编译结束和 $el 第一次插入文档之后调用\n  attached: fn // 在$el插入DOM时调用 \n  detached: fn // 在$el从DOM元素中删除时调用\n  beforeDestroy: fn // 在开始销毁实例时调用\n  destroyed: fn // 实例销毁后调用，如果有离开过渡，将会在过渡完成之后调用\n\n  directives: {} // 组件局部注册的指令\n  elementDirectives: {} // 组件局部注册的元素指令\n  filters: {} // 组件局部注册的过滤器\n  components: {} // 组件局部注册的子组件\n  transitions: {} // 组件局部注册的动画\n  partials: {} // 组件局部注册的分部元素\n\n  parent: Vue实例 // 指定实例的父实例\n  events: {} // 事件监听列表对象\n  mixins: [] // 组件的混合器\n  name: string //组件的名称，允许在自己的模板中调用自己（递归组件非常有效）\n  extends: {} or fn // 声明式的扩展组件\n});\n```\n\n虽然全量的属性比较多，但实际上常用的并不很太多，下面我列一下我比较常用的属性：\n\n```javascript\nvar Component1 = Vue.extend({\n  name: 'component-1',\n  data: {\n    firstName: 'Hu',\n    lastName: 'Jay'\n  },\n  props: ['height', 'width'],\n  template: '<h1>{{fullName}}</h1>',\n  replace: true,\n  \n  filters: {},\n  components: {},\n  transitions: {},\n\n  methods: {\n    doSomething: function(){\n      alert(this.fullName);\n    }\n  },\n  events: {\n    'do': 'doSomething', //方法名字\n    'do1': function(){\n      //当捕获 do1 事件时执行\n    }\n  },\n  watch:{},\n  computed: {\n    fullName: function(){\n      return this.firstName + this.lastName;\n    }\n  }\n\n  created: funciton(){\n    // 初始化操作\n  },\n  ready: funciton(){\n    // 如果有DOM操作，那么放在这里\n  },\n  beforeDestroy: funciton(){\n    // 如果某些东西需要手动释放，那么放在这里\n  }\n});\n```\n\n如果是在 ``.vue`` 格式中，我们的用法稍微有点变化，主要是使用ES6的新特性。\n\n```javascript\n<style>\n  <!-- 放置样式 -->\n</style>\n<template>\n  <!-- 此处放置模板，建议都用一个根元素包裹起来 -->\n  <div class=\"app-component-1\">\n    <h1>我是模板内容</h1>\n  </div>\n</template>\n<script>\n  //放置组件代码\n  export default {\n    props: [],\n    data() { \n      return {};\n    },\n    created() {\n\n    },\n    methods: {\n      fun1() {\n\n      },\n      fun2() {\n\n      }\n    },\n    events: {\n      'do': function(){\n\n      }\n    },\n    watch: {\n      firstName(newVal, oldVal){\n\n      }\n    }\n  };\n</script>\n```\n\n**注意：为什么data在.vue中data是一个方法呢？因为.vue下是使用的ES6的特性，如果是属性的话，就是原型属性，当多个组件时，就会共享同一份原型，导致数据错乱，所以通过function返回一个对象，保证每个组件实例隔离。**\n"
  },
  {
    "path": "catalog_builder.js",
    "content": "const fs = require('fs');\nconst path = require('path');\n\nconst replaceRegExp = /<!--TableOfContnets Start-->(\\r\\n|\\n|.|\\.)*<!--TableOfContnets End-->/;\n\nconst getSpaces = level => {\n  let result = '';\n  for (let i = 0; i < level; i++) {\n    result += '    ';\n  }\n  return result;\n};\n\nconst getSafePath = p => {\n  let v = p.replace(/\\\\/g, '/');\n  return encodeURI(v);\n};\n\nlet tableOfContnets = '';\n\nconst processDir = (folder, level = 0) => {\n  let dirs = fs.readdirSync(folder);\n  dirs.forEach(folderName => {\n    if (folderName.startsWith('.')) {\n      return;\n    }\n    let folderPath = path.join(folder, folderName);\n    if (fs.statSync(folderPath).isDirectory()) {\n      tableOfContnets += `${getSpaces(level)}* [${folderName}](${getSafePath(folderPath)})\\n`;\n      processDir(folderPath, level + 1);\n    } else if (path.extname(folderPath) === '.md') {\n      tableOfContnets += `${getSpaces(level)}* [${folderName}](${getSafePath(folderPath)})\\n`;\n    }\n  });\n};\n\nprocessDir('.');\n\nlet readmeContent = fs.readFileSync('README.md', 'utf8');\n\nlet newReadmeContent = readmeContent.replace(replaceRegExp, `<!--TableOfContnets Start-->\\n${tableOfContnets}<!--TableOfContnets End-->\\n\\n`)\nfs.writeFileSync('README.md', newReadmeContent, 'utf8');\n\nconsole.log('更新目录成功！');"
  },
  {
    "path": "jQuery拆解/01-目录篇.md",
    "content": "---\ntitle: 01-目录篇\ndate: 2017/02/21 14:47:10\n---\n\n## 0、导言\n\n``jQuery`` 作为最流行的DOM操作库，已经基本上成了浏览器的事实标准。抱着学习高效dom操作的目的，我就来整个系列，来拆解下 ``jQuery``。\n\n在这里，我选择的当前最新版本的 ``jQuery(3.1.1)`` 来分析。\n\n## 1、jQuery功能块\n\n为了简化分析过程，我将 ``jQuery`` 分为如下几个部分，然后针对每个部分进行单独分析。\n\n* 模块化加载&防冲突处理\n* 基础结构"
  },
  {
    "path": "jQuery拆解/02-模块化加载&防冲突处理.md",
    "content": "---\ntitle: 02-模块化加载&防冲突处理\ndate: 2017/02/21 14:47:10\n---\n\n## 0、模块化加载\n\n``jQuery`` 在之前的版本（具体不记得是哪个版本以前了）中，是不支持模块化加载的。\n\n当前，模块化作为一个趋势，``jQuery`` 也增加了对模块化的支持，当今最流行的的模块规范是 ``UMD``，简单点说就是判断各种环境，然后选择合适的方式加载。\n\n来看下具体的实现：\n\n```javascript\n//首先是一个IIFE，避免污染全局变量\n( function( global, factory ) {\n  \"use strict\";\n  // 判断是否是模块化加载\n  // 如果是CMD加载器，那么会有 module 和 module.exports 属性\n  if ( typeof module === \"object\" && typeof module.exports === \"object\" ) {\n    // 如果有document，那就算是浏览器环境，那么直接导出jQuery\n    module.exports = global.document ?\n    // 注意， factory第二个参数是true，也就是说不会挂载到全局变量上（模块化加载不需要挂载到window上）\n      factory( global, true ) :\n      // 虽然看起来是模块化加载，但是没有找到window对象，这个时候就导出一个factory,需要手动传入window对象。\n      function( w ) {\n        if ( !w.document ) {\n          throw new Error( \"jQuery requires a window with a document\" );\n        }\n        return factory( w );\n      };\n  } else {\n    // 如果不是加载器加载，那么就直接把window或者this传递给 jQuery factory\n    factory( global );\n  }\n\n// Pass this if window is not defined yet\n} )( typeof window !== \"undefined\" ? window : this, function( window, noGlobal ) {\n  // jQuery本身的逻辑代码\n\n  // 注意，jQuery也支持了AMD方式加载。\n  if ( typeof define === \"function\" && define.amd ) {\n    define( \"jquery\", [], function() {\n      return jQuery;\n    } );\n  }\n});\n```\n\n通过以上代码实现，``jQuery`` 就实现了模块化加载。\n\n## 1、防冲突处理\n\n当一个库可能会挂载属性到全局变量的时候，那么就要考虑全局变量覆盖这个问题。\n\n比如说 ``jQuery`` 会挂载到 ``window.jQuery`` 和 ``window.$`` 上，那万一这两个属性已经被其他库使用了，那我们引入 ``jQuery`` 不就把别人的库覆盖了么。\n\n作为一个完备的库，``jQuery`` 也处理了这种场景。那就是防冲突方法。\n\n看一下具体的实现：\n\n```javascript\n// 在实现防冲突时，要注意代码的顺序\n\n// 首先先用局部变量，把要挂载的属性备份一次。\nvar\n// Map over jQuery in case of overwrite\n  _jQuery = window.jQuery,\n\n  // Map over the $ in case of overwrite\n  _$ = window.$;\n\n// 实现一个冲突方法，当调用该方法时，就会把之前保存的全局变量进行还原。\n// deep的意思就是是否要还原jQuery这个属性，因为jQuery并不是一个常用的属性，所以一般不会被其他库占用。\njQuery.noConflict = function(deep) {\n  if (window.$ === jQuery) {\n    window.$ = _$;\n  }\n\n  if (deep && window.jQuery === jQuery) {\n    window.jQuery = _jQuery;\n  }\n  // 冲突之后，总要挂载吧，通过return jQuery，就能返回jQuery，这个时候可以任意挂载到你想要的属性。\n\t// 实例代码： window.abcde = jQuery.noConflict();\n  return jQuery;\n};\n\n// Expose jQuery and $ identifiers, even in AMD\n// (#7102#comment:10, https://github.com/jquery/jquery/pull/557)\n// and CommonJS for browser emulators (#13566)\n// 如果不是非全局（如果是双重否定，实际就是如果是全局）\nif (!noGlobal) {\n\t// 那么就设置到全局变量，这个时候已经把全局属性覆盖了。\n  window.jQuery = window.$ = jQuery;\n}\n\nreturn jQuery;\n```"
  },
  {
    "path": "jQuery拆解/03-基础结构.md",
    "content": "---\ntitle: 03-基础结构\ndate: 2017/02/21 14:47:10\n---\n\n## 0、jQuery() 与 new jQuery();\n\n在 ``jQuery`` 中,我们一般都是用 ``$('body')``，实际上 ``new $('body')`` 也是可以用的。这是如何实现的呢？\n\n要能使用 ``new``，那 ``jQuery`` 一定是一个 ``function``，又要让直接调用也返回一个实例，那我们就可以考虑返回另外一个全新 ``function`` 的实例。\n\n```javascript\nvar jQuery = function (selector, context) {\n  return new jQuery.fn.init(selector, context);\n};\n```\n\n通过以上这段代码，不管是用 ``new jQuery()`` 还是 ``jQuery()`` 返回的都是 ``init`` 方法的实例。\n\n在定义 ``jQuery`` 这个方法的时候，实际上 ``fn`` 和 ``init`` 都还不存在。\n\n## 1、jQuery.fn = jQuery.prototype\n\n```javascript\njQuery.fn = jQuery.prototype = {\n  // The current version of jQuery being used\n  jquery: version,\n  constructor: jQuery,\n  // The default length of a jQuery object is 0\n  length: 0,\n  toArray: function () {\n    return slice.call(this);\n  },\n  // Get the Nth element in the matched element set OR\n  // Get the whole matched element set as a clean array\n  get: function (num) {\n    // Return all the elements in a clean array\n    if (num == null) {\n      return slice.call(this);\n    }\n    // Return just the one element from the set\n    return num < 0 ? this[num + this.length] : this[num];\n  },\n  // Take an array of elements and push it onto the stack\n  // (returning the new matched element set)\n  pushStack: function (elems) {\n    // Build a new jQuery matched element set\n    var ret = jQuery.merge(this.constructor(), elems);\n    // Add the old object onto the stack (as a reference)\n    ret.prevObject = this;\n    // Return the newly-formed element set\n    return ret;\n  },\n  // Execute a callback for every element in the matched set.\n  each: function (callback) {\n    return jQuery.each(this, callback);\n  },\n  map: function (callback) {\n    return this.pushStack(jQuery.map(this, function (elem, i) {\n      return callback.call(elem, i, elem);\n    }));\n  },\n  slice: function () {\n    return this.pushStack(slice.apply(this, arguments));\n  },\n  first: function () {\n    return this.eq(0);\n  },\n  last: function () {\n    return this.eq(-1);\n  },\n  eq: function (i) {\n    var len = this.length,\n      j = +i + (i < 0 ? len : 0);\n    return this.pushStack(j >= 0 && j < len ? [this[j]] : []);\n  },\n  end: function () {\n    return this.prevObject || this.constructor();\n  },\n  // For internal use only.\n  // Behaves like an Array's method, not like a jQuery method.\n  push: push, // push = [].push\n  sort: arr.sort, // arr = []\n  splice: arr.splice // arr = []\n};\n```\n\n通过以上方法指定了 ``jQuery`` 的原型对象，也可以看到它默认的原型方法。\n\n## 2、jQuery.fn.init.prototype?\n\n看到了 ``jQuery`` 的构造和 ``jQuery.prototype`` 的申明，可能会有这样一个疑惑，在 ``jQuery()`` 中返回的明明是 ``jQuery.fn.init`` 的实例，为什么可以使用 ``jQuery.protptype`` 呢？\n\n我们先看看 ``init`` 函数的实现：\n\n```javascript\nvar init = jQuery.fn.init = function (selector, context, root) {\n  var match, elem;\n  // HANDLE: $(\"\"), $(null), $(undefined), $(false)\n  // 没有给选择器还玩个毛啊\n  if (!selector) {\n    return this;\n  }\n  // Method init() accepts an alternate rootjQuery\n  // so migrate can support jQuery.sub (gh-2101)\n  root = root || rootjQuery;\n\n  // Handle HTML strings\n  if (typeof selector === \"string\") {\n    if (selector[0] === \"<\" &&\n      selector[selector.length - 1] === \">\" &&\n      selector.length >= 3) {\n\n      // Assume that strings that start and end with <> are HTML and skip the regex check\n      match = [null, selector, null];\n\n    } else {\n      match = rquickExpr.exec(selector);\n    }\n\n    // Match html or make sure no context is specified for #id\n    if (match && (match[1] || !context)) {\n\n      // HANDLE: $(html) -> $(array)\n      if (match[1]) {\n        context = context instanceof jQuery ? context[0] : context;\n\n        // Option to run scripts is true for back-compat\n        // Intentionally let the error be thrown if parseHTML is not present\n        jQuery.merge(this, jQuery.parseHTML(\n          match[1],\n          context && context.nodeType ? context.ownerDocument || context : document,\n          true\n        ));\n\n        // HANDLE: $(html, props)\n        if (rsingleTag.test(match[1]) && jQuery.isPlainObject(context)) {\n          for (match in context) {\n\n            // Properties of context are called as methods if possible\n            if (jQuery.isFunction(this[match])) {\n              this[match](context[match]);\n\n              // ...and otherwise set as attributes\n            } else {\n              this.attr(match, context[match]);\n            }\n          }\n        }\n\n        return this;\n\n        // HANDLE: $(#id)\n      } else {\n        elem = document.getElementById(match[2]);\n\n        if (elem) {\n\n          // Inject the element directly into the jQuery object\n          this[0] = elem;\n          this.length = 1;\n        }\n        return this;\n      }\n\n      // HANDLE: $(expr, $(...))\n    } else if (!context || context.jquery) {\n      return (context || root).find(selector);\n\n      // HANDLE: $(expr, context)\n      // (which is just equivalent to: $(context).find(expr)\n    } else {\n      return this.constructor(context).find(selector);\n    }\n\n    // HANDLE: $(DOMElement)\n  } else if (selector.nodeType) {\n    this[0] = selector;\n    this.length = 1;\n    return this;\n\n    // HANDLE: $(function)\n    // Shortcut for document ready\n  } else if (jQuery.isFunction(selector)) {\n    return root.ready !== undefined ?\n      root.ready(selector) :\n\n      // Execute immediately if ready is not present\n      selector(jQuery);\n  }\n\n  return jQuery.makeArray(selector, this);\n};\n```\n\n这个不多说，就是根据选择器查找元素。接下来，关键来了：\n\n```javascript\n// 把jQuery.fn设置为init的原型\ninit.prototype = jQuery.fn;\n\n// Initialize central reference\n// 从document开始查找\nrootjQuery = jQuery(document);\n```\n\n## 3、jQuery.extend = jQuery.fn.extend\n\n有了以上的基础，那么就该扩展 ``jQuery`` 的功能了。它的核心就是以下的扩展函数：\n\n```javascript\njQuery.extend = jQuery.fn.extend = function () {\n  var options, name, src, copy, copyIsArray, clone,\n    target = arguments[0] || {},\n    i = 1,\n    length = arguments.length,\n    deep = false;\n  // Handle a deep copy situation\n  if (typeof target === \"boolean\") {\n    deep = target;\n    // Skip the boolean and the target\n    target = arguments[i] || {};\n    i++;\n  }\n  // Handle case when target is a string or something (possible in deep copy)\n  if (typeof target !== \"object\" && !jQuery.isFunction(target)) {\n    target = {};\n  }\n  // Extend jQuery itself if only one argument is passed\n  if (i === length) {\n    target = this;\n    i--;\n  }\n  // 将属性扩展到指定的对象上。\n  for (; i < length; i++) {\n    // Only deal with non-null/undefined values\n    if ((options = arguments[i]) != null) {\n      // Extend the base object\n      for (name in options) {\n        src = target[name];\n        copy = options[name];\n        // Prevent never-ending loop\n        if (target === copy) {\n          continue;\n        }\n        // Recurse if we're merging plain objects or arrays\n        if (deep && copy && (jQuery.isPlainObject(copy) ||\n          (copyIsArray = jQuery.isArray(copy)))) {\n          if (copyIsArray) {\n            copyIsArray = false;\n            clone = src && jQuery.isArray(src) ? src : [];\n          } else {\n            clone = src && jQuery.isPlainObject(src) ? src : {};\n          }\n          // Never move original objects, clone them\n          target[name] = jQuery.extend(deep, clone, copy);\n          // Don't bring in undefined values\n        } else if (copy !== undefined) {\n          target[name] = copy;\n        }\n      }\n    }\n  }\n  // Return the modified object\n  return target;\n};\n```\n\n为什么同一个 ``extend`` 函数可以扩展 ``jQuery``，也可以扩展 ``jQuery.fn``？这就涉及到另外一个JS的知识点了。\n\n**函数中this的指向是谁调用谁就是this**。 \n\n## 4、小结\n\n以上就是 ``jQuery`` 的核心基础结构，如果我们自己要编写一个 ``Lib or Framework``，也可以参考这样的方式来实现。\n\n之后所有的功能，都是从这里进行扩展。 "
  },
  {
    "path": "jQuery拆解/jQuery中那些有趣的代码.md",
    "content": "---\ntitle: jQuery中那些有趣的代码\ndate: 2017/02/21 14:47:10\n---\n\n## 0、动态执行JS代码\n\n```javascript\nfunction DOMEval(code, doc) {\n\t\tdoc = doc || document;\n\t\tvar script = doc.createElement(\"script\");\n\t\tscript.text = code;\n\t\tdoc.head.appendChild(script).parentNode.removeChild(script);\n}\n```\n\n## 1、"
  },
  {
    "path": "从0开始Stylus/01_Stylus简介&基本使用.md",
    "content": "---\ntitle: 01_Stylus简介&基本使用\ndate: 2017/02/21 14:47:10\n---\n\n## 0、导言\n\n**关于Stylus**\n\nStylus是一个CSS预处理器，也就是利用编程的方式编写css代码，然后stylus会自动帮我们编译为标准的css，并能附加更多的功能。\n\nStylus开始于2010年，来自于Node.js社区。\n\nStylus的文件后缀是``.styl``\n\n**常用预处理器之间的简单比较**\n\n常用的CSS预处理还有Sass和LESS。Sass提供了非常多的特性，也非常成熟。Less使用起来更为简单。\nStylus在功能上更为健壮，和JS的联系更加紧密。\n\n**此文产生的缘由**\n\n1. 由于亲近Node.js，所以想系统的学习下和js更紧密的Stylus预处理器\n2. 最近在实现nk-style的时候，采用了Stylus来编写CSS，用文章的方式来加深理解\n\n## 1、配套工具\n\n**如何安装Stylus**\n\n既然是Node.js社区的产出，那么很明显，安装方式也带有浓浓的Node风格。使用``npm install stylus -g``就可以在系统中安装Stylus了，当然，前提是你得先安装node和npm。\n\n**如何使用**\n\n安装好Stylus之后，我们就可以在控制台输入特定命令，来转换Stylus文件。\n\n``stylus css`` --编译css目录的.styl文件，并输出同名的.css文件\n\n``stylus index.styl abc.styl`` --编译index.styl、abc.styl文件\n\n更多命令请参考 [http://stylus-lang.com/docs/executable.html](http://stylus-lang.com/docs/executable.html)\n\n**更常规的用法**\n\n一般使用stylus的话，是会结合构建工具来一起使用的。比如结合gulp来使用的方式如下：\n\n```javascript\nvar stylus = require('gulp-stylus');\n\ngulp.task('css', _ =>\n  gulp.src('./src/index.styl')\n    .pipe(stylus())\n    .pipe(gulp.dest('./dist/'))\n);\n```\n\n## 2、Stylus语法\n\nStylus在语法支持上是比较强大的。既支持标准CSS，也支持缩进格式，最厉害的还能在同一个styl文件中混用。\n\n```styl\nbody{\n  background: white;\n}\n\nbody{\n  background blue\n}\n\nbody\n  background green \n```\n\n编译生成的CSS如下：\n\n```\nbody {\n  background: #fff;\n}\nbody {\n  background: #00f;\n}\nbody {\n  background: #008000;\n}\n```\n\n### 2.1、注释\n\n在学习一门编程语言（或者类编程语言时），我都胡优先去了解它的注释用法。因为刚学，意味着陌生，意味着需要些一些备注，那么这个时候注释就是个好东西。\n\n在Stylus中，注释有三种方式：\n\n1. 单行注释 --仅在styl文件中可见\n2. 多行注释 --常规模式下，输出到css文件中\n3. 重点注释 --在compress模式下，也会输出到css文件中\n\ntest.styl文件内容\n\n```\n// 单行注释\nbody{\n  background: white;\n}\n\n/* 多行注释 */\nbody{\n  background blue\n}\n\n/*! 多行注释 */\nbody\n  background green \n```\n\n常规模式下输出\n\n```\nbody {\n  background: #fff;\n}\n/* 多行注释 */\nbody {\n  background: #00f;\n}\n/* 多行注释 */\nbody {\n  background: #008000;\n}\n\n```\n\n压缩模式下输出\n\n```\nbody{background:#fff}body{background:#00f}/* 多行注释 */\nbody{background:#008000}\n```\n\n### 2.2、变量\n\n变量在任何一个编程语言中，都是必不可少的。Stylus中也不例外。\n\nStylus的变量比较灵活，支持较多的变量名命名方式，如$abc、abc、_abc、-abc等等，但是从易读性上来说，\n建议大家使用abc或者是$abc（推荐）其一来作为变量名规则。\n\ntest.styl内容：\n\n```\n/*! 变量 */\n/* 常规的表达式做变量名，用等号连接变量值 */\n//在Stylus中，可以使用$，_等前缀，但建议使用特定字符开头，来标识变量\nfont-size = 14px;\n$font-size = 20px \n\n// 单行注释\nbody{\n  background: white;\n  font-size font-size\n  font-size: $font-size\n  \n  width w = 100px\n  height h = 100px\n  //注意，此处表达式的括号不能少\n  margin-left -(w/2)\n  margin-top -(h/2)\n  //使用@符号引用同级的属性\n  margin-left -(@width/2)\n  margin-top -(@height/2)\n} \n```\n\n生成的CSS内容如下：\n\n```\n/* 变量 */\n/* 常规的表达式做变量名，用等号连接变量值 */\nbody {\n  background: #fff;\n  font-size: 14px;\n  font-size: 20px;\n  width: 100px;\n  height: 100px;\n  margin-left: -50px;\n  margin-top: -50px;\n  margin-left: -50px;\n  margin-top: -50px;\n}\n```\n\n更多变量使用，请查阅[http://stylus-lang.com/docs/variables.html](http://stylus-lang.com/docs/variables.html)\n\n### 2.3、选择器\n\n在编写CSS的过程中，我们用得最多的无非就是选择器了。我们来看看Stylus对选择器做了哪些扩展。\n\n```\n/*! 选择器 */\n/* 1、选择器嵌套 */\nbody\n  background white\n  .container\n    background blue\n/* 2、引用直接父级别节点 */\nli\n  &:hover\n    color blue\n/* 3、引用指定级别上层节点,个人觉得稍显复杂，不易懂，尽量少使用 */\nbody\n  color white\n  li\n    a\n      color green\n    ^[1]:hover\n        color yellow\n/* 4、根节点引用，使用/将当前样式提升到第一层级 */\nbody\n  li\n    / .test\n      color white\n/* 5、使用../来回到上一层级 */\nbody\n  li\n    ../ .test\n      color yellow\n/* 6、使用selector()来构造选择器 */\n{selector('.a', '.b, .c')}\n  color white\n/* 7、选择器为变量，那么用{}包裹，属性也是 */\n$selector = \".text-danger\"\n$font = font-family\n\n{$selector}\n  {$font} \"微软雅黑\"\n\n/* 8、使用表达式 */\nexp_margin_pad(n)\n  margin (- n)px\n  \nbody\n  exp_margin_pad(10)\n```\n\n编译为CSS如下：\n\n```\n/* 选择器 */\n/* 1、选择器嵌套 */\nbody {\n  background: #fff;\n}\nbody .container {\n  background: #00f;\n}\n/* 2、引用直接父级别节点 */\nli:hover {\n  color: #00f;\n}\n/* 3、引用指定级别上层节点,个人觉得稍显复杂，不易懂，尽量少使用 */\nbody {\n  color: #fff;\n}\nbody li a {\n  color: #008000;\n}\nbody li:hover {\n  color: #ff0;\n}\n/* 4、根节点引用，使用/将当前样式提升到第一层级 */\n.test {\n  color: #fff;\n}\n/* 5、使用../来回到上一层级 */\nbody .test {\n  color: #ff0;\n}\n/* 6、使用selector()来构造选择器 */\n.a .b,\n.a .c {\n  color: #fff;\n}\n/* 7、选择器为变量，那么用{}包裹，属性也是 */\n.text-danger {\n  font-family: \"微软雅黑\";\n}\n/* 8、使用表达式 */\nbody {\n  margin: -10px;\n}\n```\n\n花样太多，就不一一例举了。不过，常用的没几个。个人最常用的仅仅是嵌套。\n\n想了解更多，请参考： [http://stylus-lang.com/docs/selectors.html](http://stylus-lang.com/docs/selectors.html)\n\n### 2.5、样式块\n\nStylus的变量没有集合的概念，那么如果有一组样式要复用的时候，变量就有点捉襟见肘了。这个时候，我们可以采用block来实现一组样式的复用。\n\n```\n/*! 样式块，两种定义方式，推荐第二种，为了易读性 */\nfont = \n  font-family \"微软雅黑\"\n  font-size 1rem\n\nfont1 = @block{\n  font-family \"宋体\"\n  font-size 14px;\n}\n  \nbody\n  {font}\n  {font1}\n```\n\n编译结果为：\n\n```\n/* 样式块，两种定义方式，推荐第二种，为了易读性 */\nbody {\n  font-family: \"微软雅黑\";\n  font-size: 1rem;\n  font-family: \"宋体\";\n  font-size: 14px;\n}\n```\n\n### 2.6 样式继承\n\n在编写CSS的过程中，我们往往会发现新加的样式和之前已有的样式类有重复的部分，如果是原生CSS，那么我们又得拷贝一份样式出来。在Stylus中，大可不必如此麻烦。使用@extend很方便的就能解决这个问题。\n\n```\n/*! 样式继承 */\n\n.btn\n  border 1px solid red;\n  border-radius 5px\n\n.btn-danger\n  @extend .btn //继承.btn的样式\n  color red\n\n//如果不嫌输出.btn，我们只需要将btn做成占位选择器，如下\n$btn\n  border 1px solid red;\n  border-radius 5px\n  \n.btn-info\n  @extend $btn\n  color purple\n```\n\n编译后为：\n\n```\n/* 样式继承 */\n.btn,\n.btn-danger {\n  border: 1px solid #f00;\n  border-radius: 5px;\n}\n.btn-danger {\n  color: #f00;\n}\n.btn-info {\n  border: 1px solid #f00;\n  border-radius: 5px;\n}\n.btn-info {\n  color: #800080;\n}\n```\n\n**注意1：@extend和@extends完全相等，两者可以混用**\n\n**注意2：@extend与Sass不同的地方，在于Stylus的@extend支持继承嵌套选择器**\n\n### 2.7、方法\n\n编程语言重要的一个特征就是函数，在Stylus中，也有函数的概念，函数的概念和Mixins比较类似，但是，函数还可以有返回值\n\n```\n/*! 函数 */\n// 个人建议在定义函数时，以f_为前缀，方便识别\nf_plus(a, b) //简单函数\n  a + b\n  \nf_plus2(a, b = a) //带默认值的函数\n  a + b + 0px\n\nf_multireturn() //多返回值函数\n  5px 10px 15px 20px\n  \nf_margin() //想要作为整体返回,为了消除歧义，建议使用rerurn和括号包裹返回值\n  return (5px 10px 5px 10px)\n\nf_test = f_margin //函数可以指定别名\n\n//和js雷同，函数可以作为参数传递\nf_fun1(a, b)\n  a + b + 0px\nf_fun2(a, b)\n  a - b + 0px\nf_invork(a, b, fn)\n  fn(a, b)  \n\nbody\n  margin-top f_plus(5, 10)\n  margin-top f_plus(5px, 10)\n  margin-top f_plus2(5)\n  margin-top f_plus2(b: 10, a: 5) //命名参数传递\n  margin-bottom f_multireturn()[3] //取第四个值，下标从0开始\n  margin f_margin()\n  width f_invork(100, 50, f_fun1)\n  height f_invork(100, 50, f_fun2)\n```\n\n编译之后为：\n\n```\n/* 函数 */\nbody {\n  margin-top: 15;\n  margin-top: 15px;\n  margin-top: 10px;\n  margin-top: 15px;\n  margin-bottom: 20px;\n  margin: 5px 10px 5px 10px;\n  width: 150px;\n  height: 50px;\n}\n\n```\n\n## 3、未完，待续"
  },
  {
    "path": "从零开始H5/HTML5探索一（那些新增的标签和属性）.md",
    "content": "---\ntitle: HTML5探索一（那些新增的标签和属性）\ndate: 2017/02/21 14:47:10\n---\n\nhtml5相比html4，添加了部分语义化的标签和属性，现在我们就从这些标签和属性开始，学习html5吧。\n\n首先，认识下HTML5新的文档类型：\n\n\t<!DOCTYPE html>\n\n## 那些新标签\n\n### 格式\n\n1. <bdi&gt; 定义文本的文本方向，使其脱离其周围文本的方向设置\n2. <mark&gt; 定义有记号的文本\n3. <meter&gt; 定义预定义范围内的度量\n4. <progress&gt; 定义任何类型的任务的进度\n5. <rp&gt; 定义若浏览器不支持ruby元素显示的内容\n6. <rt&gt; 定义ruby注释的解释\n7. <ruby&gt; 定义ruby注释\n8. <time&gt; 定义日期/时间\n9. <wbr&gt; 强制定义换行点\n\nHTML：\n\n\t<!DOCTYPE html>\n\t<html>\n\t  <head>\n\t    <meta charset=\"utf-8\" />\n\t    <title>HTML5 Test Page 1</title>\n\t  </head>\n\t  <body>\n\t    <div><bdi>BDI:在发布用户评论或其他您无法完全控制的内容时，该标签很有用 test</bdi></div>\n\t    <hr />\n\t    <div><mark>Mark:定义带有记号的文本</mark></div>\n\t    <hr />\n\t    <div style=\"width:200px;border:1px solid red;\"><meter value=\"10\" />Meter</div>\n\t    <hr />\n\t    <div><progress value=\"10\" max=\"100\"></progress>Progress: 用于显示进度，结合JS一同使用</div>\n\t    <hr />\n\t    <div>我在 <time datetime=\"2008-02-14\">情人节</time> 有个约会\n\t    <mark>该标签不会再在任何浏览器中呈现任何特殊效果，仅仅方便搜索引擎生成更智能的结果</mark>\n\t    </div>\n\t    <hr />\n\t    <div>\n\t      <p>如果想学习 AJAX，那么您必须熟悉 XML<wbr>Http<wbr>Request 对象。</p>\n\t      <mark>wbr可强制设置换行点</mark>\n\t    </div>\n\t  </body>\n\t</html>\n\n### 表单\n\n1. <datalist&gt; 定义下拉列表\n2. <keygen&gt; 定义生成密钥\n3. <output&gt; 定义输出的一些类型\n\nHTML：\n\n\t<!DOCTYPE html>\n\t<html>\n\t  <head>\n\t    <meta charset=\"utf-8\" />\n\t    <title>HTML5 Test Page 2</title>\n\t  </head>\n\t  <body>\n\t    <form method=\"post\">\n\t      <p>datalist:和input元素配合，用于定义input可能的值</p>\n\t      <input id=\"lang\" list=\"dl\" />\n\t      <datalist id=\"dl\">\n\t        <option value=\"C#\" />\n\t        <option value=\"Java\" />\n\t        <option value=\"PHP\" />\n\t      </datalist>\n\t      <hr />\n\t      <p>keygen:提交密钥串到服务器</p>\n\t      Username: <input type=\"text\" name=\"usr_name\" />\n\t      Encryption: <keygen name=\"security\" />\n\t      <input type=\"submit\" />\n\t      <hr />\n\t    </form>\n\t    <p>output:定义不同类型的输出</p>\n\t    <form oninput=\"x.value=parseInt(a.value)+parseInt(b.value)\">\n\t      0\n\t      <input type=\"range\" id=\"a\" value=\"50\">100\n\t      +<input type=\"number\" id=\"b\" value=\"50\">\n\t      =<output name=\"x\" for=\"a\"></output>\n\t    </form> \n\t  </body>\n\t</html>\n\n### 图像\n\n1. <canvas&gt; 定义图形\n2. <figcaption&gt; 定义figure元素的标题\n3. <figure&gt; 定义媒介内容的分组，以及它们的标题\n\nHTML：\n\n\t<!DOCTYPE html>\n\t<html>\n\t  <head>\n\t    <meta charset=\"utf-8\" />\n\t    <title>HTML5 Test Page 3</title>\n\t  </head>\n\t  <body>\n\t    <div>\n\t      <p>canvas:你懂的，画布，各种绚丽效果就靠它了。</p>\n\t      <canvas id=\"c\"></canvas>\n\t      <script>\n\t        var canvas=document.getElementById('c');\n\t        var ctx=canvas.getContext('2d');\n\t        ctx.fillStyle='#FF0000';\n\t        ctx.fillRect(0,0,80,100);\n\t      </script>\n\t    </div>\n\t    <hr />\n\t    <div>\n\t      <p>\n\t      figure: 规定独立的流内容（图像、图表、照片、代码等等），figure 元素的内容应该与主内容相关，但如果被删除，则不应对文档流产生影响。\n\t      <br />\n\t      figcaption:定义 figure 元素的标题，语义化</p>\n\t      <figure>\n\t        <figcaption>黄浦江上的的卢浦大桥</figcaption>\n\t        <img src=\"https://encrypted-tbn2.gstatic.com/images?q=tbn:ANd9GcSehM_6Bfd79RwCCy1wNj_K6YGEkMsdt0Gekn10Dc6xJ8nxDcS7rg\" width=\"350\" height=\"234\" />\n\t      </figure>\n\t    </div>   \n\t  </body>\n\t</html>\n\n### 音频/视频\n\n1. <audio&gt; 定义声音内容\n2. <source&gt; 定义媒介源\n3. <track&gt; 定义用在媒体播放器中的文本轨道\n4. <video&gt; 定义视频\n\n### 链接\n\n1. <nav&gt; 定义导航链接\n\n### 列表\n\n1. <command&gt; 定义命令按钮 --**注：现在浏览器暂时都不支持**\n\n### 样式/节 -- 语义化标签\n\n1. <header&gt; 定义section或page的页眉\n2. <footer&gt; 定义section或page的页脚\n3. <section&gt; 定义section\n4. <article&gt; 定义文章\n5. <aside&gt; 定义页面内容之外的内容\n6. <details&gt; 定义元素细节\n7. <dialog&gt; 定义对话框或窗口\n8. <summary&gt; 为<details&gt;元素定义可见的标题\n\n### 编程\n\n<embed&gt; 为外部应用程序（非HTML）定义容器\n\n## 那些新属性\n\n1. contenteditable 规定元素内容是否可编辑\n2. contextmenu 规定元素的上下文菜单。上下文菜单在用户点击元素时显示\n3. data-* 用于存储页面或应用程序的私有定制数据\n4. draggable 规定元素是否可拖动\n5. dropzone 规定在拖动被多动数据时是否进行复制、移动或链接\n6. hidden 规定元素仍未或不再相关\n7. spellcheck 规定是否对元素进行拼写和语法检查\n8. translate 规定是否应该翻译元素内容\n\n**以上全局属性可用于任何HTML元素**\n\n## 参考资料\n\n1. [http://www.w3schools.com/tags/](http://www.w3schools.com/tags/)\n\n"
  },
  {
    "path": "从零开始H5/从零开始H5（一）：升级你的HTML到HTML5.md",
    "content": "---\ntitle: 从零开始H5（一）：升级你的HTML到HTML5\ndate: 2017/02/21 14:47:11\n---\n\n## 现有的网页大部分还是基于HTML4开发的，那么如何简单的升级到HTML5呢？\n\n### 1、从doctype定义开始\n\nHTML4：\n\t\n\t<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\" \n\t\"http://www.w3.org/TR/html4/loose.dtd\">\n\nHTML5：\n\n\t<!doctype html>\n\n**注意：这不仅仅是HTML5的doctype，这也是HTML将来所有版本的doctype。不仅如此，它甚至在老版本的浏览器中也能正常工作。**\n\n### 2、简化meta\n\nHTML4：\n\n\t<meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\" />\n\nHTML5：\n\n\t<meta charset=\"utf-8\" />\n\n### 3、简化link标记\n\nHTML4：\n\n\t<link type=\"text/css\" rel=\"stylesheet\" href=\"index.css\" />\n\nHTML5：\n\n\t<link rel=\"stylesheet\" href=\"index.css\" />\n\n**注意：升级到HTML5后，使用link不在需要指定type了，因为已经宣布CSS作为HTML5的标准样式，这也是HTML5的默认样式。**\n\n\n### 4、简化script标记\n\nHTML4：\n\n\t<script type=\"text/javascript\" src=\"index.js\"></script>\n\nHTML5：\n\n\t<script src=\"index.js\"></script>\n\t<script>\n\t\tconsole.log('html5');\n\t</sscript>\n\n**注意：对于HTML5，JavaScript已经成为标准，所以不在需要指定type了，同时，对于script标签，结尾标记不能简写。**\n\n## Q/A\n\n### 1、为什么做了这些改变，在老式浏览器上也能用？\n\n拿link和script来说，浏览器早已假定默认使用CSS和JavaScript，所以和标准一致。包括doctype和meta也是一样。\n\n### 2、doctype以后也不会在变了，合适吗？\n\ndoctype的使用主要在于告诉浏览器采用它们的\"标准模式\"表现内容，所以不管以后HTML6或者其他，<!doctype html>已经足够表达意思了。\n\n### 3、XHTML怎么了？很多年前听说它是未来的发展方向？\n\n它夭折了。因为灵活性超过了严格语法。当然，如果你喜欢XML，那么还可以用严格模式编写HTML5。\n\n\n"
  },
  {
    "path": "从零开始H5/从零开始H5（二）：HTML5新技术点.md",
    "content": "---\ntitle: 从零开始H5（二）：HTML5新技术点\ndate: 2017/02/21 14:47:11\n---\n\n现在，将我们的页面升级到HTML5了。为什么要这样去做呢？通过简单的升级我们没看到任何大的变化。\n\n答案即将揭晓，**升级到HTML5之后，我们可以使用如下技术：**\n\n### 1、不再需要插件，就有对音频\\视频的内置支持，可以充分利用\n\nHTML5新增了&lt;audio>和&lt;video>标签，可以让我们操作音视频。\n\n### 2、更有描述性的标记\n\n针对Web语义化，HTML5新增了许多带有特定语义的标签，如header、footer、section、article、dialog、summary等等。\n\n### 3、富客户端支持，可以使用画布、转换和JavaScript创建炫酷的界面和动画\n\n使用画布（canvas）和JavaScript，我们可以直接在Web页面上画图像、线条等，同时对开发Web游戏也是更加友好的。\n\n### 4、新增客户端存储和缓存功能\n\n使用Application-Cache，我们可以让我们的网页离线浏览，同时可以采用localStorage和sessionStorage来存储数据。\n\n### 5、新增Web工作线程，让JavaScript更高效\n\n可以让耗时的JS，在后台工作，让UI的响应更快，提高用户体验。\n\n### 6、可以结合CSS3的新内容，如高级选择器、动画、阴影\n\n可以让元素运行动画，也可以让元素具有圆角边框，甚至阴影。也能通过简单的代码快速选择对象。\n\n### 7、新增位置API（移动设备）\n\n通过该API，页面能够知道访问者的地理位置，这在移动设备访问中，尤其重要。"
  },
  {
    "path": "前端相关/CORS详解.md",
    "content": "---\ntitle: CORS详解\ndate: 2017/02/21 14:47:11\n---\n\n## 0、关于CORS\n\n说到CORS，就不得不先了解跨站HTTP请求（Cross-site HTTP request）。\n\n跨域HTTP请求是指发起请求的资源所在域不同于该请求所指向资源所在的域的HTTP请求。\n\n正如大家所知，出于安全考虑，浏览器会限制脚本中发起的跨站请求。使用XMLHttpRequest发起HTTP请求必须遵守同源策略。 具体而言，Web 应用程序能且只能使用 XMLHttpRequest 对象向其加载的源域名发起 HTTP 请求，而不能向任何其它域名发起请求。\n\n由于Web应用技术越来越丰富，我们非常渴望在不丢失安全的前提下，能够实现跨站请求。特别是现在的Web程序结构，一般是HTML+REST API。在之前的实现中，我们一般采用jsonp来发起跨站请求，这其实是利用了html标签的特点。\n\nW3C的Web应用工作组推荐了一种新的机制，即跨域资源共享（Cross-Origin Resource Sharing），也就是当前我们提到的CORS。 \n\nCORS的核心，就是让服务器来确定是否允许跨域访问。\n\n## 1、典型场景\n\n### 1.1、简单请求\n\n什么是简单请求？全部满足以下条件的请求可以称之为简单请求：\n\n1. 只使用GET、HEAD或者POST请求方法。如果是POST，则数据类型（Content-Type）只能是``application/x-www-form-urlencodeed``、``multipart/form-data``、``text/plain``中的一种。\n2. 没有使用自定义的请求头（如x-token）\n\n按照这个规则，那我们的能实现跨域请求的情况如下：\n\nServer代码：\n\n```javascript\n'use strict';\n\nvar http = require('http');\nvar server = http.createServer((req, res) => {\n  //之后设置了Access-Control-Allow-Origin，才会允许跨域\n  res.setHeader('Access-Control-Allow-Origin', '*');\n  res.write('abc');\n  res.end();\n});\n\nserver.listen(10000, () => {\n  console.log('started.');\n});\n```\nClient代码：\n\n```javascript\nvar xhr = new XMLHttpRequest();\nxhr.onreadystatechange = function(){\n  if(xhr.readyState === XMLHttpRequest.DONE){\n    console.log('Result：', xhr.responseText);\n  }\n}\n\n//场景一：GET请求，不需要Header，允许跨域\nxhr.open('GET', 'http://localhost:10000/', true);\nxhr.send();\n\n//场景二： POST请求，需要设置为指定Header（不设置content-type也可），允许跨域\nxhr.open('POST', 'http://localhost:10000/', true);\n//此处value必须是text/plain或者application/x-www-form-urlencoded或者multipart/form-data。\n//此处也可以不设置\nxhr.setRequestHeader('Content-Type', 'text/plain');\nxhr.send();\n\n//场景三：DELETE请求（不允许跨域）\nxhr.open('DELETE', 'http://localhost:10000/', true);\nxhr.send();\n\n//场景四：POST请求，有自定义Header（不允许跨域）\nxhr.open('POST', 'http://localhost:10000/', true);\nxhr.setRequestHeader('x-token', 'a');\nxhr.send();\n```\n\n### 1.2、预请求\n\n不同于简单请求，预请求要求必须先发送一个OPTIONS请求给站点，来查明该站点是否允许跨域请求，这样做的原因是为了避免跨站请求可能对目的站点的数据造成的损坏。\n\n如果请求满足以下任一条件，则会产生预请求：\n\n1. 请求以GET、HEAD、POST之外的方法发起。或者，使用POST，但数据类型为``application/x-www-form-urlencoded``, ``multipart/form-data`` 或者 ``text/plain`` 以外的数据类型。（注：之前的版本只有text/plain可以不用发起预请求）。\n2. 使用了自定义请求头。\n\n按照如上规则，我们来列举几个应用场景：\n\nServer端代码：\n\n```javascript\n'use strict';\n\nvar http = require('http');\nvar server = http.createServer((req, res) => {\n  //之后设置了Access-Control-Allow-Origin，才会允许跨域\n  res.setHeader('Access-Control-Allow-Origin', '*');\n  res.setHeader('Access-Control-Allow-Methods', 'POST, DELETE, GET');\n  res.setHeader('Access-Control-Allow-Headers', 'x-token');\n  //设置预请求缓存1天，1天内再次请求，可以跳过预请求\n  //此功能需要客户端缓存支持，如果客户端禁用缓存，那么每次都会预请求\n  res.setHeader('Access-Control-Max-Age', 60 * 60 * 24); \n  res.write('abc');\n  res.end();\n});\n\nserver.listen(10000, () => {\n  console.log('started.');\n});\n```\n\nClient端代码：\n\n```javascript\nvar xhr = new XMLHttpRequest();\nxhr.onreadystatechange = function(){\n  if(xhr.readyState === XMLHttpRequest.DONE){\n    console.log('Result：', xhr.responseText);\n  }\n}\n\n//场景一：DELETE请求，发送OPTIONS，匹配，允许跨域\nxhr.open('DELETE', 'http://localhost:10000/', true);\nxhr.send();\n\n//场景二：PUT请求，发送OPTIONS，不匹配，不允许跨域\nxhr.open('PUT', 'http://localhost:10000/', true);\nxhr.send();\n\n//场景三：DELETE请求匹配，使用自定义Header不匹配，不允许跨域\nxhr.open('DELETE', 'http://localhost:10000/', true);\nxhr.setRequestHeader('x-token1', 'aa');\nxhr.send();\n\n//场景四：POST请求，匹配的自定义Header，允许跨域\nxhr.open('POST', 'http://localhost:10000/', true);\nxhr.setRequestHeader('x-token', 'a');\nxhr.send();\n```\n\n### 1.3、带凭证的请求\n\n一般来说，对于跨站请求，浏览器是不会发送凭证（HTTP Cookies和验证信息）的。如果要发送带凭证的信息，只需要给XMLHttpRequest设置一个特殊的属性``withCredentials = true``，通过这种方式，浏览器就允许发送凭证信息。\n\n带凭证的请求可能是简单请求，也可以是会有预请求。是否允许跨域，会先判断简单请求和预请求的规则，然后还会带上带凭证的请求自己的规则。\n\n在带凭证的请求中，后端的响应必须包含Header``Access-Control-Allow-Credentials=true``，同时Header ``Access-Control-Allow-Origin``，不能再使用*号这种匹配符。\n\n具体示例如下：\n\n服务端代码：\n\n```javascript \n'use strict';\n\nvar http = require('http');\nvar server = http.createServer((req, res) => {\n  //要处理带凭证的请求，此Header不能使用*。\n  res.setHeader('Access-Control-Allow-Origin', 'http://10.16.85.170:8000');\n  res.setHeader('Access-Control-Allow-Methods', 'POST, DELETE, GET');\n  res.setHeader('Access-Control-Allow-Headers', 'x-token');\n  res.setHeader('Access-Control-Max-Age', 60 * 60 * 24); \n  //只有设置了该Header，才允许带凭证的请求。\n  res.setHeader('Access-Control-Allow-Credentials', true);\n  res.write('abc');\n  res.end();\n});\n\nserver.listen(10000, () => {\n  console.log('started.');\n});\n```\n\n客户端代码：\n\n```javascript\nvar xhr = new XMLHttpRequest();\nxhr.onreadystatechange = function(){\n  if(xhr.readyState === XMLHttpRequest.DONE){\n    console.log('Result：', xhr.responseText);\n  }\n}\n//优先满足预请求，然后满足凭证请求，允许跨域。\nxhr.open('POST', 'http://localhost:10000/', true);\nxhr.withCredentials = true;\nxhr.setRequestHeader('x-token', 'a');\nxhr.send();\n```\n## 2、HTTP响应头\n\n### 2.1、 后端HTTP响应头\n\n此处列举后端有关CORS的响应头：\n\n1. Access-Control-Allow-Origin： <origin> | *  允许的域名，只能有一个值。比如“*”或“abc.com”，\"a.com,b.com\"这种不允许\n2. Access-Control-Expose-Headers: <headers> 允许的白名单Header，多个用逗号隔开\n3. Access-Control-Max-Age: <delta-seconds>  预请求缓存时间，单位秒，**禁用缓存**时无效哦！\n4. Access-Control-Allow-Credentials: true | false  是否允许带凭证的请求，如果为true，则Origin只能是具体的值\n5. Access-Control-Allow-Methods: <methods> 允许的请求类型，多个用逗号隔开\n6. Access-Control-Allow-Headers: <headers> 在实际请求中，允许的自定义header，多个用逗号隔开\n\n### 2.2、 浏览器发出跨域请求的响应头\n\n此处列举出浏览器在发送跨域请求时，会带上的响应头：\n\n1. Origin: <origin> 告诉服务器，请求来自哪里，仅仅是服务器名，不包含路径。\n2. Access-Control-Request-Method: <method> 预请求时，告诉服务器实际的请求方式\n3. Access-Control-Request-Headers: <headers> 预请求时，告诉服务器，实际请求所携带的自定义Header\n\n\n## 3、参考资料\n\n1. [MDN HTTP access control (CORS)](https://developer.mozilla.org/en-US/docs/Web/HTTP/Access_control_CORS#Preflighted_requests)\n2. [MDN HTTP访问控制(CORS)](https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Access_control_CORS)\n"
  },
  {
    "path": "前端相关/CSS布局（上）.md",
    "content": "---\ntitle: CSS布局（上）\ndate: 2017/02/21 14:47:11\n---\n\n## 1、CSS布局之display\n\n### 1.1、dispaly\n\ndispaly是CSS中最重要的用于控制布局的属性，每个元素都有一个默认的display，大多数元素的默认值通常是block（块级元素）或inline（行内元素）。\n\n\n另一个常用的display是none。一些特殊元素的默认值就是它，如script、link等。\n\n### 1.2 display:none 与 visibility:hidden\n\ndisplay设置为none，是不会保存元素本该显示的空间，但是visibility:hidden会保留。\n\n\t<div style=\"width: 100px; height: 100px; border: 1px solid red;float:left;\">\n\t  <span style=\"display:none;\">ABCD</span>EFG\n\t</div>\n\t<div style=\"width: 100px; height: 100px; border: 1px solid red;float:left;\">\n\t  <span style=\"visibility:hidden;\">ABCD</span>EFG\n\t</div>\n\n<div style=\"width: 100px; height: 100px; border: 1px solid red;float:left;\">\n  <span style=\"display:none;\">ABCD</span>EFG\n</div>\n<div style=\"width: 100px; height: 100px; border: 1px solid red;float:left\">\n  <span style=\"visibility:hidden;\">ABCD</span>EFG\n</div>\n<div style=\"clear:both;\" />\n\n### 1.3、更多的display值\n\n比较常用的有list-item,inline-block,table,table-cell,flex等。\n\n全部列表如下：\n\n\tnone inline block contents list-item inline-block inline-table \n\t\n\ttable table-cell table-column table-column-group table-footer-group table-header-group table-row table-row-group \n\t\n\tflex inline-flex grid inline-grid \n\t\n\truby ruby-base ruby-text ruby-base-container ruby-text-container\n\t\n\trun-in\n\t\n\t/* Global values */\n\tdisplay: inherit;\n\tdisplay: initial;\n\tdisplay: unset;\n\n### 1.4 可改写的display属性\n\n虽然每个元素都有默认的display，但是我们可以随时随地的重写它，比如将li元素修改为inline-block，制作水平菜单。\n\n## 2、元素居中\n\n### 2.1、水平居中\n\n通过设置margin为auto可以实现水平居中，前提是元素必须得有宽度\n\n\t<div style=\"width:400px;margin:0 auto;height:10px;border:1px solid red;\"></div>\n\n<div style=\"width:400px;margin:0 auto;height:10px;border:1px solid red;\"></div>\n\n### 2.2、垂直居中\n\n因为table的cell可以设置垂直居中，所以玩么可以模拟这样的效果\n\n\t<div style=\"width: 400px;height: 200px;border: 1px solid red;display: table-cell; vertical-align: middle;\">\n\t\t<div style=\"width:100px; height:100px;background: blue;\"></div>\n\t</div>\n\n<div style=\"width: 200px;height: 200px;border: 1px solid red;display: table-cell; vertical-align: middle;\">\n\t<div style=\"width:100px; height:100px;background: blue;\"></div>\n</div>\n\n### 2.3、绝对居中\n\n知道水平居中和垂直居中，那么绝对居中就比较容易实现了。组合一下：\n\n\t<div style=\"width: 200px;height: 200px;border: 1px solid red;display: table-cell; vertical-align: middle;\">\n\t  <div style=\"width:100px; height:100px;background: blue;margin:0 auto;\"></div>\n\t</div>\n<div style=\"width: 200px;height: 200px;border: 1px solid red;display: table-cell; vertical-align: middle;\">\n  <div style=\"width:100px; height:100px;background: blue;margin:0 auto;\"></div>\n</div>\n\n还有没有更好的方式呢？如下：\n\n通过设置position:absolute,然后top、bottom、left、right值为0，margin:auto;实现绝对居中。\n如果要相对容器居中，设置容器的position为relative。\n\n\t<div style=\"width: 200px;height: 200px;border: 1px solid red; position:relative;\">\n\t  <div style=\"width:100px; height:100px;background: blue;margin:auto;position:absolute;top:0;left:0;bottom:0; right: 0;\"></div>\n\t</div>\n\n<div style=\"width: 200px;height: 200px;border: 1px solid red; position:relative;\">\n  <div style=\"width:100px; height:100px;background: blue;margin:auto;position:absolute;top:0;left:0;bottom:0; right: 0;\"></div>\n</div>\n\n\n## 3、盒子模型\n\n盒子模型(box-sizing)有两种典型值，分别为content-box,border-box。\n\n### 3.1、content-box\n\n此时，设置在元素上的宽度为内容宽度，那么元素所占用的宽度为：width + border \\* 2 + padding \\* 2 + margin * 2。宽度同理\n\n### 3.2、border-box\n\n此时，设置在元素上的宽度为包含border的宽度，那么占用总宽度为width + margin \\* 2。内容宽度为width - padding \\* 2 - border \\* 2。\n\n\n### 3.3 示例\n\n\t<div style=\"width:100px; margin: 10px; padding: 15px; border: 5px solid blue; box-sizing:content-box\"></div>\n\t<div style=\"width:100px; margin: 10px; padding: 15px; border: 5px solid blue; box-sizing:border-box\"></div>\n\n<div style=\"width:100px; margin: 10px; padding: 15px; border: 5px solid blue; box-sizing:content-box\"></div>\n<div style=\"width:100px; margin: 10px; padding: 15px; border: 5px solid blue; box-sizing:border-box\"></div>\n\n### 3.4、浏览器兼容性\n\n为了保证浏览器兼容性，需要加上特定浏览器前缀。\n\n\n## 4、元素定位\n\n如果要实现更多复杂的布局，那么就需要了解下position了。\n\n### 4.1、position:static\n\nstatic是position属性的默认值，position:static的元素不会被特殊定位。\n\n### 4.2、position:relative\n\n在相对定位(relative)的元素上设置top、right、bottom、left会使其偏离正常位置，其他元素不会调整位置来弥补它偏离后剩下的空隙。\n\n\t<div style=\"border:1px solid red; width: 400px; height: 200px;\">\n\t  <div style=\"background: blue; width:100px; height: 100px;\"></div>\n\t  ABCDE\n\t</div>\n\t<div style=\"border:1px solid red; width: 400px; height: 200px;\">\n\t  <div style=\"background: blue; width:100px; height: 100px;position:relative; left: 100px;top:50px;\"></div>\n\t  ABCDE\n\t</div>\n\n<div style=\"border:1px solid red; width: 400px; height: 200px;\">\n  <div style=\"background: blue; width:100px; height: 100px;\"></div>\n  ABCDE\n</div>\n<div style=\"border:1px solid red; width: 400px; height: 200px;\">\n  <div style=\"background: blue; width:100px; height: 100px;position:relative; left: 100px;top:50px;\"></div>\n  ABCDE\n</div>\n\n### 4.3、position:fixed\n\n固定定位（fixed）元素会相对于视窗来定位，所以就算页面滚动，它还是会留在相同位置。示例请看左下角。\n\n\t<div style=\"width: 100px;height:100px; position:fixed; bottom: 0; right: 0;\n\tborder: 1px solid red;\">固定定位</div>\n<div style=\"width: 100px;height:100px; position:fixed; bottom: 0; right: 0;\nborder: 1px solid red;\">固定定位</div>\n\n### 4.4、position:absolute\n\n绝对定位元素（absolute）与fixed类似，但是它不是相对视窗，而是相对最近的positioned（position值不是static的元素都是positioned元素）祖先元素，如果没有这样的祖先元素，那么它相对于文档的body元素，并且会随着页面滚动而滚动。\n\n\t<div style=\"border:1px solid red; width: 400px; height: 200px;position:relative;\">\n\t    <div style=\"background: blue; width:100px; height: 100px;position:absolute;top: 25px;right:25px;\"></div>\n\t</div>\n<div style=\"border:1px solid red; width: 400px; height: 200px;position:relative;\">\n    <div style=\"background: blue; width:100px; height: 100px;position:absolute;top: 25px;right:25px;\"></div>\n</div>\n"
  },
  {
    "path": "前端相关/CSS布局（下）.md",
    "content": "---\ntitle: CSS布局（下）\ndate: 2017/02/21 14:47:11\n---\n\n## 1、CSS布局之浮动\n\n### 1.1、float之图文混排\n\nfloat的意思就是元素漂浮在上层。\n\n可以直接通过设置float属性实现图文混排，代码如下：\n\n\t<div style=\"width:200px;height:200px;border: 1px solid gray;\">\n\t  <img src=\"\" alt=\"\" style=\"width:100px;height:100px;float:right;\"> Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus imperdiet, nulla et dictum interdum, nisi lorem egestas odio, vitae scelerisque enim ligula venenatis dolor.\n\t</div>\n\n<div style=\"width:200px;height:200px;border: 1px solid gray;\">\n  <img src=\"\" alt=\"\" style=\"width:100px;height:100px;float:right;\"> Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus imperdiet, nulla et dictum interdum, nisi lorem egestas odio, vitae scelerisque enim ligula venenatis dolor.\n</div>\n\n### 1.2、float的副作用\n\n当为元素设置float只有，它将会漂浮起来。那么它之后的元素就会忽略它，而进行定位。所以会导致元素重叠：\n\n\t<div style=\"width:50px;height:50px;border:1px solid red;float:left\">\n\t  我是悬浮元素\n\t</div>\n\t<div style=\"width:100px;height:100px;border:1px solid blue;\">\n\t  我是标准div元素内容\n\t</div>\n\n<div style=\"width:50px;height:50px;border:1px solid red;float:left\">\n  我是悬浮元素\n</div>\n<div style=\"width:100px;height:100px;border:1px solid blue;\">\n  我是标准div元素内容\n</div>\n\n这个时候，就需要清除浮动(clear:both)，另外，还可以通过clear:left|right来分别清除左右浮动：\n\t\n\t<div style=\"width:50px;height:50px;border:1px solid red;float:left\">\n\t  我是悬浮元素\n\t</div>\n\t<div style=\"width:100px;height:100px;border:1px solid blue;clear:both;\">\n\t  我是标准div元素内容\n\t</div>\n\n<div style=\"width:50px;height:50px;border:1px solid red;float:left\">\n  我是悬浮元素\n</div>\n<div style=\"width:100px;height:100px;border:1px solid blue;clear:both;\">\n  我是标准div元素内容\n</div>\n\n### 1.3、奇怪的浮动效果\n\n在内容的浮动元素高度大于外部容器时，效果如下：\n\n\t\n\t<div style=\"width:100px;border:1px solid blue;\">\n\t  <div style=\"width:50px;height:150px;border:1px solid red;float:right\">\n\t    我是悬浮元素\n\t  </div>\n\t  我是标准div元素内容\n\t</div>\n\n\n<div style=\"width:100px;border:1px solid blue;\">\n  <div style=\"width:50px;height:150px;border:1px solid red;float:right\">\n    我是悬浮元素\n  </div>\n  我是标准div元素内容\n</div>\n\n<div style=\"clear:both;\"></div>\n\t**如何修复？**\n\n可以通过clearfix样式来修复：\n\n\t<style>\n\t  .clearfix {\n\t    overflow: auto;\n\t    zoom: 1;/*针对IE需要额外关照*/\n\t  }\n\t</style>\n\t<div class=\"clearfix\" style=\"width:100px;border:1px solid blue;\">\n\t  <div style=\"width:50px;height:150px;border:1px solid red;float:right\">\n\t    我是悬浮元素\n\t  </div>\n\t  我是标准div元素内容\n\t</div>\n\n<style>\n  .clearfix {\n    overflow: auto;\n    zoom: 1;/*针对IE需要额外关照*/\n  }\n</style>\n<div class=\"clearfix\" style=\"width:100px;border:1px solid blue;\">\n  <div style=\"width:50px;height:150px;border:1px solid red;float:right\">\n    我是悬浮元素\n  </div>\n  我是标准div元素内容\n</div>\n\n## 2、CSS布局之百分比宽度\n\n百分比宽度可以非常容易的实现动态布局，但是当窗口变得很窄的时候，元素的展示可能会错乱。所以需要选择最合适的布局方式。\n\n另外，不能使用min-width来修复这个问题，因为如果是左右结构，对左边的元素设定min-width，右边的元素是不会遵守的，可能会引起重叠。\n\n## 3、CSS布局之媒体查询（Media Query）\n\n“响应式设计（Responsive Design）”是一种让网站针对不同的浏览器和设备“响应”不同显示效果的策略，这样可以让网站在任何情况下显示的很棒！\n\n如果要兼容移动端，请加上如下类似meta\n\n\t\n\t<meta name=\"viewport\" content=\"width=device-width, maximum-scale=2, minimum-scale=0.5, user-scalable=no\">\n\n因为众所周知，手机端的屏幕分辨率相当多，所以可以强制指定屏幕的width是等于设备宽度的，忽略分辨率。\nmaximum-scale 是指最大缩放比例。\n\n## 4、CSS布局之column\n\nCSS columns是较新的标准，不支持IE9及以下和Opera Mini，所以需要使用浏览器前缀。Column可以很轻松的实现文字的多列布局，示例如下：\n\n\t<div style=\"width:150px;-webkit-column-count:3;-webkit-column-gap: 1em;\">\n\t  CSS布局CSS布局CSS布局CSS布局CSS布局CSS布局CSS布局CSS布局CSS布局CSS布局\n\t</div>\n\n<div style=\"width:150px;-webkit-column-count:3;-webkit-column-gap: 1em;\">\n  CSS布局CSS布局CSS布局CSS布局CSS布局CSS布局CSS布局CSS布局CSS布局CSS布局\n</div>\n\n## 5、CSS布局之flexbox\n\nflexbox布局模式被用来重新定义CSS中的布局方式。\n\n\t<style>\n\t  .container{\n\t    width: 80%;\n\t    border: 1px solid red;\n\t    height:50px;\n\t    display: flex;\n\t  }\n\t</style>\n\t<div class=\"container\">\n\t  <div style=\"width:100px;\"></div>\n\t  <div style=\"flex:3;background:lightgray;\">1</div>\n\t  <div style=\"flex:5;background:lightyellow;\">2</div>\n\t</div>\n\n<style>\n  .container{\n    width: 80%;\n    border: 1px solid red;\n    height:50px;\n    display: flex;\n  }\n</style>\n<div class=\"container\">\n  <div style=\"width:100px;\"></div>\n  <div style=\"flex:3;background:lightgray;\">1</div>\n  <div style=\"flex:5;background:lightyellow;\">2</div>\n</div>\n\n分析一下以上代码，只需要在容器上设置display:flex，那么内部元素的如果设置了flex样式，那么就会按照flex进行计算，然后实现flex布局。\n\nflex布局，还能实现简单的垂直居中布局。\n\n\t<div class=\"container\" style=\"align-items: center; justify-content: center;\">\n\t  我是垂直居中的元素\n\t</div>\n\n<div class=\"container\" style=\"align-items: center; justify-content: center;\">\n  我是垂直居中的元素\n</div>\n\n其中，align-items设置水平居中，justify-content设置了垂直居中。"
  },
  {
    "path": "前端相关/Google JavaScript Style Guide（上）.md",
    "content": "---\ntitle: Google JavaScript Style Guide（上）\ndate: 2017/02/21 14:47:11\n---\n\n## 引言\n\nJavaScript是一套灵活的语言，所以我们可以用各种风格来编写代码。但为了更好的编写JavaScript代码，有些共同的规范是需要我们去遵循的。以下是Google提供的JavaScript代码风格指南，值得我们学习。\n可以到 [原文链接](http://google.github.io/styleguide/javascriptguide.xml) 查看原文英文版。\n\n## JavaScript 语言规则\n1、 ``var``\n\n总是使用 ``var`` 来定义变量，如果你没这样做，那么定义的变量很可能会污染全局变量，导致不必要的问题。\n\n**注：在ES6中，我们还可以使用let来定义变量，该方式定义的变量具有词法作用域，没有变量提升的风险。**\n\n2、Constants（常量）\n\n1. 使用NANES_LIKE _THIS这种全大写，下划线分割风格来定义常量\n2. 使用@const来注释你的常量\n3. 不要使用 ``const`` 关键字来定义常量，IE不支持\n\n**注：在支持ES6的环境下，建议直接通过 ``const`` 关键字来定义常量**\n\n3、Semicolons（分号）\n\n由于JavaScript的自动补全分号机制，可能会导致意料之外的结果，所以建议总是使用分号。\n\n错误实例：\n\n```javascript\n// Error 1\nvar fun1 = function(){\n  return 1;\n} //此处缺少分号，由于JavaScript的优先级，会导致优先和后面的括号结果，当成函数调用。\n(function(){\n  console.log('abc');\n})();\n\n// Error 2\nvar arr = [1, 2, 3]\n-1 == 0 || 1;\nconsole.log(arr); //输出1\n```\n**注：函数和分号，函数申明不需要分号，函数表达式需要使用分号**\n```javascript\nvar foo = function() {\n  return true;\n};  //函数表达式需要分号\n\nfunction foo2() {\n  return true;\n}  //函数申明不需要分号\n```\n4、嵌套函数\n\n嵌套函数在大多数时候是非常有用的，可以随意的使用给它们。\n```javascript\nfunction f() {\n  var x = 'local';\n  function g() {\n    console.log(x);\n  }\n  g(); //可以在函数内部访问到，但没有对外公开\n}\nf();\n```\n\n5、在块中定义函数\n\n不要在块中定义函数，JavaScript是函数作用域。\n\n**注：我们可以在块中使用函数表达式**\n\n```javascript \n//就算if为false，foo函数同样会被申明\nif(false){\n  function foo(){\n    console.log('foo');\n  }\n}\nfoo && foo(); \n\n//此处满足需求，if为false，则bar未定义\nif(false){\n  var bar = function(){\n    console.log('bar');\n  }\n}\nbar && bar();\n```\n\n6、异常\n\n异常基本上是无法避免的，可以考虑使用完善的开发框架。\n\n7、自定义异常\n\n建议使用自定义异常，如果没这样做，函数返回的异常信息可能是很不友好的。同时会暴露原始异常信息，所以建议使用自定义异常来屏蔽技术细节。\n\n8、标准功能\n\n标准功能总是由于非标准功能，为了最大的可移植性和兼容性，尽量使用标准功能。\n\n9、基本类型的包装对象\n\n没必要对基本类型进行包装，而且很容易产生错误。\n\n```javascript\nvar x = new Boolean(false);\nif (x) { //此处x为真，所以会执行到内部的代码\n  alert('hi');\n}\n```\n\n10、多级层次原型\n\n多级原型继承不是首选，建议使用 [Closure](https://github.com/google/closure-library/) 或者类似的库。\n\n11、方法和属性定义\n\n虽然有很多种方式可以创建函数，但是优先选择使用函数表达式，以属性或者变量的方式存在。如果是要定义类，那么还是采用方法申明的方式。\n\n**注：ES6可以使用 ``class`` 关键字定义类**\n\n```javascript\n//常规使用函数\nvar bar = function(){\n  \n};\n//类函数定义\nfunction Foo(){\n  \n}\n//使用class申明类\nclass Foo2{\n  constructor(){\n    \n  }\n}\n```\n11、delete（关键字）\n\n优先将无用的属性设置为null，而不是delete。因为在JavaScript引擎中，改变一个对象的属性数目要比重新设置属性值慢得多。\n\n12、闭包\n\n可以使用，但是要当心。闭包使用不当，容易导致内存泄露。实例如下：\n\n```javascript\n// 内存泄露，每次点击将使用到a和b，导致循环引用（ERROR）\nfunction foo(element, a, b) {\n  element.onclick = function() { \n    /* uses a and b */ \n  };\n}\n// 内存泄露修复\nfunction foo(element, a, b) {\n  element.onclick = bar(a, b);\n}\n//通过函数隔离，onclick事件不在对原始的a和b引用，所以避免的循环引用。\nfunction bar(a, b) {\n  return function() { \n    /* uses a and b */ \n  };\n}\n```\n\n13、eval()\n\n尽量避免使用eval，一个是性能低下，二个是很容易被攻击\n\n14、with(){}\n\n不要使用with，with会让你的代码难以理解，而且结果具有不确定性。\n\n```javascript\n//foo是否包含x属性，将会导致不同的结果\nvar foo = {x1: 1}; // {x: 1}\nvar x = 3;\nwith (foo) {\n  console.log(x);\n}\n```\n\n15、this\n\n建议仅在对象构造函数，方法和配置闭包中使用 ``this`` 。因为this在不同的地方，指向的对象不一样，这些很容易引发错误。\n\n16、for-in 循环\n\n建议在object/map/hash这种key-value结构中使用 for-in 循环，其他地方还是用for吧。另外for-in循环并不能保证key的顺序，所以在要顺序要求的地方也不要使用for-in。\n\n**注：ES5中新增了Object.keys可以用来替代for-in，在ES6中可以用for of遍历可迭代对象**\n\n17、关联数组\n\n不要使用数组作为 ``map/hash/关联数组`` 。关联数组不允许使用非数字索引。\n\n```javascript\n//关联数组\nvar arr = {};\n//添加元素\narr['one'] = 'abc';\narr['two'] = 'def';\n//删除元素\ndelete arr['one'];\n//访问元素\nconsole.log(arr['two']);\n```\n\n18、多行字符（行尾\\反斜杠）\n\n应避免使用多行字符，在每行开始处的空白无法被编译，反斜杠也可能会导致错误的空白，另外，它不是ECMAScript的一部分。我们可以使用字符串连接来替代\n\n```javascript\nvar str1 = 'AA \\\n            BB';\nvar str2 = 'AA ' + \n           'BB';\n//ES6用法\nvar str3 = `AA\nBB`;\n\nconsole.log(str1);\nconsole.log(str2);\nconsole.log(str3);\n```\n\n19、数组和对象字面量\n\n总是使用数组和对象字面量。数组构造是很容易出错的，new Array('a')表示一个长度的数组，new Array(5)表示5个长度的数组。具体如何返回结果，取决于第一个参数的类型。\n\n20、不要修改内置对象的原型\n\n对于Object和Array原型的修改是严格禁止的。修改其他内置对象的原型虽然不那么危险，但仍然会导致难以调试，应当避免。\n\n21、IE条件注释\n\n不要使用IE条件注释。会阻碍自动化工具处理js代码，也有可能会在运行时改变语法树。\n\n```javascript\nvar f = function () {\n    /*@cc_on if (@_jscript) { return 2* @*/  3; /*@ } @*/\n};\nconsole.log(f());\n```\n\n## 未完，待续"
  },
  {
    "path": "前端相关/Iframe跨域通信的几种方式.md",
    "content": "---\ntitle: Iframe跨域通信的几种方式\ndate: 2017/02/21 14:47:11\n---\n\n## 0、前言\n\n虽然iframe已经越来越不流行了，但是在某些特定的场景下，使用它可以大大减小我们的工作量。\n\n当在页面内使用iframe，甚至是嵌套iframe的时候，它们之间少不了要通信。如果是同域的情况，那我们可以直接进行通信。\n\n然而，很多场景下，往往是跨域通信，那这个时候我们可以用怎样的方式来跨域通信呢？\n\n请看此文分解。\n\n\n## 1、重现跨域\n\n我们新建两个 ``index.html``，分别部署两个不同的端口上。\n\n```html\n// localhost:8001/index.html\n...\n<body>\n  <h1>Page1</h1>\n  <iframe src=\"http://localhost:8003\" frameborder=\"5\" \n  style=\"width: 100%;height: 300px\"></iframe>\n  <script>\n    window.getPage1Title = function(){\n      return 'abc';\n    };\n  </script>\n</body>\n...\n```\n\n```html\n// localhost:8003/index.html\n<body>\n  <h1>Page2</h1>\n  <button onclick=\"fun1();\">Test</button>\n  <script>\n    function fun1(){\n      var parentTitle = window.parent.getPage1Title();\n      alert(parentTitle);\n    }\n  </script>\n</body>\n```\n\n从浏览器打开 ``http://localhost:8001``，然后点击iframe中的按钮，会出现一个如下提示：\n\n```\n(index):15 Uncaught SecurityError: Blocked a frame with origin \"http://localhost:8003\" from accessing a frame with origin \"http://10.16.85.170:8001\". Protocols, domains, and ports must match.\n```\n简而言之，就是不允许跨域访问。\n\n## 2、方式1 - 通过修改domain来实现跨域访问\n\n该方式适合主域相同，而子域不同的场景。此时可以在多个iframe中，通过修改document.domain = '主域' 的方式来实现跨域。\n\n**特定场合适用，不推荐**\n\n## 3、方式2 - 通过 ``window.name`` 跨域访问\n\n该方式原理是通过先请求其他的域的页面，把要传输的值赋值给 ``window.name`` 属性，然后把该iframe的src地址，修改为不跨域的一个页面。此时，由于是同一个iframe，所以name还是之前的数据，通过这样的方式变相的来获取其他域的数据。\n\n示例如下：\n\n在子页面中，仅仅需要把数据赋值给 ``window.name``\n```html\n// localhost:8003/index.html\n...\n<body>\n  <h1>Page2</h1>\n  <script>\n    window.name = '我是page2的数据';\n  </script>\n</body>\n...\n```\n\n父页面中，需要修改iframe为不跨域，然后获取数据\n\n```html\n// localhost:8001/index.html\n...\n<body>\n  <h1>Page1</h1>\n  <iframe id=\"f1\" frameborder=\"5\" style=\"width: 100%;height: 300px\"></iframe>\n  <script>\n    var iframe = document.getElementById('f1');\n    var isCrossFrameUrl = true;\n    iframe.onload = function(){\n      if(isCrossFrameUrl){\n        iframe.src = 'about:block;';\n        isCrossFrameUrl = false;\n        return;\n      }\n      console.log(iframe.contentWindow.name);\n    };   \n    iframe.src = 'http://localhost:8003';\n  </script>\n</body>\n...\n```\n\n这种方式实现起来，比较别扭，另外只能获取单次数据，并不友好，**不推荐使用**。\n\n## 4、方式3 - 通过 ``navigator`` 对象来跨域(已过期，不能使用了)\n\n该方式利用多个iframe窗口，访问的 ``navigator`` 对象都是同一个，而且并没有跨域问题这个原理；通过在该对象上注册和发送事件的方式来跨域访问。\n\n## 5、通过 ``window.postMessage`` 传递消息\n\n这是IE8+之后正统的iframe跨域解决方案，全称“跨文档消息”，是一个HTML5的新特性。\n\n```javascript\notherWindow.postMessage(message, targetOrigin, [transfer]);\n```\n\n其中第一个参数是消息对象，允许JS数据类型，第二个是要发送到的域，可以设置为 ``*`` 表示不限制。\n\n使用如下：\n\n```html\n// localhost:8001/index.html\n...\n<body>\n  <h1>Page1</h1>\n  <iframe id=\"f1\" src=\"http://localhost:8003/\" frameborder=\"5\" style=\"width: 100%;height: 300px\"></iframe>\n  <script>\n    window.addEventListener('message', function(evt){\n      console.log(evt);\n    });\n  </script>\n</body>\n...\n```\n\n```html\n// localhost:8003/index.html\n...\n<body>\n  <h1>Page2</h1>\n  <script>\n    parent.postMessage('test', '*');\n  </script>\n</body>\n...\n```\n\n打开 ``http://localhost:8001`` 就可以看到父页面已经收到子页面发过来的消息了。\n\nevt对象有几个重要的属性需要我们去了解一下：\n\n1. data // 具体发送的数据\n2. origin // 发送者origin（http://localhost:8003）\n3. source // 发送者（window对象）\n\n**该方式是当前最合适的跨文档通信方式，如果没有兼容IE6、7的需求，建议全部使用该方式。**"
  },
  {
    "path": "前端相关/JSONP详解.md",
    "content": "---\ntitle: JSONP详解\ndate: 2017/02/21 14:47:11\n---\n\n## 0、关于JSONP\n\n### 什么的JSONP\nJSONP（JSON with Padding）是资料格式 JSON 的一种“使用模式”，可以让网页从别的网域要资料。另一个解决这个问题的新方法是跨来源资源共享。（参考：[https://zh.wikipedia.org/wiki/JSONP](https://zh.wikipedia.org/wiki/JSONP)）\n\n### JSONP的起源\n\n1. 曾经的Ajax不能跨域请求（现在的也不能，不过有cors）\n2. Web上使用script调用js文件不存在跨域问题（实际上，只要拥有src属性的标签都允许跨域，比如script,img,iframe）\n3. 那个时候，想要通过web端跨域访问数据，只可以在服务器端设法把数据装进js，然后客户端调用\n4. 刚好这个时候JSON大行其道\n5. 所以，解决方案就出来，web端像调用脚本一样来跨域请求服务器上动态生成的js文件\n6. 为了便于客户端使用数据，逐渐形成了一种非正式传输协议，人们把它称作JSONP。\n\n### JSONP用来做什么\n\n通过JSONP的起源，我们大概也知道了JSONP就是为了跨域资源访问的。\n\n## 1、JSONP实现原理\n\n我们知道，在script标签中请求的js代码，到客户端之后，是能被自动执行的。\n\n我们先构造一个后端（采用node实现）：\n\n\tvar http = require('http');\n\t\n\tvar server = http.createServer((req, res) => {\n\t  var sendObj = {\n\t    url: req.url,\n\t    name: 'test'\n\t  };\n\t  res.write(`callback(${JSON.stringify(sendObj)})`);\n\t  res.end();\n\t});\n\t\n\tserver.listen(9999, () => {\n\t  console.log('started.')\n\t});\n\n我们要使用这个这个数据呢？可以用Ajax，可能会产生跨域问题\n\n另外，可以用如下写法：\n\n\t<!DOCTYPE html>\n\t<html lang=\"en\">\n\t<head>\n\t  <meta charset=\"UTF-8\">\n\t  <title>JSONP TEST</title>\n\t</head>\n\t<body>\n\t  <script>\n\t  function callback(obj){\n\t    console.log(obj);\n\t  }\n\t  </script>\n\t  <script src=\"http://localhost:9999/abc\"></script>\n\t</body>\n\t</html>\n\n打开这个页面后，我们会看到控制台会输出一个对象``Object {url: \"/abc\", name: \"test\"}``,\n也就是后端返回的对象。\n\n**当使用script请求地址时，会将返回的字符串，默认当成js解析。由于后端返回是的callback(xxx),所以会调用本地的callback函数。**\n\n**从原理上来看，要使用JSONP，必须要后端返回相应的数据，这个就是JSONP的模式了，允许客户端传递一个callback函数，后端将数据包裹在callback函数中返回。**\n\n**从原理也能看出，JSONP并不要求必须传递JSON格式的数据，只要是JS函数能够认可的数据都是可以传递的**\n\n## 2、封装JSONP调用JSONP\n\n知道了原理，我们很容易能够实现一个jsonp的函数调用，代码如下：\n\n\twindow.JSONP = function(url, callback){\n\t  callback = callback || 'callback';\n\t  var result;\n\t  return new Promise((resolve, reject) => {\n\t    var overwritten;\n\t    var scriptEl = document.createElement('script');\n\t    scriptEl.src = url + '?callback=' + callback;\n\t    //加载完成后，删除callback\n\t    scriptEl.onload = function(){\n\t      if(overwritten === undefined){\n\t        delete window[callback];  \n\t      }else{\n\t        window[callback] = overwritten;\n\t      }\n\t      resolve(result);\n\t    }\n\t    //挂载一个callback到window上\n\t    overwritten = window[callback]; //先保存一个，用完之后再还原\n\t    window[callback] = function(data){\n\t      result = data\n\t    }\n\t    document.head.appendChild(scriptEl);\n\t  });\n\t};\n\n如何用？\n\n\twindow.JSONP('http://localhost:9999/abc').then((data) => {\n\t  console.log(data);\n\t});\n\n## 3、扩展\n\n在jQuery中，我们使用jsonp感觉就和使用ajax没有区别，但实际上它们的底层实现实现是完全不一样的，毕竟原理都不同。\n\n虽然很多库和框架都把jsonp封装到了ajax中，但是一定要记得jsonp不是ajax的一个特例。\n\n当前，除了用jsonp跨域之外，还可以采用服务端代理（通过不跨域的后端程序，发送webClient去请求数据，然后转发），CORS（API服务器允许跨域的一种设置）。\n"
  },
  {
    "path": "前端相关/JWT详解.md",
    "content": "---\ntitle: JWT详解\ndate: 2017-9-9 13:12:15\n---\n\n# 0、什么是JWT(JsonWebToken)\n\nJWT，全称 JSON Web Token，本身是一种开放的行业标准 [JSON Web Token (JWT)](https://tools.ietf.org/html/rfc7519)，官网 [https://jwt.io/](https://jwt.io/)。\n\n# 1、JWT场景\n\nJWT在分布式环境下，更具有优势。可以实现与主机/部署无关的用户认证。\n\n# 2、对比Session/token认证\n\n**Session**\n\n`Session` 是把认证信息放在服务端（内存，数据库，高速缓存等），一般把 `SessionId` 放在客户端 `Cookie` 中（SessionId也可以放url）。\n\n在请求到达服务端后，通过解析 `Cookie` 中的 `CookieId` 来映射出用户的认证信息。\n\n**Token**\n\n一般常说的 `Token` 认证，和 `Session` 没有本质的区别，只是把 `Token` 放置于请求 `Header` 中。通过获取请求的 `header` 来反查认证信息，实现用户认证。\n\n对比JWT，Session/Token认证具有如下优缺点：\n\n**优势：**\n1. 实现简单（大部分Web框架自动就支持session认证，并能很容易实现token认证）\n2. 滑动过期比较容易实现（session默认就是滑动过期）\n3. 高性能（k-v存储，直接映射）\n\n**劣势：**\n1. 在分布式环境下，需要把 `Session/Token` 存储于分布式缓存上，增加部署成本\n2. 分布式环境下，由于网络开销，性能有所下降\n3. 需要服务端支持，跨不同类型的服务端比较麻烦\n\n**实际上，JWT比较适合分布式应用，在单机部署上，并没有什么优势**\n\n# 3、JWT实现原理\n\nJWT实际上，是通过将 `算法类型和token类型` 以及 `具体的用户认证信息` 连接，并使用加密算法进行加密，生成一串密文。该密文与主机无关，与部署地址无关。只要拿到到用于加密的公私钥和密文，服务端就能解密出认证数据。之所以性能较低，是由于解密需要花去较长的时间（访问量大的时候）。\n\n代码说话，以RSA256为例：\n\n```js\nconst crypto = require('crypto');\n// 1、首先，你得先准备一对RSA公私钥\nconst privateKey = `-----BEGIN RSA PRIVATE KEY-----\nMIICWwIBAAKBgQDdlatRjRjogo3WojgGHFHYLugdUWAY9iR3fy4arWNA1KoS8kVw33cJibXr8bvwUAUparCwlvdbH6dvEOfou0/gCFQsHUfQrSDv+MuSUMAe8jzKE4qW+jK+xQU9a03GUnKHkkle+Q0pX/g6jXZ7r1/xAK5Do2kQ+X5xK9cipRgEKwIDAQABAoGAD+onAtVye4ic7VR7V50DF9bOnwRwNXrARcDhq9LWNRrRGElESYYTQ6EbatXS3MCyjjX2eMhu/aF5YhXBwkppwxg+EOmXeh+MzL7Zh284OuPbkglAaGhV9bb6/5CpuGb1esyPbYW+Ty2PC0GSZfIXkXs76jXAu9TOBvD0ybc2YlkCQQDywg2R/7t3Q2OE2+yo382CLJdrlSLVROWKwb4tb2PjhY4XAwV8d1vy0RenxTB+K5Mu57uVSTHtrMK0GAtFr833AkEA6avx20OHo61Yela/4k5kQDtjEf1N0LfI+BcWZtxsS3jDM3i1Hp0KSu5rsCPb8acJo5RO26gGVrfAsDcIXKC+bQJAZZ2XIpsitLyPpuiMOvBbzPavd4gY6Z8KWrfYzJoI/Q9FuBo6rKwl4BFoToD7WIUS+hpkagwWiz+6zLoX1dbOZwJACmH5fSSjAkLRi54PKJ8TFUeOP15h9sQzydI8zJU+upvDEKZsZc/UhT/SySDOxQ4G/523Y0sz/OZtSWcol/UMgQJALesy++GdvoIDLfJX5GBQpuFgFenRiRDabxrE9MNUZ2aPFaFp+DyAe+b4nDwuJaW2LURbr8AEZga7oQj0uYxcYw==\n-----END RSA PRIVATE KEY-----`;\nconst publicKey = `-----BEGIN PUBLIC KEY-----\nMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDdlatRjRjogo3WojgGHFHYLugdUWAY9iR3fy4arWNA1KoS8kVw33cJibXr8bvwUAUparCwlvdbH6dvEOfou0/gCFQsHUfQrSDv+MuSUMAe8jzKE4qW+jK+xQU9a03GUnKHkkle+Q0pX/g6jXZ7r1/xAK5Do2kQ+X5xK9cipRgEKwIDAQAB\n-----END PUBLIC KEY-----`;\n\n// 2、准备一批数据\nlet header = {\n  'alg': 'RS256',\n  'typ': 'JWT'\n};\nlet payload = {\n  'sub': '1234567890',\n  'name': 'John Doe',\n  'admin': true\n};\n\n// 3、准备处理数据的方法\n// 3.1、将一个对象，处理为Base64字符串\nfunction base64UrlEncode(obj) {\n  var buf = Buffer.from(JSON.stringify(obj), 'utf8');\n  var base64Str = buf.toString('base64');\n  return base64Str.replace(/=/g, '')\n    .replace(/\\+/g, '-')\n    .replace(/\\//g, '_');\n}\n// 3.2、处理一个Base64字符串\nfunction processBase64(base64) {\n  return base64\n    .replace(/=/g, '')\n    .replace(/\\+/g, '-')\n    .replace(/\\//g, '_');\n}\n\n// 4、拼接要加密的的字符串\nlet input = `${base64UrlEncode(header)}.${base64UrlEncode(payload)}`;\n\n// 5、利用私钥加密(RSA256)\nvar signer = crypto.createSign('RSA-SHA256');\nsigner.update(input);// 填入要加密的数据\nvar signature = signer.sign(privateKey, 'base64'); // 获取密文\nsignature = processBase64(signature); // 处理密文\n\n// 6、拼接最终的jwt值\nvar encrypted = `${input}.${signature}`;\n\n// 7、输出查看与验证\nconsole.log('密文是：', encrypted);\nconst expert = `eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.EkN-DOsnsuRjRO6BxXemmJDm3HbxrbRzXglbN2S4sOkopdU4IsDxTI8jO19W_A4K8ZPJijNLis4EZsHeY559a4DFOd50_OqgHGuERTqYZyuhtF39yxJPAjUESwxk2J5k_4zM3O-vtd1Ghyo4IbqKKSy6J9mTniYJPenn5-HIirE`;\nconsole.log('与https://jwt.io/的结果进行比对：', expert === encrypted);\n```\n\n**注意：jwt本身可以使用多种加密方式，如上代码只是显示了RSA-SHA256算法**\n\n# 4、常用问题解析\n\n## 4.1、jwt如何过期呢？\n\n可以在 `jwt` 的 `payload` 数据中，设置好过期时间。后台校验的时候，判断该时间进行过期。\n\n## 4.1、由于jwt与服务器无关，是直接解析jwt这个token值，那么如何强制过期呢？\n\n为了实现强制过期，我们可以考虑把公私钥和用户绑定，如果要强制过期，则重置用户的公私钥，这样也就无法解码了。\n\n# 5、总结\n\n`jwt` 在分布式环境中，有一定的用武之地。但其他相关的逻辑处理，则相对比较复杂。\n\n我个人比较喜欢直接用 `token + 分布式缓存（如Redis）` 这种方案。\n\n以上均为个人理解，如有错误，望指出，感激不尽。\n"
  },
  {
    "path": "前端相关/Nginx常规用法解析.md",
    "content": "---\ntitle: Nginx常规用法解析\ndate: 2017/02/21 14:47:11\n---\n\n## 0、Nginx简介\n\n``Nginx`` 是时下最流行的静态Web服务器之一，使用它能快速的托管一个web站点。\n\n当然，它的功能并不仅限于此，负载均衡，反向代理，它都非常拿手。\n\n然而，要使用它，就不得不提到它的配置文件。本文就大致来了解下 ``Nginx`` 常规配置。\n\n**注意：由于环境因素，该文所有测试均在Win10上使用 ``nginx-1.11.5`` 测试通过。**\n\n## 1、如何开始？\n\n### 1.1、安装\n\n**注意：该安装是指在Windows上安装 **\n\n首先，我们进入 ``Nginx`` 的下载地址：[https://nginx.org/en/download.html](https://nginx.org/en/download.html)（书写该文时，``nginx`` 最新版本是 ``1.11.5``）。\n\n然后，找到nginx的Windows压缩包并下载，然后解压到指定目录即可。\n\n**提示：Linux下安装也是同样的思路，下载压缩包，解压缩。**\n\n### 1.2、启动\n\n解压好之后，进入解压目录，我们会看到一个 ``nginx.exe``，它就是 ``nginx`` 的启动文件。\n\n进入该目录的cmd环境，执行 ``nginx`` 即可启动 ``nginx``，此时我们打开的是一个阻塞的控制台。当我们关闭控制台之后，``nginx`` 服务也相应的关闭了。\n\n大部分时候，我们需要它能在后台执行，此时我们可以使用 ``start nginx`` 来启动一个后台运行的 ``nginx`` 实例。\n\n如果是线上环境，可能还要求它能够开机启动，此时我们可以采用把 ``nginx`` 作为Windows服务的方式来实现开机启动，具体做法请参考： [http://www.spkevin.com/?p=946](http://www.spkevin.com/?p=946)。\n\n### 1.3、重新载入配置\n\n当我们后台启动 ``nginx`` 之后，如果修改了配置文件，想重新载入配置怎么办呢？当然，我们可以停止，然后再启动。实际上，还有更简单的方法，执行如下命令：\n\n```bash\n# 重新载入配置文件\n$ nginx -s reload\n\n# 重新打开日志文件\n$ nginx -s reopen\n```\n\n### 1.4、停止服务\n\n```bash\n# 停止（快速停止，不保存相关信息，快）\n$ nginx -s stop\n\n# 退出（完整有序的停止，保存相关信息，慢）\n$ nginx -s quit \n```\n\n## 2、托管静态目录\n\n托管静态目录是 ``Nginx`` 最常用的功能之一，该功能是将一个目录部署为一个静态的Web站点。\n\n先使用 ``start nginx`` 启动 ``nginx``（默认绑定的是80端口，可能会有冲突，需要修改下端口绑定）。\n\n**修改端口绑定：在 ``nginx/conf`` 的目录下，打开 ``nginx.conf`` 配置文件，找到80，修改为另外一个端口号即可。**\n\n启动之后，我们直接输入 ``http://localhost:<port>`` 即可查看到Nginx的欢迎页面。\n\n先来分析下它的配置文件：\n\n```bash\nhttp {\n  ...\n  server {\n    listen 7777;\n\n    location / {\n      root html;  # 从html相对目录查找内容\n      index index.html index.htm; # 默认首页查找顺序\n    }\n  }\n}\n```\n\n什么意思呢？我们主要需要关注 ``server/location`` 节点，其中 ``root属性`` 表示去哪个目录查找文件，``index属性`` 表示默认首页查找顺序。\n\n### 2.1、托管多个站点\n\n当我们要托管多个站点的时候，我们可以以同样的方式，创建多个 ``server`` 节点，这样就能监听不同的端口，也能托管不同的目录。\n\n### 2.2、有条件查找（复杂实例）\n\n```bash\nserver {\n  listen 8101; # 监听8101端口\n  server_name newegg-central-2.0; # 设置server_name，类似iis的主机头 \n  root /dist;\n  index index.html; \n\n  location ~* \\.(eot|ttf|woff|woff2)$ { # 针对字体请求做特殊处理\n    add_header x-nc2-server $server_addr;\n    add_header Access-Control-Allow-Origin '*'; # 允许字体跨域\n  } \n\n  location ~* \\.[a-zA-Z0-9]+$ { # 针对有后缀名的请求特殊处理，直接返回\n    add_header x-nc2-server $server_addr; \n  }\n\n  location / { # 针对不满足上述条件的请求做处理\n    add_header x-nc2-server $server_addr;\n    access_log off;\n    error_page 404 = /index.html;  # 如果遇到了404，则返回首页（为浏览器history api做的特殊处理，客户端路由）\n  }\n}\n```\n\n## 3、反向代理\n\n反向代理是根据客户端的请求，从后端的服务器上获取资源，然后再将这些资源返回给客户端。所以我们需要一个后端，此处随便找一个站点来测试，如 \"www.newegg.com\"，让如何实现我们访问 localhost:7778，就能返回 ``newegg``的数据呢？\n\n配置如下：\n\n```bash\nserver {\n  listen 7778; # 监听7778端口\n  location / {\n    proxy_pass http://www.newegg.com; #根据客户端请求返回newegg的数据\n  }\n}\n```\n\n### 3.1、利用反向代理，处理跨域\n\n跨域是前后端分离项目中，比较容易遇到的一个问题，在这里演示下，如何利用Nginx来避免跨域问题。\n\n该方法的原理是，将API通过反向代理，让它看起来和站点是在同一个域，避免发起跨域请求。\n\n先在7777端口托管的index.html中添加如下代码：\n\n```html\n<script>\n  let xhr = new XMLHttpRequest();\n  xhr.open('GET', 'http://localhost:7779', true);\n  xhr.send();\n</script>\n```\n\n访问 ``http://localhost:7777``，可以明显的看到有跨域请求，接下来，我们来解决该问题，\n\n首先，修改请求域名为： ``http://localhost:7777/api`` 这样就变成了同域，然后配置 ``nginx``，把 ``/api``的请求转发到真实的后端api上。\n\n配置如下：\n\n```bash\nlocation / {\n  root html;\n  index index.html index.htm;\n}\n\nlocation /api { # 把所有已/api 开头的请求，转发到7779端口\n  proxy_pass http://localhost:7779;\n}\n```\n\n## 4、负载均衡\n\n负载均衡是将应用部署到多个地方，然后用统一入口访问，解决单点故障问题。\n\n先用 ``Node`` 创建两个 ``Web server``，代码如下：\n\n```javascript\n// Server 1\nconst http = require('http');\n\nlet server = http.createServer((req, res) => {\n  res.write('Server 1, port: 7801');\n  res.end();\n});\n\nserver.listen(7801, err => {\n  if(err) return console.error(err);\n  console.log('server 1 started.port: 7801');\n});\n```\n\n为了方便查看效果，我们将 ``Server 2`` 的端口修改为 ``7802``，输出的文字也稍微变下。\n\n接下来我们来进行负载均衡的配置，在 ``nginx`` 中，负载均衡有几种方式：\n\n1. 轮询（默认方式）\n2. 加权轮询（轮询升级版，可以指定权重）\n3. ip_hash（通过ip计算hash，然后跳转到指定服务器）\n4. fair（第三方，根据后台服务器的响应时间来分配请求，响应时间短，优先分配，适应性较强）\n5. upstream_hash（ip hash升级版，可以指定hash因子）\n\n这里我们就简单测试下前两种方式，实现配置如下：\n\n```bash\nhttp {\n  ...\n  upstream test_server {\n    server localhost:7801; # 后端服务器1,\n    server localhost:7802; # 后端服务器2\n  }\n\n  server {\n    listen 7779;\n    location / {\n      proxy_pass http://test_server; # 负载均衡 \n    }\n  }\n}\n```\n\n此时，访问 ``http://localhost:7779``，会发现 Server1 和 Server2 循环命中，这就是默认的轮询方式负载。\n\n接着，测试带权重的负载，修改配置如下：\n\n```bash\nhttp {\n  ...\n  upstream test_server {\n    server localhost:7801 weight=2; # 后端服务器1，权重2\n    server localhost:7802 weight=1; # 后端服务器2，权重1\n  }\n\n  server {\n    listen 7779;\n    location / {\n      proxy_pass http://test_server; # 负载均衡 \n    }\n  }\n}\n```\n\n此时，再多次请求 ``http://localhost:7779``，会发现 Server1 出现两次，Server2 出现一次交替命中。\n\n## 5、更多配置\n\n```bash\nhttp {\n  ...\n  gzip on; # 开启Gzip\n\n  server {\n    ...\n    location / {\n      add_header <field> value [always]; # 返回时追加Header\n      proxy_set_header <field> <value>; # 反向代理时，发送Header \n    }\n  }\n}\n```\n\n更多指令，请查询：[https://nginx.org/en/docs/dirindex.html](https://nginx.org/en/docs/dirindex.html)\n\n想了解完整配置，请查阅：[https://nginx.org/en/docs/](https://nginx.org/en/docs/)\n\n## 6、其他\n\n### 6.1、测试使用的完整Nginx配置文件\n\n```bash\n\n#user  nobody;\nworker_processes  1;\n\n#error_log  logs/error.log;\n#error_log  logs/error.log  notice;\n#error_log  logs/error.log  info;\n\n#pid        logs/nginx.pid;\n\n\nevents {\n    worker_connections  1024;\n}\n\nhttp {\n    include       mime.types;\n    default_type  application/octet-stream;\n\n    # 定制日志格式\n    #log_format  main  '$remote_addr - $remote_user [$time_local] \"$request\" '\n    #                  '$status $body_bytes_sent \"$http_referer\" '\n    #                  '\"$http_user_agent\" \"$http_x_forwarded_for\"';\n\n    #access_log  logs/access.log  main;\n\n    sendfile        on;\n    #tcp_nopush     on;\n\n    # 定制保持连接超时时间\n    #keepalive_timeout  0;\n    keepalive_timeout  65;\n\n    # 是否启用gzip\n    #gzip  on;\n\n    upstream test_server {\n      server localhost:7801 weight=2; \n      server localhost:7802 weight=1;\n    }\n\n    server {\n        listen       7777;\n        server_name  localhost;\n\n        # 设置编码格式\n        #charset koi8-r;\n\n        # 是否开启访问日志\n        #access_log  logs/host.access.log  main;\n\n        location / {\n            root   html;\n            index  index.html index.htm;\n        }\n\n        location /api {\n          proxy_pass http://localhost:7779;\n        }\n\n        # 指定错误页面\n        #error_page  404              /404.html;\n\n        # redirect server error pages to the static page /50x.html\n        #\n        error_page   500 502 503 504  /50x.html;\n        location = /50x.html {\n            root   html;\n        }\n\n        # proxy the PHP scripts to Apache listening on 127.0.0.1:80\n        #\n        #location ~ \\.php$ {\n        #    proxy_pass   http://127.0.0.1;\n        #}\n\n        # 托管PHP的相关配置\n        # pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000\n        #\n        #location ~ \\.php$ {\n        #    root           html;\n        #    fastcgi_pass   127.0.0.1:9000;\n        #    fastcgi_index  index.php;\n        #    fastcgi_param  SCRIPT_FILENAME  /scripts$fastcgi_script_name;\n        #    include        fastcgi_params;\n        #}\n\n        # deny access to .htaccess files, if Apache's document root\n        # concurs with nginx's one\n        #\n        #location ~ /\\.ht {\n        #    deny  all;\n        #}\n    }\n\n    server {\n      listen 7778;\n      location / {\n        proxy_pass http://www.newegg.com;\n      }\n    }\n\n    server {\n      listen 7779;\n      location / {\n        proxy_pass http://test_server;\n      }\n    }\n\n\n    # another virtual host using mix of IP-, name-, and port-based configuration\n    #\n    #server {\n    #    listen       8000;\n    #    listen       somename:8080;\n    #    server_name  somename  alias  another.alias;\n\n    #    location / {\n    #        root   html;\n    #        index  index.html index.htm;\n    #    }\n    #}\n\n\n    # 托管HTTPS\n    # HTTPS server\n    #\n    #server {\n    #    listen       443 ssl;\n    #    server_name  localhost;\n\n    #    ssl_certificate      cert.pem;\n    #    ssl_certificate_key  cert.key;\n\n    #    ssl_session_cache    shared:SSL:1m;\n    #    ssl_session_timeout  5m;\n\n    #    ssl_ciphers  HIGH:!aNULL:!MD5;\n    #    ssl_prefer_server_ciphers  on;\n\n    #    location / {\n    #        root   html;\n    #        index  index.html index.htm;\n    #    }\n    #}\n\n}\n```\n"
  },
  {
    "path": "前端相关/TypeScript配置文件tsconfig简析.md",
    "content": "---\ntitle: TypeScript配置文件tsconfig简析\ndate: 2017/02/21 14:47:11\n---\n\n## 0、前言\n\n在使用VsCode编写TypeScript时，VsCode提供了一个tsconfig.json辅助我们设置TypeScript的配置项，另外使用gulp-typescript或者是webpack编译ts文件时，都可以用到这个配置项来确定如何生成最终的js文件。\n\n那tsconfig.json到底有哪些常用属性，它们又起到什么作用呢？下文将为你一一揭晓。\n\n## 1、tsconfig.json\n\n### 1.1、compilerOptions\n\ntsconfig.json文件中的 ``compilerOptions`` 属性用于确定如何编译ts文件。\n\n其中大概有如下属性：\n\n1.1.1、**module: enum**\n\n``module`` 用于指定模块的代码生成规则，可以使用 ``commonjs`` 、 ``amd`` 、 ``umd`` 、 ``system`` 、 ``es6`` 、 ``es2015`` 、 ``none`` 这些选项。\n\n选择commonJS，会生成符合commonjs规范的文件，使用amd，会生成满足amd规范的文件，使用system会生成使用ES6的system.import的代码。使用es6或者是es2015会生产包含ES6特性的代码。\n\n1.1.2、**target: enum**\n\n``target`` 用于指定生成代码的兼容版本，可以从es3,es5,es2015,es6中选择一个，如果不设置，默认生产的代码兼容到es3。\n\n1.1.3、**sourceMap: boolean**\n\n``sourceMap`` 是是否生成SourceMap的开关，如果设置为true，则会生成.map文件。\n\n1.1.4、 **noImplicitAny: boolean**\n\n``noImplicitAny`` 当表达式和申明 类型为any时，是否需要发出警告，设置true，则不警告\n\n1.1.5、 **removeComments: boolean**\n\n用于指定是否需要输出注释，设置为true，则不输出注释。\n\n1.1.6、 **charset: string**\n\n用于指定ts文件的编码格式\n\n1.1.7、 **declaration: boolean**\n\n是否需要生成定义文件d.ts，设置为true，则生成。\n\n1.1.8、 **diagnostics: boolean**\n\n是否需要显示诊断信息，设置为true，则显示。\n\n1.1.9、 **emitBOM: boolean**\n\n是否需要在输出文件的开头发出一个UTF-8字节顺序标记，设置为true，则输出。\n\n1.1.10、**inlineSourceMap: boolean**\n\n是否需要将sourceMap文件生成到js文件中，设置为true，则生成到js文件中。\n\n**注：此选项和sourceMap、mapRoot选项冲突，会优先使用inlineSouceMap**\n\n1.1.11、**inlineSources： boolean**\n\n用于指定生成的source内容是否inline，如果设置为true，则inline展示（从测试的效果来看，就是生成在js文件中的source map内容要多一些）\n\n**注：该设置项依赖inlineSouceMap设置为true**\n\n1.1.12、**jsx: enum**\n\n用于指定按照何种方式生成jsx代码，可选react和preserve。\n\n1.1.13、**reactNamespace: string**\n\n配置jsx属性使用，指定生成react代码时，需要使用的命名空间。默认\"\"\n\n1.1.14、**listFiles: boolean**\n\n编译时是否需要打印文件列表，设置为true，则打印。默认false\n\n1.1.15、**locale: string**\n\n用于指定本地化错误信息，如果设定为en-us，那么错误信息将显示英文。默认\"\"\n\n1.1.16、**mapRoot: string(uri)**\n\n指定map文件的跟路径，该选项的值影响.map文件中的sources属性。默认\"\"\n\n**注：该选项依赖sourceMap: true**\n\n1.1.17、**newLine: enum**\n\n指定换行符。可选 ``CRLF`` 和 ``LF`` 两种，前者是回车换行，后者是换行。默认是回车换行\n\n1.1.18、**noEmit: boolean**\n\n当设置为true时，将不会输出\n\n1.1.19、**noEmitHelpers: boolean**\n\n设置为true时，不会生成自定义的helper函数。\n\n1.1.20、**noEmitOnError: boolean**\n\n设置为true时，如果遇到了错误，就不再输出\n\n1.1.21、**noLib: boolean**\n\n设置为true时，将不会包含默认的库，如（lib.d.ts）,此时有可能导致找不到Array，String等对象\n\n1.1.22、**noResolve: boolean**\n\n设置为true时，不使用三斜杠引入模块，需要从编译的文件列表中添加。\n\n```javascript\n/// <reference path=\"\" />\nimport PI from './2A.ts';\n```\n\n1.1.23、**skipDefaultLibCheck: boolean**\n\n设置为true时，将跳过默认库检查。\n\n1.1.24、**outFile: string(uri)**\n\n设置输出文件，会将多个ts输入文件合并到该文件中\n\n1.1.25、**outDir: string(uri)**\n\n指定输出文件的根目录。\n\n1.1.26、**preserveConstEnums: boolean**\n\n设置为true时，生成代码时不会删除常量枚举声明。\n\n1.1.27、**pretty: boolean**\n\n当设置为true时，错误信息，跟踪信息将带有颜色和样式\n\n1.1.28、**noImplicitUseStrict: boolean**\n\n当设置为true时，编译输出时不会调用'use strict'指令（也就是不生成use strict）\n\n1.1.29、**rootDir: string(uri)**\n\n指定输入文件的根目录。rootDir应包含所有的源文件。\n\n1.1.30、**isolatedModules: boolean**\n\n设置为true时，无条件的触发导入未解决的文件。\n\n1.1.31、**sourceRoot: string(uri)**\n\n设置在调试时定位的目标文件根目录\n\n1.1.32、**suppressExcessPropertyErrors: boolean**\n\n设置为true时，禁止过剩的对象字面量属性检查\n\n1.1.33、**suppressImplicitAnyIndexErrors: boolean**\n\nSuppress noImplicitAny errors for indexing objects lacking index signatures.\n\n1.1.34、**stripInternal: boolean**\n\n设置为true，则遇到@internal注解时，不会触发代码定义。\n\n1.1.35、**watch: boolean**\n\n设置为true时，将监视文件变化。当文件变化时，自动编译\n\n1.1.36、**experimentalDecorators: boolean**\n\n设置为true，则支持ES7的装饰器特性\n\n1.1.37、**emitDecoratorMetadata: boolean**\n\n设置为true，则使用元数据特性\n\n1.1.38、**moduleResolution: string**\n\n指定模块的解析策略，Node或者是classic，默认是classic。\n\n1.1.39、**allowUnusedLabels: boolean**\n\n设置为true时，允许没有用到的标签。\n\n```\nl: do{\n  console.log('abc');\n}while (1 !== 1);\n```\n\n以上代码有个未使用的标签l，默认是会报错的。\n\n1.1.40、**noImplicitReturns: boolean**\n\n设置为true时，如果不是函数中的所有路径都有返回值，则提示Error。\n\n```javascript\nvar a = 2;\nfunction fun(){\n  if(a === 1){\n    return 'abc';\n  }\n  //fun函数只有当a = 1的时候，才有确定的返回值。\n}\n```\n\n1.1.41、**noFallthroughCasesInSwitch: boolean**\n\n设置为true时，将严格校验switch-case语法。\n\n```javascript\nfunction fun2(){\n  let key = 'ab';\n  switch (key) {\n    case 'ab':\n      console.log('abc');\n  }\n}\n```\n\n以上代码默认情况不会报错，当设置noFallthroughCasesInSwitch: true时，则会提示错误。\n\n1.1.42、**allowUnreachableCode: boolean**\n\n设置为true时，如果有无法访问的代码，也不会报错。\n\n```javascript\nfunction fun(){\n  return 'abc';\n  return 'ccc'; //默认会报错，设置allowUnreachableCode为true时，则不报错\n}\n```\n\n1.1.43、**forceConsistentCasingInFileNames: boolean**\n\n设置为true时，将强制区分大小写。默认为false。\n\n```javascript\n//2a.ts\nexport const PI = 3.1415926;\n//1a.ts\nimport PI from './2A.ts';\nfunction fun(){\n  return PI;\n}\n```\n\n以上代码默认可以通过，当强制区分大小写时，则提示错误 '2a' !== '2A'\n\n1.1.44、**allowSyntheticDefaultImports: boolean**\n\n设置为true时，则允许从没有默认导出的模块中默认导入(也就是不做检查)。\n\n```javascript\n//2.ts\nexport const PI = 3.1415926;\n//1.ts\nimport PI from './2.ts';\nfunction fun(){\n  return PI;\n}\n```\n\n以上代码，默认是会报错的，当设置allowSyntheticDefaultImports时，则不会报错。\n\n1.1.45、**allowJs: boolean**\n\n当设置为true时，js文件也会被编译。\n\n**注意：编译js文件时，如果不另外设置outFile，将不会成功，因为不能够重写源代码文件**\n\n### 1.2、compileOnSave\n\n该属性用于启用保存时编译功能。\n\n***注意：当前仅仅只有VS2015配置TypeScript1.8.4以后或者在atom中搭配atom-typescript插件才有效*\n\n### 1.3、exclude\n\nexclude用于排除不需要编译的ts文件。该属性和files属性冲突。两者只能设置其一。\n\n### 1.4、files\n\n当files属性不存在时，编译器会编译当前目录和子目录中的所有文件。当files属性存在是，仅仅是编译files列表中的文件。\n\n该属性和exclude属性冲突。如果同时指定了exclude和files，则files属性优先。\n\n\n## 2、常用tsconfig配置\n\n```json\n{\n  \"compilerOptions\": {\n    \"target\": \"ES5\",\n    \"module\": \"commonjs\",\n    \"emitDecoratorMetadata\": true,\n    \"experimentalDecorators\": true,\n    \"sourceMap\": true,\n    \"noEmitHelpers\": true\n  },\n  \"exclude\": [\n    \"node_modules\",\n    \"typings/main\",\n    \"typings/main.d.ts\"\n  ],\n  \"compileOnSave\": false\n}\n```\n\n## 3、参考资料\n\n* [https://www.typescriptlang.org/docs/handbook/tsconfig-json.html](https://www.typescriptlang.org/docs/handbook/tsconfig-json.html)\n* [http://json.schemastore.org/tsconfig](http://json.schemastore.org/tsconfig)"
  },
  {
    "path": "前端相关/VsCode简易配置手册.md",
    "content": "---\ntitle: VsCode简易配置手册\ndate: 2017/02/21 14:47:11\n---\n\n## 0、什么是VsCode（Visual Studio Code - vsc）\n\nVsCode是MS基于Atom Shell开发的新一代 ***跨平台*** 编辑器。vsc将简洁和\"编码-编辑-调试\"循环的流水线特点结合在代码编辑器中。\n\n截止该文创建时，最新版本是0.10.11。官网地址： [https://code.visualstudio.com](https://code.visualstudio.com)\n\n## 1、VsCode的优势\n\n对比业界各类强大的编辑器，VsCode有哪些优势呢？\n\n1. 跨平台 - VsCode兼容Windows,Linux,OSX三大平台\n2. 高性能 - VsCode对比Atom，有更高的性能，更快的大文件加载速度\n3. 集成Git - Git作为最流行的版本控制工具，VsCode默认集成\n4. 代码调试 - 强大的调试工具\n5. 智能提示（代码不全） - 强大的代码不全，语义理解\n6. 插件机制 - 可以很方便的扩展需要的功能\n\n## 2、配置编辑器\n\n### 2.1、用户设置\n\n通过选择菜单栏上的Files > Preferences > User Settings，或者是按F1，然后输入user，进行user settings.json的配置。\n\n在配置时，默认是分栏的界面，左侧是标准的系统配置项，右侧是用户自定义配置项。配置右边的项就可以参考左边的标准配置。\n\n由于默认配置已经非常实用了，所以需要我们自定义配置的项并不多：\n\n```json\n{\n    \"editor.tabSize\": 2, //tab键所占用的字符数\n    \"files.exclude\": { //需要排除的文件（夹），不在EXPLORER面板中显示\n      \".idea/\": false,\n      \".vscode/\": false,\n      \"typings/\": false\n    }\n}\n```\n### 2.2、工作空间配置\n\n与用户设置一样，我们可以针对工作空间进行配置，此处的配置文件，保存在目录下vscode/settings.json中。\n\n### 2.3 常用快捷键说明\n\n1. Ctrl+Shift+\\  在块标记之间跳转\n2. Ctrl+Down or Ctrl+Up  上下移动一行\n3. Ctrl+Alt+Down or Ctrl+Alt+Up  上下纵向选择\n4. Alt+Click  添加辅助光标，然后就可以多个光标编辑了\n5. Ctrl+D 当有选中字符时，跳转到下一个该字符处\n6. Ctrl+F2 or Ctrl+Shift+L  选中所有出现的选中字符\n7. Ctrl+T 搜索语法\n8. F1打开输入框，删除>,输入?,可以看到相关命令\n\n更多快捷键，请参考：[https://code.visualstudio.com/docs/customization/keybindings](https://code.visualstudio.com/docs/customization/keybindings)\n\n## 3、Debugging相关配置\n\n当前支持的Debugging语言有node，Chrome，Go，大概配置如下：\n\n```json\n{\n    \"version\": \"0.2.0\",\n    \"configurations\": [\n        {\n            \"name\": \"Launch\", //名称\n            \"type\": \"node\",  //类型\n            \"request\": \"launch\",\n            \"program\": \"app.js\", //要启动的程序\n            \"stopOnEntry\": false,\n            \"args\": [], //启动参数\n            \"cwd\": \".\", //从那个相对路径启动\n            \"runtimeExecutable\": null,\n            \"runtimeArgs\": [\n                \"--nolazy\"\n            ],\n            \"env\": {\n                \"NODE_ENV\": \"development\" //环境变量\n            },\n            \"externalConsole\": false,\n            \"preLaunchTask\": \"\",\n            \"sourceMaps\": false,\n            \"outDir\": null\n        },\n        {\n            \"name\": \"Attach\",\n            \"type\": \"node\",\n            \"request\": \"attach\",\n            \"port\": 5858\n        }\n    ]\n}\n```\n\n## 4、Tasks相关配置\n在VsCode中，我们还可以自定义task。首先在.vscode目录下创建tasks.json，进行task的配置：\n\n```json\n{\n\t\"version\": \"0.1.0\",\n\t\"command\": \"gulp\",\n\t\"isShellCommand\": true,\n\t\"tasks\": [\n\t\t{\n\t\t\t\"taskName\": \"less\", //配置任务名称\n\t\t\t// Make this the default build command.\n\t\t\t\"isBuildCommand\": true,\n\t\t\t// Show the output window only if unrecognized errors occur.\n\t\t\t\"showOutput\": \"silent\",\n\t\t\t// Use the standard less compilation problem matcher.\n\t\t\t\"problemMatcher\": \"$lessCompile\"\n\t\t}\n\t]\n}\n```\n\n## 5、语言环境相关配置\n\n在使用VsCode的过程中，非常吸引人的一个特性就是智能提示。在0.10.8和之前的版本，node代码是直接可以通过typings来智能提示的。但是在0.10.10版本之后，就需要通过配置文件来启用功能了。\n\n### 5.1 JavaScript\n\n在使用JavaScript的时候，配置文件是jsconfig.json(tsconfig.json的子集)，如果目录中存放该问题，表示该目录是项目的根路径。jsconfig配置文件本身列出属于该项目以及编译器的选项。大致内容如下：\n\n```json\n{\n  \"compilerOptions\": {\n    \"target\": \"ES6\", //可选es3,es5,es6,es2015\n    \"module\": \"commonjs\" //可选amd,umd,commonjs,system\n  },\n  \"files\": [ \n    \"app.js\",\n    \"model.js\"\n  ],\n  \"exclude\": [ //排除的目录，不在搜索\n    \"node_modules\"\n  ]\n}\n```\n**Tips: 如果应用程序有的代码在app/ or src/下，那么jsconfig应该在这些目录下创建**\n\n**Tips: 如果你的工作空间中，包含多个不同的应用程序（比如client和server），那么请在每个文件夹下增加独立的jsconfig.json文件**\n\n**Tips： 如果没有jsconfig.json，默认是排除node_modules目录**\n\n### 5.2、Dockerfile\n\nDockerfile编写支持，极大的丰富了VsCode的多样性。要支持Dockerfile，需要通过ext install 来安装Dockerfile and Docker Compose File Support扩展。\n\n创建好一个Dockerfile文件后，使用空格就可以使用智能提示了。\n\n### 5.3、关于智能提示所需要的typing文件\n\n要安装typing文件，那么首先需要我们全局安装``npm install typings -g``，typings相关地址[https://github.com/typings/typings](https://github.com/typings/typings)\n\n通过``typings search --name <name>`` 可以全名匹配搜索插件\n\n通过``typings search <name>`` 可以模糊搜索插件\n\n通过``typings install <name> --ambient --save`` 可以安装type definition\n\n如何要js文件能够智能提示呢？\n\n**方式一：**可以在js文件中首行使用引用注释的方式：\n\n```javascript\n/// <reference path=\"typings/main.d.ts\" />\n'use strict';\nvar fs = require('fs');\nfs.access...\n```\n\n**方式二：**\n把typings文件加入到jsconfig.json中的files项下，不要忘记Reload JavaScript Project\n\n```json\n{\n  \"compilerOptions\": {\n    \"target\": \"ES6\", //可选es3,es5,es6,es2015\n    \"module\": \"commonjs\", //可选amd,umd,commonjs,system\n    \"moduleResolution\": \"node\"\n  },\n  \"files\": [\n    \"typings/main.d.ts\"\n  ]\n}\n```\n\n\n**另，附上DefinitelyTyped仓库地址：[https://github.com/DefinitelyTyped/DefinitelyTyped](https://github.com/DefinitelyTyped/DefinitelyTyped)**\n\n## 6、MORE\n\n更多内容，请查阅：[https://code.visualstudio.com/docs/](https://code.visualstudio.com/docs/)\n\n补充说明，2016-3-18 16:08:01更新\n\n如果在VsCode中开启了EsLint，而又使用了import等语法，请使用如下.eslintrc\n\n```json\n{\n    \"parserOptions\": {\n        \"ecmaVersion\": 6,\n        \"sourceType\": \"module\", //支持import\n        \"ecmaFeatures\": {\n            \"globalReturn\": true,\n            \"impliedStrict\": true,\n            \"jsx\": true,\n            \"experimentalObjectRestSpread\": true,\n            \"modules\": true\n        }\n    },\n    \"rules\": {\n        \"semi\": 2\n    },\n    \"env\": {\n      \"browser\": false,\n      \"node\": true\n    }\n}\n```\n"
  },
  {
    "path": "前端相关/Web API接口之FileReader.md",
    "content": "---\ntitle: Web API接口之FileReader\ndate: 2017/02/21 14:47:11\n---\n\n## 0、导言\n\n在给网站编写 JavaScript 代码时，也有很多可用的 API。 [WEB API 参考](https://developer.mozilla.org/zh-CN/docs/Web/API)。\n\n## 1、FileReader\n\n使用FileReader对象,web应用程序可以异步的读取存储在用户计算机上的文件(或者原始数据缓冲)内容。兼容IE10+，FF和Chrome。Safari和Opera不支持。\n\n\n## 2、如何读取文件？\n\n\tvar readFile = function(file, callback) {\n\t  var fileReader = new FileReader(); //实例化FileReader\n\t  fileReader.onloadend = function() { //加载完成后执行\n\t    var result = null; \n\t    if (fileReader.readyState === FileReader.DONE) { //判断操作是否完成\n\t      result = fileReader.result; //获取结果\n\t    }\n\t    if (callback) {\n\t      callback(result);\n\t    }\n\t  };\n\t  fileReader.readAsBinaryString(file); //以二进制的方式读取文件\n\t};\n\n调用的话，就可以通过如下代码调用\n\n\treadFile(file, function(result){\n\t  // do something\n\t});\n\n其中file既可以是blob对象也可以是一个File对象。一般我们常用的是File对象，如何来获取一个简单的File对象呢？\n\n\t<input type=\"file\" id=\"file_input\">\n\nJS:\n\n\tvar fileEl = document.getElementById('#file_input');\n\tvar file = fileEl.files[0]; //files是数组对象\n\n## 3、FileReader API\n\n### 方法\n\n1. void abort(); \n2. void readAsArrayBuffer(in Blob blob);\n3. void readAsBinaryString(in Blob blob);\n4. void readAsDataURL(in Blob blob);\n5. void readAsText(in Blob blob, [optional] in DOMString encoding);\n\n其中1是终止读取操作，2~4是将数据读取为不同的格式。\n\n### 状态常量\n\n1. EMPTY 还没有加载任何数据\n2. LOADING 数据正在被加载\n3. DONE 已完成全部的读取请求\n\n### 属性（属性全部都是只读的）\n\n1. error 读取文件时发生的错误\n2. readyState FileReader对象的当前状态\n3. result 读取到的文件内容\n\n## 4、用途\n\n1. 客户端校验文件内容\n2. 预览图片\n3. 客户端导出\n\n## 5、参考文档\n\n1. [MDN - Blob](https://developer.mozilla.org/zh-CN/docs/Web/API/Blob)\n2. [MDN - FileReader](https://developer.mozilla.org/zh-CN/docs/Web/API/FileReader)\n\n## 6、后续补充\n\n### 2016-2-17日追加\n\nFileReader的API方法readAsBinaryString在IE11中无法使用，为了兼容IE11，我们需要使用另外的API或者使用猴子补丁的方式实现该API。\n\n参考[http://stackoverflow.com/questions/31391207/javascript-readasbinarystring-function-on-e11](http://stackoverflow.com/questions/31391207/javascript-readasbinarystring-function-on-e11)\n\n补丁代码如下：\n\n```javascript\nif(!FileReader.prototype.readAsBinaryString){\n   FileReader.prototype.readAsBinaryString = function (blob) {\n     var binary = '';\n     var self = this;\n     var reader = new FileReader();\n     reader.onload = function(e){\n       var bytes = new Uint8Array(reader.result); \n       var length = bytes.byteLength;\n       for (var i = 0; i < length; i++) {\n         binary += String.fromCharCode(bytes[i]);\n       }\n       self.result = binary;\n       $(pt).trigger('onload');\n     };\n     reader.readAsArrayBuffer(blob);\n   }\n}\n```"
  },
  {
    "path": "前端相关/Web API接口之Geolocation.md",
    "content": "---\ntitle: Web API接口之Geolocation\ndate: 2017/02/21 14:47:11\n---\n\n## 0、关于Geolocation\n\nGeolocation，地理位置API。用于获取用户的位置信息。它不算是现有的HTML5标准的“直系”成员，但是是W3C的一个标准。它几乎就是一个真正的JavaScript API！\n\n## 1、位置相关\n\n### 1.1、经纬度表示位置\n要想知道用户的位置，就需要有一个坐标系统，这就是我们的经纬度。一般我们用度/分/秒表示经纬度，如果需要将经纬度转换为小数，可以使用如下函数：\n\n    function degreesToDecimal(degrees, minutes, seconds){\n      return degrees + (minutes / 60) + (seconds / 3600);\n    }\n    \n###  1.2、API如何确定你的位置\n\n浏览器要获取你的位置信息，并不要求你非得使用最新的智能手机，即使桌面浏览器也能获取你的位置。那么是如何获取到位置的呢？\n\n其实，获取位置信息的方式有很多，比如：\n\n1. IP地址 --通过ip地址库获取你的位置\n2. GPS  --通过全球定位系统获取你的位置（高精度）\n3. 蜂窝电话  --通过三角定位获取你的位置\n4. Wi-Fi  -- 同样适用类似蜂窝电话的三角定位获取位置\n\n我们没办法知道设备是使用的何种方法获取我们的位置信息，一些聪明的浏览器很可能会使用多种方式来确定你的位置。\n\n## 2、如何使用Geolocation\n\n在使用Geolocation之前，我们需要先是否支持，通过如下代码：\n\n    if(navigator.geolocation){\n      //Supported.\n    } else {\n      window.alert('No geolocation support.');\n    }\n\n### 2.1、获取位置\n\n获取位置信息的方法是一个异步方法，我们应该如下来使用它：\n\n    if(navigator.geolocation){\n      var callback = function(pos){\n        console.log('你的位置是：', pos);\n      };\n      navigator.geolocation.getCurrentPosition(callback);\n    } else {\n      window.alert('No geolocation support.');\n    }\n\n其中pos长什么样呢？大概是如下这个样子的：\n\n    {\n      coords： { // Coordinates\n        accuracy: 137082,\n        altitude: null,\n        altitudeAccuracy: null,\n        heading: null,\n        latitude: 24.1848198,\n        longitude: 120.63149479999998,\n        speed: null\n      },\n      timestamp: 1453877895563\n    }\n\n其中latitude和longitude就是我们的经纬度了。\n\n该方法的语法是：navigator.geolocation.getCurrentPosition(success[, error[, options]])，具体参数含义，请接着往下看。\n\n### 2.2、监控位置变化\n\n在2.1中，我们知道如何获取位置，那如何监控位置变化呢？很容易相当的办法，就是我们定时去获取位置信息，然后比对。那么有没有更好的方式呢？当然，Geolocation API已经帮我们考虑好了。如下：\n\n    //正常时，会获取到一个地理位置信息\n    var watchSuccess = function(pos){\n      var latitude = pos.coords.latitude;\n      var longitude = pos.coords.longitude;\n      console.log('你的经纬度是：', latitude, longitude);\n    };\n    //错误时，函数会接收一个错误对象\n    var watchError = function(err){\n      console.warn(err, err.code, err.message);\n    };\n    var watcherId = navigator.geolocation.watchPosition(watchSuccess, watchError);\n    \nwatchPosition方法的语法是：id = navigator.geolocation.watchPosition(success[, error[, options]])。所以，我们还可以针对这个方法设置参数：\n\n    var options = {\n      enableHighAccuracy: false, //默认false，为true时，则选择最高的精度获取位置\n      timeout: 5000, //每次获取位置信息的最长时间，默认是无限的\n      maximumAge: 0 // 缓存时间（毫秒），如果为0，则每次获取最新的\n    };\n    \n### 2.3 清除监控\n\n既然我们有监控方法，那么该如何停止呢？这就要借助清除监控的方法了，代码如下：\n\n    navigator.geolocation.clearWatch(watcherId);\n    \nwatcherId是2.2中监控时的返回值。\n\n### 2.4、如何计算距离？\n\n给予两个坐标点，如何计算两者之间的距离呢？一般采用半正矢（Haversine）公式，具体代码如下：\n\n  function degreesToRadians(degrees){\n    var radians = (degrees * Math.PI) / 180;\n    return radians;\n  }\n\n  function computeDistince(startCoords, destCoords){\n    var Radius = 6371; //每度在地球上的距离（km）\n    \n    var startLatRads = degreesToRadians(startCoords.latitude);\n    var startLongRads = degreesToRadians(startCoords.longitude);\n    var destLatRads = degreesToRadians(destCoords.latitude);\n    var destLongRads = degreesToRadians(destCoords.longitude);\n    \n    var distince = Math.acos(\n      Math.sin(startLatRads) * Math.sin(destLatRads) + \n      Math.cos(startLatRads) * Math.cos(destLatRads) * \n      Math.cos(startLongRads - destLongRads)\n    ) * Radius;\n    return distince;\n  }\n  \n## 3、扩展\n\n地理位置API单独使用意义不大，一般来说，结合地图就可以实现很复杂的功能了。\n\n待续..."
  },
  {
    "path": "前端相关/Webpack In Angular2.md",
    "content": "---\ntitle: Webpack In Angular2\ndate: 2017/02/21 14:47:11\n---\n\n## 0、前言\n\n当下Angular2是比较值得关注的技术了，想要把Angular2跑起来，还是比较容易的。但\n\n在这里，我要做的是搭建一个Angular2的开发环境，那么就一步一步来尝试下。\n\n## 1、搭建开发环境\n\n### 1.1、创建项目\n\n新建目录 ``webpack-in-angular2``，然后进入目录执行 ``npm init -f`` 创建好 ``package.json`` 文件。\n\n打开package.json文件。新建属性 ``dependencies`` ，并赋值如下：\n\n```json\n{\n  \"dependencies\": {\n    \"@angular/common\": \"^2.0.0-rc.1\",\n    \"@angular/compiler\": \"2.0.0-rc.1\",\n    \"@angular/core\": \"2.0.0-rc.1\",\n    \"@angular/http\": \"2.0.0-rc.1\",\n    \"@angular/platform-browser\": \"2.0.0-rc.1\",\n    \"@angular/platform-browser-dynamic\": \"2.0.0-rc.1\",\n    \"@angular/router\": \"^2.0.0-rc.1\",\n    \"@angular/router-deprecated\": \"2.0.0-rc.1\",\n    \"@angular/upgrade\": \"2.0.0-rc.1\",\n    \n    \"es6-shim\": \"^0.35.0\",\n    \"reflect-metadata\": \"^0.1.3\",\n    \"rxjs\": \"5.0.0-beta.6\",\n    \"zone.js\": \"^0.6.12\"\n  }\n}\n```\n\n目录下打开控制台，执行 ``npm i`` 安装依赖。\n\n至此，Angular2项目已经创建完成。\n\n### 1.2、安装webpack\n\n首先要确保已经全局安装了 ``webpack``\n\n接着安装所需要包 ``npm install --save-dev webpack typescript ts-loader ts-helpers``\n\n### 1.3、配置webpack\n\n在根目录创建 ``webpack.config.js`` 文件，用于编写webpack的相关配置项。\n\n在配置 ``webpack`` 之前，我们先在 ``src`` 目录下创建好我们需要的 ``polyfills.ts`` 和 ``vendor.ts``，内容如下：\n\n```typescript\n//polyfills.ts\n\nimport 'es6-shim';\nimport 'reflect-metadata';\nrequire('zone.js/dist/zone');\n\nimport 'ts-helpers';\n```\n\n```typescript\n//vendor.ts\n\n// Angular 2\nimport '@angular/platform-browser';\nimport '@angular/platform-browser-dynamic';\nimport '@angular/core';\nimport '@angular/common';\nimport '@angular/http';\nimport '@angular/router';\nimport '@angular/router-deprecated';\n\n//RxJS\nimport 'rxjs';\n```\n\n接下来，我们再配置webpack的配置项，如下：\n\n```javascript\n//webpack.config.js\n\n'use strict';\n\nlet webpack = require('webpack');\n\nmodule.exports = {\n  debug: true,\n  entry: {\n    polyfills: './src/polyfills.ts',\n    vendor: './src/vendor.ts'\n  },\n  output: {\n    path: 'dist/assets/js',\n    filename: '[name].js',\n    chunkFilename: '[id].chunk.js'\n  },\n  module: {\n    loaders: [\n      {test: /\\.ts$/, loader: 'ts'}\n    ]\n  }\n};\n```\n\n此时，通过控制台执行 ``webpack`` 会提示找不到路径错误。\n\n这个时候，我们需要新建一个 ``tsconfig.json`` 文件，内容如下：\n\n```json\n//tsconfig.json\n\n{\n  \"compilerOptions\": {\n    \"module\": \"commonjs\",\n    \"target\": \"es5\"\n  }\n}\n```\n\n之后再执行 ``webpack``，可以看到在dist/assets/js目录下，已经生成了我们需要的 ``polyfills.js`` 和 ``vendor.js`` 两个文件。\n\n至此，简单的webpack使用，已经ok，同时也可以生成我们需要的来个库文件了。\n\n### 1.4、Angular2 Hello World\n\n接下来，我们实现一个Angular2的Hello App。\n\n在 ``src/`` 下创建 ``bootstrap.ts`` 文件，内容如下：\n\n```typescript\nimport {Component} from '@angular/core';\nimport {bootstrap} from '@angular/platform-browser-dynamic';\n\n@Component({\n  selector: 'demo-app',\n  template: `\n<h3>Hello, Angular2 and Webpack.</h3>\n  `\n})\n\nexport class AppComponent{\n  constructor(){\n    \n  }\n}\n\nbootstrap(AppComponent);\n```\n\n然后在 ``webpack.config.js`` 中添加入口点：\n\n```javascript\nentry: {\n  polyfills: './src/polyfills.ts',\n  vendor: './src/vendor.ts',\n  bootstrap: './src/bootstrap.ts' //新增的入口文件\n}\n```\n\n再次执行 ``webpack`` 命令，就可以找到 ``dist/assets/js/bootstrap.js`` 文件了。但打开该文件一看，似乎不对，把angular都已经打包进去了。其实我们已经把angular打包到 ``vendor.js`` 中了，根本就不需要再打包到 ``bootstrap.js`` 。\n\n此时，我们可以修改webpack的config文件，添加公共代码块引用，配置如下：\n\n```javascript\n'use strict';\n\nlet webpack = require('webpack');\nlet CommonsChunkPlugin = webpack.optimize.CommonsChunkPlugin;\n\nmodule.exports = {\n  debug: true,\n  entry: {\n    polyfills: './src/polyfills.ts',\n    vendor: './src/vendor.ts',\n    bootstrap: './src/bootstrap.ts'\n  },\n  output: {\n    path: 'dist/assets/js',\n    filename: '[name].js',\n    chunkFilename: '[id].chunk.js'\n  },\n  module: {\n    loaders: [\n      {test: /\\.ts$/, loader: 'ts'}\n    ]\n  },\n  plugins: [\n    new CommonsChunkPlugin({\n      name: ['vendor', 'polyfills'] //vendor和polyfills设置为公共代码块\n    })\n  ]\n};\n```\n\n再次执行 ``webpack`` ，发现已经达到我们想要的效果了（bootstrap.js只包含了必须的代码）。\n\n接着，我们在 ``src/`` 创建 ``index.html`` 文件，内容如下：\n\n```html\n<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n  <meta charset=\"UTF-8\">\n  <title>Webpack in Angular2 demo</title>\n</head>\n<body>\n  <demo-app></demo-app>\n  <script src=\"assets/js/polyfills.js\"></script>\n  <script src=\"assets/js/vendor.js\"></script>\n  <script src=\"assets/js/bootstrap.js\"></script>\n</body>\n</html>\n```\n\n同时，修改 ``webpack.config.js`` ，使用 ``copy-webpack-plugin`` 将 ``index.html`` 复制到 ``dist/`` 目录下。\n\n先通过 ``npm install copy-webpack-plugin --save-dev`` 安装该插件，然后修改配置节点如下：\n\n```javascript\nplugins: [\n  new CommonsChunkPlugin({\n    name: ['vendor', 'polyfills'] //vendor和polyfills设置为公共代码块\n  }),\n  new CopyWebpackPlugin([\n    //将src/index.html复制到dist目录。\n    {form: 'src/index.html', to: path.join(__dirname, 'dist')}\n  ])\n]\n```\n\n完成以上步骤之后，打开dist目录，使用 ``anywhere`` 命令打开 ``web server``，可以在自动打开的浏览器中看到 Hello, Angular2 and Webpack.\n\n至此，我们已经将Angular2和Webpack结合起来使用了。\n\n### 1.5、使用webpack-dev-server\n\n每次都在 ``dist/`` 目录下，通过 ``anywhere`` 来打开，并不方便。我们可以通过 ``webpack-dev-server`` 插件实现静态服务器。"
  },
  {
    "path": "前端相关/Webpack初体验.md",
    "content": "---\ntitle: Webpack初体验\ndate: 2017/02/21 14:47:11\n---\n\n## 0、关于webpack\n\nWebpack是灵活的、可扩展的、开源的模块打包工具。[https://webpack.github.io/](https://webpack.github.io/)。\nWebpack获取模块和它们之间的依赖关系，然后将这些内容打包为静态资源。\n\n### 0.1、 Webpack有如下几大特点：\n\n1. 插件 --利用webpack提供的多功能插件接口，使得它可以被添加很多新特性，实现了webpack的高扩展性。\n2. 加载器（Loaders） -- webpack通过加载器来预处理文件，所以它不仅仅只能打包javascript。同时，使用node程序可以很容易的实现自己的加载器。\n3. 代码分割 -- webpack允许你将代码分块，按需加载，减少初始加载时间。\n4. 开发工具 --webpack支持SourceUrl和相关调试，同时可以通过开发中间件和开发服务器来实现自动化。\n5. 高性能 --通过异步IO和多级缓存支持，实现了webpack的高性能。\n6. 多支持 -webpack支持amd和commonjs风格的代码，并且还可以静态分析和拥有一个评价引擎来评估简单表达式。\n7. 资源优化 --webpack能够实现多层优化来减少输出大小，还能使用散列还关注请求缓存。\n8. 多目标 --webpack的主要目标是在Web上，但是它还支持WebWorks和node.js。\n\n### 0.2、为什么要webpack？(webpack形成的动机)\n\n1. 多种模块加载的方式，AMD、CommonJS、ES6 modules，各有优点，也各有缺陷。\n2. 传输方式的两个极端，每次请求一个模块和一个请求所有模块，都并不能达到很好的效果。\n3. 资源不仅仅是JavaScript，也有可能是字体、图片，多语言，模板等。\n\n## 1、Webpack功能\n\n### 1.1、加载器（Loaders）\n\nLoader是把资源文件进行转换的一种程序，使用资源文件做为参数，然后返回新的资源文件。比如，将CoffeeScript代码处理为JS代码。\n\n**Loader有哪些特点呢？**\n\n1. Loader可以链接，通过管道方式传输。\n2. Loader可以是同步或者是异步的。\n3. Loader使用nodeJs运行，而且能够做更多。\n4. Loader接受查询参数，可以用来传递配置。\n5. Loader可以在配置中绑定到扩展货正则表达式。\n6. Loader可以使用npm发布。\n7. 普通模块可以通过package.json来转换为loader。\n8. Loader可以访问配置。\n9. 插件可以为Loader添加更多特性。\n\nLoader采用``xxx-loader``这样的名字命令。``xxx``就是上下文的名称。在使用loader的时候，可以忽略``-loader``，只需要使用上下文名称就可以了。\n\n**有如下三种方式可以使用loader，**\n\n1. 通过require来使用\n\n```javascript\n  // 使用当前目录的loader.js处理指定的文件\n  require(\"./loader!./dir/file.txt\");\n  // 使用jade-loader来处理.jade模板文件\n  require(\"jade!./template.jade\");\n```\n\n2. 通过配置\n\n```javascript\n{\n  module: {\n    loaders: [\n      { test: /\\.jade$/, loader: \"jade\" },\n      // => 针对.jade文件使用jade-loader\n\n      { test: /\\.css$/, loader: \"style!css\" },\n      // => 针对.css文件使用style和css两种loader\n      // 另外的一种配置语法\n      { test: /\\.css$/, loaders: [\"style\", \"css\"] },\n    ]\n  }\n}\n```\n\n3. 通过cli\n\n```\nwebpack --module-bind jade --module-bind 'css=style!css'\n```\n\n**如何使用查询参数？**\n\n在require中\n\n```javascript\nrequire(\"url-loader?mimetype=image/png!./file.png\");\n```\n\n在配置中\n\n```javascript\n//方式一\n{ test: /\\.png$/, loader: \"url-loader?mimetype=image/png\" }\n\n//方式二\n{\n    test: /\\.png$/,\n    loader: \"url-loader\",\n    query: { mimetype: \"image/png\" }\n}\n```\n\n在CLI中\n\n```javascript\nwebpack --module-bind \"png=url-loader?mimetype=image/png\"\n```\n\n### 1.1、插件\n\n使用插件一般都涉及到webpack的打包功能，比如使用[BellOnBundlerErrorPlugin](https://github.com/senotrusov/bell-on-bundler-error-plugin)，来提示在打包过程的错误。\n\nwebpack包含部分内置插件，可以在config中进行配置：\n\n```javascript\nvar webpack = require(\"webpack\");\n\nmodule.exports = {\n  plugins: [\n    new webpack.ResolverPlugin([\n        new webpack.ResolverPlugin.DirectoryDescriptionFilePlugin(\"bower.json\", [\"main\"])\n    ], [\"normal\", \"loader\"])\n  ]\n};\n```\n\n也可以通过以下代码使用外部插件：\n\n```javascript\nvar ComponentPlugin = require(\"component-webpack-plugin\");\nmodule.exports = {\n  plugins: [\n      new ComponentPlugin()\n  ]\n}\n```\n\n### 1.3 开发工具\n\n开发工具相关配置通过通过[Configuration](http://webpack.github.io/docs/configuration.html)进行查看。\n\n提供开发服务器的包：[webpack-dev-server](http://webpack.github.io/docs/webpack-dev-server.html)\n\n用于高级用户的中间件：[webpack-dev-middleware](http://webpack.github.io/docs/webpack-dev-middleware.html)\n\n## 2、使用Webpack\n\n我们一般采用配置文件加webpack.config的方式来使用webpack，那具体应该如何用呢？\n\n首先，通过 ``npm install webpack -g`` 和 ``npm install webpack --save-dev`` 全局和在项目中安装webpack。\n\n然后通过 ``npm install xxx`` 来安装webpack所需要的插件和加载器（Loader）\n\n接下来，就是编写webpack的配置文件：\n\n```javascript\nvar webpack = require('webpack'); //引入webpack\n\n//webpack配置\nmodule.exports = {\n  entry: {\n    app: 'index.js'\n  },\n  output: {\n    path: './dist', //输出目录\n    filename: '[name][hash]bundle.js' //输出文件名\n  },\n  resolve: {\n    root: __dirname\n  },\n  module: {\n    noParse: [],\n    loaders: [ //针对不同的文件，采用不同的加载器来处理\n      { test: /\\.js$/, exclude: /node_modules/, loader: 'babel'}, //js文件除开node_modules,通过babel来处理\n      { test: /\\.html$/, loader: 'raw' }, //html文件通过raw-loader处理\n      { test: /\\.css$/, loader: 'style!css'} //css文件通过style-loader和css-loader来处理\n    ]\n  },\n  plugins: [\n    //Banner插件，合并时增加注释\n    new webpack.BannerPlugin('Hello' + new Date())\n  ]\n};\n```\n最后，执行webpack，就能够启动了，在执行webpack的时候，可以附加很多参数，这些参数可以通过 ``webpack -h`` 来查看\n\n**当前只能简单使用，等理解了webpack的核心思想，再写一篇《webpack深度解析》，敬请期待...**\n\n## 3、参考文档\n\n1. [Webpack官方文档](http://webpack.github.io/docs/)\n2. [在一个真实的项目中使用Webpack](http://blog.madewithlove.be/post/webpack-your-bags/)"
  },
  {
    "path": "前端相关/Webpack小抄.md",
    "content": "---\ntitle: Webpack小抄\ndate: 2017/02/21 14:47:11\n---\n\n## 0、前言\n\n此文用于记载在使用webpack打包过程中的点点滴滴！（仅适用于 `webpack1.x`）\n\n## 1、动态指定loader\n\n``webpack`` 的config文件中的loader，一般针对特定的后缀文件，当我们要对单个文件进行独立配置的时候，就不太好满足需求了，此时我们就需要在 ``import`` 或者 ``require`` 中指定文件的loader。\n\n指定loader的时候，有如下几种场景：\n\n1. 需要使用自定义loader，此时的import应该按照如下方式编写：``require('./loader!./xxx.js)`` ，使用当前目录下的 ``loader.js`` 来处理 ``xxx.js`` 。\n2. 需要在全局loader（这里指在config中配置的loader）之前做一些处理（插入loader），那我们可以如下处理：``import 'jade!./template.jade'``，使用 ``jade-loader`` 来处理template.jade文件。\n3. 需要覆盖全局的loader，可以如下使用：``import '!style!css!less!./test.less'``，使用 ``less-loader`` ``css-loader`` ``style-loader`` 来处理test.less文件。\n\n**注意1：插入loader与覆盖loader，差别在于第一个字符是不是!(感叹号)。**\n\n**注意2：当指定loader的时候，需要记住loader是从右往左依次执行的，一定要注意顺序。**\n\n## 2、打包项目为类库\n\n当我们要把项目打包为类库的时候，我们需要在entry中进行如下配置：\n\n```json\n{\n  entry: {\n    libraryTarget: 'umd', //可选var：直接全局变量，amd: AMD风格的库，cmd: CMD风格的库\n    library: 'xxx' //挂载到window上的名称\n  }\n}\n```\n\n## 3、webpack-dev-server部署IP访问\n\n使用默认的选项，只能使用 ``localhost`` 访问，如果想通过 ``IP`` 访问，那么需要设定配置如下：\n\n```javascript\ndevServer: {\n  contentBase: './',\n  port: 7410,\n  host: '0.0.0.0' // 设定host属性，绑定到 0.0.0.0，则可允许IP访问\n}\n```\n\n## 4、Webpack使用外部依赖\n\n```javascript\nexternals: {\n  '@angular/common' : 'ng.common',\n  '@angular/compiler' : 'ng.compiler',\n  '@angular/core' : 'ng.core',\n  '@angular/http' : 'ng.http',\n  '@angular/platform-browser' : 'ng.platformBrowser',\n  '@angular/platform-browser-dynamic' : 'ng.platformBrowserDynamic',\n  '@angular/router' : 'ng.router',\n  '@angular/forms' : 'ng.forms',\n  'rxjs' : 'Rx'\n}\n```\n\n## 5、Webpack多入口点，打包为library\n\n```javascript\noutput: {\n  path: './dist',\n  publicPath: 'http://10.16.85.170:9999/',\n  filename: 'newkit.[name].js',\n  library: ['newkit', '[name]'],\n  liabraryTarget: 'umd'\n},\n```\n\n## 6、和gulp配合使用\n\n尽量使用 `const webpack = require('webpack')` 。\n\n用法如下：\n\n```javascript\nconst showWebpackError = (err, stats) => {\n  if (err) {\n    throw new gutil.PluginError('webpack', err);\n  }\n  let statColor = stats.compilation.warnings.length < 1 ? 'green' : 'yellow';\n  if (stats.compilation.warnings.length > 0) {\n    stats.compilation.errors.forEach(error => {\n      statColor = 'red';\n    });\n  } else {\n    gutil.log(stats.toString({\n      colors: gutil.colors.supportsColor,\n      hash: false,\n      timings: true,\n      chunks: true,\n      chunkModules: false,\n      modules: false,\n      children: false,\n      version: true,\n      cached: true,\n      cachedAssets: true,\n      reasons: false,\n      source: false,\n      errorDetails: false\n    }));\n  }\n};\n\nwebpack(opt).watch(200, (err, stats) => {\n\tshowWebpackError(err, stats);\n});\n```"
  },
  {
    "path": "前端相关/Web前端基础测试题.md",
    "content": "---\ntitle: Web前端基础测试题\ndate: 2017/02/21 14:47:11\n---\n\n## 1、HTML篇\n\n#### 1、HTML是一种（）。\n\nA、标记语言  \n\nB、编程语言  \n\nC、自然语言  \n\nD、描述语言\n#### 2、HTML的后缀名是（）。\n\nA、 .h \n\nB、.ht  \n\nC、.htm  \n\nD、.html\n\n#### 3、HTML5新增的元素有（）。\n\nA、 p\n\nB、 ruby\n\nC、 canvas\n\nD、 span\n\n#### 4、HTML5中，使用（）可以播放视频？\n\nA、 audio\n\nB、 mark\n\nC、 video\n\nD、 source\n\n## 2、CSS篇\n\n####5、以下html代码中，用（）可设置Home的颜色的元素。\n\n\t<div class=\"container\">\n\t  <p class=\"about\">\n\t    <a href=\"#\" id=\"home-link\">Home</a>\n\t  </p>\n\t</div>\n\nA、 #home-link{}\n\nB、 .container .about{}\n\nC、 .container > a{}\n\nD、 .about a{}\n\n#### 6、要对Me设置样式，应该使用（）。\n\n\t<div>\n\t\t<span id=\"id\"></span>\n\t\t<span>Me</span>\n\t</div>\n\nA、 #id > span{}\n\nB、 #id + span{}\n\nC、 #id span{}\n\nD、 #id ~ span{}\n\n#### 7、如果对文本的第一个字符进行设置样式？()\n\nA、 :after{}\n\nB、 :before{}\n\nC、 :first-letter{}\n\nD、 :first-line{}\n\n#### 8、在CSS的border-box盒子模型中，border-width: 2px; width: 100px; padding: 5px; margin: 1px;那么，该元素占用的宽度为（）\n\nA、 100px\n\nB、 102px\n\nC、 108px\n\nD、 116px\n\n#### 9、如何对span设置width？（）\n\nA、 span{width: 100px; display: inline-block;}\n\nB、 span{width: 100px; display: inline;}\n\nC、 span{width: 100px; display: block;}\n\nD、 span{width: 100px;}\n\n#### 10、当position设置为（）时，可以让元素钉在浏览器窗口上，不随滚动条滚动而滚动。\n\nA、 absolute\n\nB、 static\n\nC、 relative\n\nD、 fixed\n\n#### 11、以下哪些是CSS的预处理器（）\n\nA、 Less\n\nB、 Sass\n\nC、 Stylus\n\nD、 Dart\n\n## 3、JavaScript篇\n\n#### 12、以下变量定义不正确的是（）。\n\nA、 var a, var b;\n\nB、 var a = b = 1;\n\nC、 var a, b, c;\n\nD、 var class = 1;\n\n#### 13、假定今天是2015年11月1日，如下代码的输出结果是（）\n\n\tnew Date().getMonth()\n\nA、 111\n\nB、 10\n\nC、 11\n\nD、 2015\n\n#### 14、当表单填写完毕，鼠标单击提交按钮时，触发的是（）事件\n\nA、onblur\n\nB、onmouseleave\n\nC、onmouseenter\n\nD、onsubmit\n\n#### 15、以下哪些是JavaScript的中间语言？\n\nA、 TypeScript\n\nB、 CoffeeScript\n\nC、 JScript\n\nD、 ActionScript\n\n#### 16、typeof undefined 输出（）\n\nA、 \"undefined\"\n\nB、 \"object\"\n\nC、 \"null\"\n\nD、 Error\n\n#### 17、 typeof Array.prototype 输出 （）\n\nA、 \"undefined\"\n\nB、 \"object\"\n\nC、 \"null\"\n\nD、 Error\n\n#### 18、[undefined == null, NaN == NaN] 输出（）\n\nA、 [true, true]\n\nB、 [false false]\n\nC、 [true, false]\n\nD、 [false, true]\n\n#### 19、以下代码输出（）\n\t\n\t(function() {\n\t  var a = b = 5;\n\t})();\n\tconsole.log(b);\n\nA、 Error\n\nB、 null\n\nC、 undefined\n\nD、 5\n\n#### 20、以下代码输出（）\n\n\tfunction test() {\n\t  console.log(a);\n\t  console.log(foo());\n\t  var a = 1;\n\t  function foo() {\n\t    return 2;\n\t  }\n\t} \n\ttest();\n\nA、 1 和 undefined\n\nB、 1 和 2\n\nC、 undefined 和 2\n\nD、 undefined 和 undefined\n\n## 4、编程篇\n\n#### 21、请补全代码\n\n\tfunction sortArr(arr){\n\t  //Your code here... \n\t}\n\tconsole.log(sortArr([1, 3, 0, 9, 7])); // Result: [9, 7, 3, 1, 0]\n\n#### 22、请补全代码\n\n\tfunction distinctArr(arr){\n\t  //Your code here... \n\t}\n\tconsole.log(distinctArr([1, 5, 7, 3, 7, 5])); // Result: [1, 5, 7, 3]\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n附参考答案：\n\nA\nCD\nBC\nC\nABD\nBD\nC\nB\nAC\nD\nABC\nAD\nB\nD\nAB\nA\nB\nC\nD\nC\n\n21、\nfunction sortArr(arr){\n  var arrCopy = arr.slice(0);\n  // 使用sort时，需要先复制一次arr，避免修改原本的数组。\n  arrCopy.sort(function(a1, a2){\n    return a2 - a1;\n  });\n  return arrCopy;\n}\n\n22、\nfunction distinctArr(arr){\n  var resultArr = [];\n  arr.forEach(function(item, i){\n    if(resultArr.indexOf(item) === -1){\n      resultArr.push(item);\n    }\n  });\n  return resultArr;\n}\n\nfunction distinctArr(arr){\n  var tempArr = arr.filter(function(item, index, inputArray){\n      return inputArray.indexOf(item) === index;\n   });\n  return tempArr;\n}"
  },
  {
    "path": "前端相关/Yarn vs. Npm.md",
    "content": "---\ntitle: Yarn vs. Npm\ndate: 2017/02/21 14:47:11\n---\n\n# 0、什么是 ``Yarn`` ?\n\n官方解释是：**FAST, RELIABLE, AND SECURE DEPENDENCY MANAGEMENT（快速，可靠和安全的依赖管理器）**。\n\n它是 ``facebook`` 开源的一个 ``npm`` 替代品。我们主要用它来进行依赖管理工作。\n\n# 1、为什么需要 ``Yarn`` ?\n\n对于 ``Yarn``，其实一开始我是拒绝的。\n\n为啥？\n\n1. 发布第二天时我就去体验，结果发现安装它自身比较麻烦，而且安装依赖还慢，还非常容易崩溃。\n2. 命令和 ``npm`` 不一样，增加了学习成本\n3. ``yarn`` 安装依赖进度条在windows上不友好（是npm之前旧版本的样式）\n4. 觉得赶超 ``npm`` 还需要一定的时间\n\n那为什么又开始推荐 ``yarn`` 呢？\n\n1. ``npm`` 安装依赖慢，是个长久的问题\n2. 再次体验 ``yarn``, 发现之前遇到的一些问题都不见了\n3. 使用 ``yarn`` 安装依赖，速度有很大的提升\n4、``yarn`` 比 ``npm`` 提供更多更易用的功能 \n\n综上，我觉得现在可以开始尝试使用 ``yarn``，说不定过不了多久，还真能完全替代 ``npm``。\n\n# 2、``yarn-cli`` 使用手册（对比npm）\n\n首先，要使用 ``yarn-cli``，需要先执行 ``npm install -g yarn`` 来全局安装 ``yarn``（莫名的想到，IE浏览器最大的作用就是下载其他浏览器），通过 ``yarn --version`` 来查看 ``yarn`` 的版本。\n\n如 ``npm`` 一样，``yarn`` 也提供了较多的命令。其中最常用的命令如下：\n\n1. yarn init ($ npm init)\n2. yarn install ($ npm install) \n3. yarn add ($ npm install --save)\n4. yarn remove ($ npm uninstall --save) \n5. yarn publish ($ npm publish)\n\n**注意：下文中仅仅会列出本人觉得比较有代表性/常用的命令，并不是所有的命令**\n\n### 2.1、``yarn init``\n\n``yarn init`` 用于初始化 ``package.json`` 文件，效果和 ``npm init`` 类似。\n\n同样也支持直接创建，不询问。\n\n```bash\n# 初始化package.json，带交互\nnpm init  =>  yarn init\n\n# 初始化package.json，直接创建\nnpm init -f/--force  =>  yarn init -y/--yes\n```\n\n### 2.2、``yarn install``\n\n``yarn install`` 该命令和 ``npm install `` 一样，用于根据 ``package.json`` 来初始化项目依赖。\n\n和 ``npm`` 对比如下：\n\n```bash\n# 安装所有依赖\nnpm install => yarn install\n\n# 安装项目依赖（对比开发依赖，上产线时的依赖）\nnpm install --production => yarn install --production/--prod \n```\n\n除此之外，``yarn install`` 还有一些新的参数设置，正是这些设置，让 ``yarn`` 比 ``npm`` 有着更强大的功能。\n\n```bash\n# 强制安装所有的包，就算是已经安装过的\nyarn install --force\n\n# 不会读取和生成lockfile\nyarn install --no-lockfile\n\n# 不生成lockfile\nyarn install --pure-lockfile\n```\n\n### 2.3、``yarn add``\n\n``yarn add <package>`` 等同于 ``npm install <package>``，用于安装指定包，它和 ``npm`` 的区别如下：\n\n```bash\n# 仅安装依赖包\nnpm install <package> --save  =>  N/A\n\n# 安装依赖包，并写入 dependencies 属性\nnpm install <package> --save  =>  yarn add <package>  \n\n# 安装依赖包，并写入 devDependencies 属性\nnpm install <package> --save-dev  =>  yarn add <package> --dev\n\n# 安装依赖包，并写入 peerDependencies 属性\nN/A  =>  yarn add <package> --peer\n\n# 安装依赖包，并写入 optionalDependencies 属性\nnpm install <package> --save-optional  =>  yarn add <package> --optional\n\n# 安装精确的版本号的依赖包\n# 什么意思呢？简单点说，就是在 package.json中写入的版本号规则不一样。\n# 默认场景下，是会写： \"xxx\":\"^1.1.0\"，允许使用同一主版本的包，如1.2.0\n# 当设置该参数之后，就会变成： \"xxx\": \"1.1.0\"，（只能使用该版本号的包）\nnpm install <package>[@version] --save-exact  => yarn add <package>[@version] --exact\n\n# 安装具有相同次版本号的依赖包\nN/A  =>  yarn add <package>[@version] --tilde\n```\n\n除此之外，还有一个比较常见的安装包方式，那就是全局安装安装，在 ``yarn`` 中又该如何使用呢？\n\n```bash\n# 全局安装依赖包\nnpm install <package> -g/--global  => yarn global add <package>\n```\n\n### 2.4、``yarn remove``\n\n该命令和 ``npm uninstall`` 比较类似，用于删除依赖包。但和 ``npm uninstall`` 又有点不同。\n\n因为它没法直接删除包，而不更新 ``package.json``。\n\n当 ``remove`` 一个包时，它会同时更新 ``package.json`` 中对它的所有引用。\n\n```bash\nyarn remove <package> = npm uninstall <package> --save | --save-dev | save-exact | save-optional\n```\n\n和 ``yarn add`` 类似，如果要移除全局安装包，需要用 ``yarn global remove <package>``\n\n### 2.5、``yarn publish``\n\n用于将包发布到仓库（当前是npm仓库）上。类似于 ``npm publish``\n\n# 3、其他重要的命令\n\n### 3.1、``yarn run``\n\n该命令和 ``npm run`` 没啥区别，用户执行 ``package.json`` 中 ``script`` 节点中定义的命令。\n\n### 3.2、``yarn self-update``\n\n用于更新自身，如果是 ``npm``，则是： ``npm install -g npm``\n\n### 3.3、``yarn outdated``\n\n检查依赖版本情况，类似 ``npm outdated``\n\n### 3.4、``yarn upgrade``\n\n用于更新版本，类似于 ``npm update``\n\n### 3.5、``yarn config``\n\n该命令用于管理 ``yarn`` 的配置数据。\n\n```bash\n# 查看配置数据\nnpm config list  =>  yarn config list \n\n# 查看指定的key值\nnpm config get <key>  =>  yarn config get <key>\n\n# 删除指定的Key\nnpm config delete <key>  =>  yarn config delete <key>\n\n# 设置指定的key - value\nnpm config set <key> <value> => yarn config set <key> <value>\n```\n\n**由于国内的环境，强烈建议将 ``registry``设置为： ``https://registry.npm.taobao.org/``，命令如下：**\n\n```bash\nyarn config set registry https://registry.npm.taobao.org/ -g\n```\n\n# 4、参考资料\n\n1. [yarn 文档](https://yarnpkg.com/en/docs/cli/)\n2. [npm 文档](https://docs.npmjs.com/cli/)"
  },
  {
    "path": "前端相关/[20140311]前端构建之gulp与常用插件.md",
    "content": "---\ntitle: 前端构建之gulp与常用插件\ndate: 2015/3/14 13:56:32\n---\n\n##gulp是什么？\n\n[http://gulpjs.com/](http://gulpjs.com/) 相信你会明白的！\n\n与著名的构建工具grunt相比，有什么优势呢？\n\n1. 易于使用，代码优于配置\n2. 高效，不会产生过多的中间文件，减少I/O压力\n3. 易于学习，API非常少，你能在很短的事件内学会gulp\n\n\n##那些常用的gulp插件\n\n###No.1、run-sequence\n\n**Links**: [https://www.npmjs.com/package/run-sequence](https://www.npmjs.com/package/run-sequence)\n\n**作用**：让gulp任务，可以相互独立，解除任务间的依赖，增强task复用\n\n**推荐指数**：★★★★★\n\n###No.2、browser-sync\n\n**Links**: [http://www.browsersync.io/](http://www.browsersync.io/)\n\n**作用**：静态文件服务器，同时也支持浏览器自动刷新\n\n**推荐指数**：★★★★★\n\n###No.3、del\n\n**Links**：https://www.npmjs.com/package/del\n\n**作用**：删除文件/文件夹\n\n**推荐指数**：★★★★★\n\n###No.4、gulp-coffee\n\n**Links**: [https://github.com/wearefractal/gulp-coffee](https://github.com/wearefractal/gulp-coffee)\n\n**作用**：编译coffee代码为Js代码，使用coffeescript必备\n\n**推荐指数**：★★★★\n\n###No.5、coffee-script\n\n**Links**: [https://www.npmjs.com/package/coffee-script](https://www.npmjs.com/package/coffee-script)\n\n**作用**：gulpfile默认采用js后缀，如果要使用gulpfile.coffee来编写，那么需要此模块\n\n**推荐指数**：★★★\n\n###No.6、gulp-nodemon\n\n**Links**: [https://www.npmjs.com/package/gulp-nodemon](https://www.npmjs.com/package/gulp-nodemon)\n\n**作用**：自动启动/重启你的node程序，开发node服务端程序必备\n\n**推荐指数**：★★★★★\n\n###No.7、yargs\n\n**Links**: [https://www.npmjs.com/package/yargs](https://www.npmjs.com/package/yargs)\n\n**作用**：用于获取启动参数，针对不同参数，切换任务执行过程时需要\n\n**推荐指数**：★★★\n\n###No.8、gulp-util\n\n**Links**: [https://www.npmjs.com/package/gulp-util](https://www.npmjs.com/package/gulp-util)\n\n**作用**：gulp常用的工具库\n\n**推荐指数**：★★★★★\n\n###No.9、gulp-uglify\n\n**Links**: [https://www.npmjs.com/package/gulp-uglify](https://www.npmjs.com/package/gulp-uglify)\n\n**作用**：通过UglifyJS来压缩JS文件\n\n**推荐指数**：★★★★\n\n###No.9、gulp-concat\n\n**Links**: [https://www.npmjs.com/package/gulp-concat](https://www.npmjs.com/package/gulp-concat)\n\n**作用**：合并JS\n\n**推荐指数**：★★★★\n\n###No.10、gulp-sourcemaps\n\n**Links**: [https://www.npmjs.com/package/gulp-sourcemaps](https://www.npmjs.com/package/gulp-sourcemaps)\n\n**作用**：处理JS时，生成SourceMap\n\n**推荐指数**：★★★★\n\n###No.11、gulp-less\n\n**Links**：[https://www.npmjs.com/package/gulp-less](https://www.npmjs.com/package/gulp-less)\n\n**作用**：将less预处理为css\n\n**推荐指数**：★★★★\n\n###No.12、gulp-sass\n\n**Links**：[https://www.npmjs.com/package/gulp-sass](https://www.npmjs.com/package/gulp-sass)\n\n**作用**：将sass预处理为css\n\n**推荐指数**：★★★★\n\n###No.13、gulp-autoprefixer\n\n**Links**：[https://www.npmjs.com/package/gulp-autoprefixer](https://www.npmjs.com/package/gulp-autoprefixer)\n\n**作用**：使用Autoprefixer来补全浏览器兼容的css。\n\n**推荐指数**：★★★★\n\n###No.14、gulp-minify-css\n\n**Links**：[https://www.npmjs.com/package/gulp-minify-css](https://www.npmjs.com/package/gulp-minify-css)\n\n**作用**：压缩css。\n\n**推荐指数**：★★★★\n\n###No.15、connect-history-api-fallback\n\n**Links**：[https://www.npmjs.com/package/connect-history-api-fallback](https://www.npmjs.com/package/connect-history-api-fallback)\n\n**作用**：开发angular应用必须，用于支持HTML5 history API.\n\n**推荐指数**：★★★\n\n##一般的gulpfile文件（采用coffee编写）\n\n首先是，node应用程序：\n\n\tgulp = require('gulp')\n\trunSequence = require('run-sequence')\n\t\n\tcoffee = require('gulp-coffee')\n\tgutil = require('gulp-util')\n\tdel = require('del')\n\tnodemon = require('gulp-nodemon')\n\targv = require('yargs').argv\n\trename = require('gulp-rename')\n\tbrowserSync = require('browser-sync')\n\treload = browserSync.reload\n\t\n\t# 处理参数\n\tisDebug = not (argv.r || false)\n\t\n\t# --入口任务-----------------------------------------------------------------\n\tgulp.task('default', (callback)->\n\t  runSequence(\n\t    ['clean']\n\t    ['coffee-server', 'copy-server', 'copy-client', 'coffee-client', 'copy-views']\n\t    'serve'\n\t    ['browserSync', 'watch']\n\t    callback\n\t  )\n\t)\n\t# --构建相关任务---------------------------------------\n\tgulp.task('clean', (callback)->\n\t  del(['./dist/'], callback)\n\t)\n\t\n\tgulp.task('coffee-server', ->\n\t  gulp.src([\n\t    './src/**/*.coffee'\n\t    '!./src/public/**/*.coffee'\n\t    '!./src/views/**'\n\t  ])\n\t  .pipe(coffee({bare: true}).on('error', gutil.log))\n\t  .pipe(gulp.dest('./dist/'))\n\t)\n\t\n\tgulp.task('copy-server', ->\n\t  gulp.src([\n\t    './src/config*/*.json'\n\t    './src/database*/*.*'\n\t  ])\n\t  .pipe(gulp.dest('./dist/'))\n\t)\n\t\n\tgulp.task('copy-client', ->\n\t  gulp.src([\n\t    './src/public*/**/*'\n\t    '!./src/public*/**/*.coffee'\n\t  ])\n\t  .pipe(gulp.dest('./dist/'))\n\t)\n\t\n\tgulp.task('coffee-client', ->\n\t  gulp.src([\n\t    './src/public*/**/*.coffee'\n\t  ])\n\t  .pipe(coffee({bare: true}).on('error', gutil.log))\n\t  .pipe(gulp.dest('./dist/'))\n\t)\n\t\n\tgulp.task('copy-views', ->\n\t  gulp.src('./src/views/**/*.html')\n\t  .pipe(rename({extname: '.vash'}))\n\t  .pipe(gulp.dest('./dist/views'))\n\t)\n\t\n\t\n\t# --启动程序,打开浏览器任务----------------------------------------------------\n\tnodemon_instance = undefined\n\tgulp.task('serve', (callback)->\n\t  called = false\n\t  if not nodemon_instance\n\t    nodemon_instance = nodemon({\n\t      script: './dist/index.js'\n\t      ext: 'none'\n\t    })\n\t    .on('restart', ->\n\t      console.log('restart server......................')\n\t    )\n\t    .on('start', ->\n\t      if not called\n\t        called = true\n\t        callback()\n\t    )\n\t  else\n\t    nodemon_instance.emit(\"restart\")\n\t    callback()\n\t  nodemon_instance\n\t)\n\t\n\tgulp.task('browserSync', ->\n\t  browserSync({\n\t    proxy: 'localhost:3000'\n\t    port: 8888\n\t  #files: ['./src/public/**/*']\n\t    open: true\n\t    notify: true\n\t    reloadDelay: 500 # 延迟刷新\n\t  })\n\t)\n\t\n\t\n\t\n\t# --监视任务------------------------------------------------\n\tgulp.task('watch', ->\n\t  gulp.watch([\n\t    './src/**/*.*'\n\t    '!./src/**/*.coffee'\n\t  ], ['reload-client'])\n\t  gulp.watch('./src/**/*.coffee', ['reload-server'])\n\t)\n\t\n\tgulp.task('reload-client', (callback) ->\n\t  runSequence(\n\t    ['copy-client', 'coffee-client', 'copy-views']\n\t    'bs-reload'\n\t    callback\n\t  )\n\t)\n\t\n\tgulp.task('reload-server', (callback) ->\n\t  runSequence(\n\t    ['copy-server', 'coffee-server']\n\t    'serve'\n\t    'bs-reload'\n\t    callback\n\t  )\n\t)\n\t\n\tgulp.task('bs-reload', ->\n\t  browserSync.reload()\n\t)\n\n接下来是前端网站：\n\n\tgulp = require('gulp')\n\tgutil = require('gulp-util')\n\tcoffee = require('gulp-coffee')\n\tdel = require('del')\n\trunSequence = require('run-sequence')\n\tbrowserSync = require('browser-sync')\n\thistoryApiFallback = require('connect-history-api-fallback')\n\t# 入口点\n\tgulp.task('default', ->\n\t  runSequence(\n\t    ['clean']\n\t    ['copy']\n\t    ['serve']\n\t  )\n\t)\n\t\n\tgulp.task('copy', ->\n\t  gulp.src([\n\t    './src/**/*.*'\n\t    '!./src/**/*.coffee'\n\t    '!./src/**/*.less'\n\t  ])\n\t  .pipe(gulp.dest('./dist'))\n\t)\n\t\n\tgulp.task('clean', (callback)->\n\t  del(['./dist/'], callback)\n\t)\n\t\n\tgulp.task('serve', ->\n\t  browserSync({\n\t    server: {\n\t      baseDir: \"./dist\"\n\t      middleware: [historyApiFallback]\n\t    }\n\t    port: 2222\n\t  })\n\t)\n\t\n\tgulp.task('watch', ->\n\t  # do something...\n\t)"
  },
  {
    "path": "前端相关/[20141025]从0开始Grunt.md",
    "content": "---\ntitle: 从0开始Grunt\ndate: 2014/10/25 00:00:00\n---\n\n## 首先，Grunt是什么？\n\nGrunt是JavaScript任务运行工具。使用它可以自动化诸如文件(夹)操作、代码压缩、代码编译、单元测试、代码规范校验等等重复的任务。\n\n## 如何安装Grunt？（Windows）\n\n**Step1**、Grunt依赖Node扩展包，那么必须要安装Node：\n\n1. 打开Node官网：[http://nodejs.org/](http://nodejs.org/)\n2. 点击 **INSTALL** 按钮，会自动适配环境，下载一个安装包，双击安装即可\n3. 打开cmd命令行，输入命令代码：<i> node -v </i> ，如果输出一个具体的版本号，如 <i>v0.10.xx </i>，则表示安装成功。\n\n**Step2**、安装grunt的命令行工具：\n1. 打开cmd命令行，输入命令代码：<i>npm install -g grunt-cli</i> ,该命令表示全局安装grunt的命令行\n\n到此，Grunt算是安装完成。\n\n## 如何使用？（以一个SPA为例）\n\n首先，创建该项目SPADemo，目录结构如下：\n\n\tSPADemo\n\t\tsrc\n\t\t\timages     //图片文件夹\n\t\t\tstyles     //样式表文件夹\n\t\t\tscripts    //脚本文件夹\n\t\t\t\tvendor //存放第三方组件\n\t\t\tindex.html //默认页\n\n第二步，书写package.json 文件，确定依赖项。放在src同级目录,内容如下：\n\t\n\t{\n\t  \"name\": \"SPADemo\",\n\t  \"version\": \"0.1.0\",\n\t  \"devDependencies\": {\n\t    \"grunt\": \"~0.4.2\"\n\t  }\n\t}\n\n\n第三步，打开SPADemo根文件夹，在路径栏输入cmd，进入当前目录的cmd模式，输入 <i>npm i</i> ,初始化依赖项。当更该package.json后，可重复执行该命令。将依赖项重新初始化。会生成一个node_modules文件夹。\n\n第四步，进行Grunt配置。默认配置文件名为Gruntfile.js（.coffee也可以，但是Gruntfile不能更改，同时必须放在src同级目录）\n\n第五步，配置Gruntfile.js/coffee\n\t\n\tmodule.exports = function(grunt){\n\t\t//初始化Grunt\n\t\tgrunt.initConfig({});\n\t\n\t\t// registerTask(taskName,taskDescription,taskFunc/childTaskArray)\n\t\tgrunt.registerTask('default','任务入口',function(){\n\t\t\tgrunt.log.write('任务已启动！');\n\t\t});\n\t};\n\n到现在，一个Grunt已经完整配置好了。进入SPADemo目录的cmd命令行，输入grunt，会显示“任务已启动！”\n\n## 如何使用插件？\n\n### 文件清理插件 grunt-contrib-clean\n该插件用于清除目录/文件\n\n首先在package.json中添加该依赖包。变化后配置如下：\n\n\t{\n\t  \"name\": \"SPADemo\",\n\t  \"version\": \"0.1.0\",\n\t  \"devDependencies\": {\n\t    \"grunt\": \"~0.4.2\",\n\t    \"grunt-contrib-clean\": \"~0.6.0\"\n\t  }\n\t}\n\n然后在Gruntfile.js中增加该插件的配置，代码如下（更详细的配置请参考插件地址）：\n\n\tmodule.exports = function(grunt){\n\t\tgrunt.initConfig({\n\t\t\t//clean插件的配置项，名字不能变化。\n\t\t\t//插件地址：https://github.com/gruntjs/grunt-contrib-clean\n\t\t\tclean: {\n\t\t\t\t//任务具体配置，清除dist文件夹\n\t\t\t\tcleanDist: {\n\t\t\t\t\tforce: true, //允许操作当前工作目录之外的目录\n\t\t\t\t\tsrc: 'dist/**/*' //dist下的所有文件和目录\n\t\t\t\t}\n\t\t\t}\n\t\t});\n\t\n\t\tgrunt.loadNpmTasks('grunt-contrib-clean');// 必须，使用clean插件，必须要调用该方法，加载插件\n\t\n\t\t// registerTask(taskName,taskDescription,taskFunc/childTaskArray)\n\t\tgrunt.registerTask('default','任务入口',function(){\n\t\t\tgrunt.log.write('任务已启动！');\n\t\t\tgrunt.task.run([\n\t\t\t\t'clean:cleanDist' //使用clean任务的cleanDist配置运行任务\n\t\t\t]);\n\t\t});\n\t};\n\n最后cmd运行grunt命令，就可以执行对dist目录的清理了。可以手动新建dist文件夹测试。\n\n## 写在最后\n\nGrunt是一个庞大的生态系统，我们可以自由的选择数以百计的插件来帮助处理自动化任务。同时，如果发现没有合适的插件，我们还可以自己创建插件并通过npm发布，以供更多人使用和完善。"
  },
  {
    "path": "前端相关/[20150107]Web离线存储的几种方式.md",
    "content": "---\ntitle: Web离线存储的几种方式\ndate: 2015/01/07 00:00:00\n---\n\n随着HTML5的正式定稿，我们也可以大量使用HTML离线网络应用程序的特性。\n\n# #1、Application Cache\n[Application Cache](http://www.w3schools.com/html/html5_app_cache.asp) 可以很简单让我们的WebApp具有离线的能力。\n\n**支持的浏览器：**IE10+，FireFox，Chrome，Safari，Opera\n\n**优点：**\n\n1. 离线浏览 -- 用户可以再离线时使用Application\n2. 速度 -- 由于缓存了资源，如果加载很快\n3. 减少服务端数据加载 -- 浏览器只需要从服务器加载更新过的数据\n\n**缺点：**\n\n1. Manifest文件有变化时才更新\n2. 一次必须更新Manifest中的所有文件，下次才生效\n\n**如何使用？**\n\nStep1:在html上指定manifest文件 (index.html)\n\n\t<html manifest=\"appCacheList.manifest\">\n\t</html>\n\nStep2:设定manifest文件内容 (appCache.manifest)\n\n\tCACHE MANIFEST\n\n\t# 离线缓存的内容\n\t./all.css\n\t./1.jpg\n\t./index.js\n\t\n\t# NETWORK:*，表示其他内容从网络获取\n\tNETWORK:\n\t*\n\t\n\t# 第一个uri是资源，第二个是fallback\n\tFALLBACK:\n\t/html/ /offline.html\n\n手动更新缓存：\n\n\tif ( window.applicationCache.status == window.applicationCache.UPDATEREADY ){\n\t    window.applicationCache.update();\n\t}\n\n**注意：**\n\n1. 不同的浏览器对Application Cache的大小不一致，请注意。\n2. 更多细节可参考[http://kayosite.com/web-app-by-jquery-mobile-and-html5-offline-web-applications.html](http://kayosite.com/web-app-by-jquery-mobile-and-html5-offline-web-applications.html)\n\t\n\n# #2、Local Storage\n[Local Storage](http://www.w3schools.com/html/html5_webstorage.asp)使得我们可以在浏览器中保存数据。\n\n**支持的浏览器：**IE10+，FireFox，Chrome，Safari，Opera\n\n**优点：**\n\n1. 容量大\n2. 易用\n3. 强大\n4. 原生支持\n5. 仅存在本地，不会与服务器发生交互\n\n**缺点：**\n\n1. 浏览器兼容性差\n2. 安全性差（不要存储敏感数据）\n\n\n**如何使用？**\n\n首先通过 ``window.localStorage`` 来判断浏览器是否支持Local Storage。然后由于该方式具有浏览器兼容性，建议用一个通用的库，来屏蔽兼容性。\n\n\t// 对基本方法的封装，需要判断浏览器，屏蔽它们的细节差异。\n\t(function(window){\n\t  if(!window.localStorage){\n\t    throw new Error('Your brower can\\'t support local storage!');\n\t  }\n\t  var ls = window.localStorage;\n\t  var localStorageKit = {\n\t    getLength: function(){\n\t      return ls.length;\n\t    },\n\t    clear: function(){\n\t      ls.clear();\n\t      return true;\n\t    },\n\t    set: function(k, v){\n\t      ls.setItem(k, v);\n\t    },\n\t    get: function(k){\n\t      return ls.getItem(k);\n\t    },\n\t    remove: function(k){\n\t      ls.removeItem(k);\n\t    },\n\t    getKeyByIndex: function(index){\n\t      return ls.key(index);\n\t    }\n\t  };\n\t  window.lsKit = localStorageKit;\n\t})(window);\n\n基本操作方式与cookie无太多差异。\n\n**Session Storage：**\nSession Storage和Local Storage非常类似，操作方式也一致。由于其中保存的存只是当前会话有效，那么此处就不细说。\n\n# #3、Web SQL\n\n[Web Sql Database](http://en.wikipedia.org/wiki/Web_SQL_Database)，是html5环境下可以用js执行CRUD的web数据库。数据库核心是SQLite。\n\n**优点：**\n\n1. 本地数据库\n2. 可以处理复杂的关系型数据\n\n**缺点：**\n\n1. 暂时只有chrome才支持，对于Android大行其道的移动端，这应该是可以避免的缺点(貌似最新版本的Opera和Safari也支持了)\n\n**如何使用？**\n\n首先，先介绍Web sql的三个核心方法：\n\n1. openDatabase：这个方法使用现有数据库或创建新数据库创建数据库对象。\n2. transaction：这个方法允许我们根据情况控制事务提交或回滚。\n3. executeSql：这个方法用于执行真实的SQL查询。\n\n\tvar db = openDatabase('mydb', '1.0', 'Test DB', 2 * 1024 * 1024);\n\tvar msg;\n\tdb.transaction(function (tx) {\n\t  tx.executeSql('CREATE TABLE IF NOT EXISTS LOGS (id unique, log)');\n\t  tx.executeSql('INSERT INTO LOGS (id, log) VALUES (1, \"foobar\")');\n\t  tx.executeSql('INSERT INTO LOGS (id, log) VALUES (2, \"logmsg\")');\n\t  console.log('Log message created and row inserted.');\n\t});\n\tdb.transaction(function (tx) {\n\t  tx.executeSql('SELECT * FROM LOGS', [], function (tx, results) {\n\t    var len = results.rows.length, i;\n\t    console.log('Found rows: ' + len);\n\t    for (i = 0; i < len; i++){\n\t      console.log(results.rows.item(i).log)\n\t    }\n\t  }, null);\n\t});\n\n当成数据库用，就行。\n\n# #4、IndexedDB\n[IndexedDB](http://www.w3.org/TR/IndexedDB/)是结构化的本地数据存储。是基于平面文件的数据库，采用了分层的键值存储和基本的索引。\n\n**优点：**\n\n1. 标准化\n2. 存储复杂数据\n3. 支持索引\n\n**缺点：**\n\n1. 不支持SQL\n2. 相对来说，操作较复杂\n\n**如何使用？**\n\n\t// 打开数据库，第一个参数为数据库名，第二个为数据库版本号\n\tvar dbRequest = window.indexedDB.open('testDb', 2);\n\t\n\tdbRequest.onupgradeneeded=function(e){\n\t    // 创建数据仓库\n\t    var db=e.target.result;\n\t    if(!db.objectStoreNames.contains('users')){\n\t        var store=db.createObjectStore('users',{keyPath: 'id'});\n\t        store.createIndex('nameIndex','name',{unique:true}); \n\t        store.createIndex('ageIndex','age',{unique:false}); \n\t    }\n\t    console.log('upgrade successfully!');\n\t};\n\t\n\tdbRequest.onsuccess = function(e){\n\t  console.log('Open database successfully!');\n\t  // 这里拿到了数据库\n\t  var db = e.target.result;\n\t  var storeName = 'users';\n\t  // 写入数据\n\t  var tran = db.transaction(storeName, 'readwrite');\n\t  var users = tran.objectStore(storeName);\n\t  for(var i = 0; i < 5; i++){\n\t    users.add({\n\t      id: i,\n\t      name: 'user' + i,\n\t      age: Math.floor(Math.random() * 10) + 18\n\t    });\n\t  }\n\t\n\t  //查询数据\n\t  var userStore = db.transaction(storeName).objectStore(storeName);\n\t  var request = userStore.openCursor();\n\t  request.onsuccess = function(e){\n\t    var cursor = e.target.result;\n\t    if(cursor){\n\t      console.log(cursor.key);\n\t      console.log(cursor.value);\n\t      cursor.continue();\n\t    }\n\t  }\n\t}\n\n# 其他\n\n[HTML 5中几种用于在客户端本地存储数据的API之间的比较](http://html5online.com.cn/articles/2012080901.html)\n\n[HTML5本地存储——IndexedDB（一：基本使用）](http://www.cnblogs.com/dolphinX/p/3415761.html)\n\n[HTML5本地存储——IndexedDB（二：索引）](http://www.cnblogs.com/dolphinX/p/3416889.html)\n\n"
  },
  {
    "path": "前端相关/一个元素实现3个回图形.md",
    "content": "---\ntitle: 一个元素实现3个回图形\ndate: 2017/02/21 14:47:11\n---\n\n## 题目\n\n如何用一个html元素实现三个回子类似的图形？\n\n## 解题方案\n\n### 方案一，利用outline和outline-offset结合伪元素来实现。\n\n```css\n.test, .test::before, .test:after{\n  height: 100px;\n  width: 100px;\n  border: 1px solid blue;\n  outline: 1px solid red;\n  outline-offset: 10px;\n}\n.test::before, .test::after{\n  content: '';\n  display: block;\n}\n.test::before{\n  margin-left: 150px;  \n}\n.test::after{\n  margin-top: -100px;\n  margin-left: 300px;\n}\n```\n\n### 方案二，利用box-shadow来模拟边框实现\n\n```css\n.test{\n  position:relative;\n  border: 1px solid red;\n  height: 150px;\n  width: 120px;\n  box-shadow: 275px 0px 0 0px white, 275px 0px 0 1px red;;\n}\n.test::before{\n  content: '';\n  position: absolute;\n  display: block;\n  width: 100px;\n  height: 100px;\n  left: 10px;\n  top: 25px;\n  border: 1px solid blue;\n  box-shadow:135px 0 0px 0px white, 135px 0 0px 1px blue,  275px 0px 0 0px white, 275px 0px 0 1px blue;;\n}\n.test::after{\n  content: '';\n  display: block;\n  position:absolute;\n  width: 120px;\n  height: 150px;\n  left: 135px;\n  top: 0;\n  border: 1px solid red;\n}\n```"
  },
  {
    "path": "前端相关/再说Promise.md",
    "content": "---\ntitle: 再说Promise\ndate: 2017/02/21 14:47:11\n---\n\n## 0、导言\n\n在JavaScript，由于天生的回调机制，当业务逻辑嵌套较多的时候，就很容易产生回调地狱。\n\n为了避免回调地狱，在JS中可以使用 ``co``, ``Promise``, ``async await`` 等等方式。\n\n在当前ES6开始流行的情况下，``Promise`` 则是主流，就算是 ``async await`` ，也需要和Promise搭配，那我们就来看下 ``Promise`` 到底是怎么工作的！\n\n## 1、原生Promise\n\n打开Chrome控制台，输入 ``Promise.`` 然后可以看到Promise的一些静态方法如下：\n\n```javascript\nPromise.accept(); // 【非标准】等价于Promise.resolve()\nPromise.all(); // 同时执行多个Promise，所有Promise都执行完毕（resolve, reject都算）之后，才会调用then方法。\nPromise.defer(); // 【非标准】返回一个Deferred对象\nPromise.race(); // 通过执行多个Promise，有一个成功就继续下一步。\nPromise.reject(); // 返回一个rejected的Promise\nPromise.resolve(); // 返回一个resolved的Promise\n```\n\n通过 ``let p = Promise.defer().promise`` 之后，使用 ``p.``，可以看到 ``Promise`` 实例可用的一些方法（实际是原型方法）\n\n```javascript\ncatch(); // 是onRejected的语法糖，注册onRejected方法。\nchain(); // 【非标准】\nthen(); // 注册onFulfilled和onRejected方法。\n```\n\n至于具体如何用，看 [Promise Book](http://liubin.org/promises-book)。\n\n## 2、Promise原理浅析\n\n光用还不够，在这里我们来分解一下Promise背后的原理。\n\n要去分析Promise的原理，首先就不得不提到两个规范。一个是 Promise A+ 规范（ES6 Promise的前身，是一个社区规范）和 ECMA Promise规范（标准化之后的规范）。它们约定了Promise的规则。\n\n在这里，我们就不按部就班的来对应该问题，我们就通过TDD的方式来实现一个Promise-polyfills。\n\n测试工具采用了 [ava]()。\n\n为了避免浏览器干扰，我们把Promise取别名为Promise2。目录结构如下：\n\n```\nPromise2.js\ntest/\n  promise.test.js\n```\n\n我们先完成第一个测试，验证Promise应该有的静态方法和原型方法。\n\n```\n//promise.test.js\n\n'use strict';\n\nlet test = require('ava');\nlet Promise2 = require('./../Promise2');\n\n// Promise2 is a function. \ntest(t => t.is('function', typeof Promise2));\n\n// Promise2 has static function resolve\ntest(t => t.is('function', typeof Promise2.resolve));\n\n// Promise2 has static function reject\ntest(t => t.is('function', typeof Promise2.reject));\n\n// Promise2 has static function race\ntest(t => t.is('function', typeof Promise2.race));\n\n// Promise2 has static function all\ntest(t => t.is('function', typeof Promise2.all));\n\nlet p1 = new Promise2();\n\n// new Promise2 is a Promise2 instance\ntest(t => t.is(true, p1 instanceof Promise2));\n\n// Promise2 instace muse has function catch\ntest(t => t.is('function', typeof p1.catch));\n\n// Promise2 instace muse has function then\ntest(t => t.is('function',  typeof p1.then));\n```\n\n要让这些测试用例通过，编写了如下代码：\n\n```javascript\nclass Promise2 {\n  constructor() {\n\n  }\n\n  static all() {\n\n  }\n\n  static race() {\n\n  }\n\n  static reject() {\n\n  }\n\n  static resolve() {\n\n  }\n\n\n  then() {\n\n  }\n\n  catch() {\n\n  }\n}\n\nmodule.exports = Promise2;\n```\n\n至此，我们的一个程序基架已经ok了。接下来我们一步步的来继续完善测试和实现代码。\n\n接下来，我们看一下Promise的常规用法：\n\n```javascript\nlet p = new Promise((resolve, reject) => {\n  setTimeout(() => {\n    console.log('before resolve');\n    resolve();\n    console.log('after resolve');\n  }, 1000);\n});\n```\n\n## 参考资料\n\n1. [MDN Promise](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise)\n2. [Promise Book](http://liubin.org/promises-book)\n3. [Promise A+ 规范](https://promisesaplus.com/)\n4. [ECMA262 Promise](https://tc39.github.io/ecma262/#sec-promise-objects)"
  },
  {
    "path": "前端相关/前端模块化：RequireJS.md",
    "content": "---\ntitle: 前端模块化：RequireJS\ndate: 2017/02/21 14:47:11\n---\n\n## 前言\n\n前端模块化能解决什么问题？\n\n1. 模块的版本管理\n2. 提高可维护性 -- 通过模块化，可以让每个文件职责单一，非常有利于代码的维护\n3. 按需加载 -- 提高显示效率\n4. 更好的依赖处理 -- 传统的开发模式，如果B依赖A，那么必须在B文件前面先加载好A。如果使用了模块化，只需要在模块内部申明依赖即可。\n\n## AMD规范 & CMD规范\n\n说到前端模块化，就不得不提**AMD规范**（[中文版](https://github.com/amdjs/amdjs-api/wiki/AMD-(%E4%B8%AD%E6%96%87%E7%89%88))、[英文版](https://github.com/amdjs/amdjs-api/wiki/AMD)）和**CMD规范**（[英文版](https://github.com/cmdjs/specification/blob/master/draft/module.md)）\n\n它们的区别：\n\n[http://www.zhihu.com/question/20351507](http://www.zhihu.com/question/20351507)\n\n[http://www.cnblogs.com/tugenhua0707/p/3507957.html](http://www.cnblogs.com/tugenhua0707/p/3507957.html)\n\nAMD规范是 RequireJS 在推广过程中对模块定义的规范化产出，所以我在这里重点介绍下 AMD规范。\n\nAMD规范全名异步模块定义（Asynchronous Module Definition）规范，让模块和依赖可以异步加载。\n\n主要API：\n\n\tdefine(id?, dependencies?, factory);\n\nid,字符串，定义中模块的名字，可选参数（没有提供，则默认为模块加载器请求的指定脚本的名字），如果提供，那么模块名必须是顶级和绝对的（不允许相对名字）\n\ndependencies，数组，模块的依赖，可选参数\n\nfactory，函数或对象，必选参数。\n\n## RequireJS\n\nRequireJS是一个JS的文件和模块加载器。专门为浏览器优化，同时也支持其他JS环境。\n\n\n### 使用RequireJS \n\n要想使用requireJS，首先需要在页面引入脚本：\n\n\t<script src=\"assets/vendor/require/require.js\"></script>\n\n接下来，书写脚本：\n\n\t<script>\n        requirejs(['js/a'], function(a){\n            alert(a.test);\n        });  \n    </script>\n\n再来看看a.js:\n\n\tdefine({\n\t  test: 'aa'\n\t});\n\nhtml代码如下：\n\n\t<!DOCTYPE html>\n\t<html>\n\t    <head>\n\t        <title>RequireJS Demo</title>\n\t        <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n\t        <meta http-equiv=\"X-UA-Compatible\" content=\"chrome=1\">\n\t        <script src=\"assets/vendor/require/require.js\"></script>\n\t    </head>\n\t    <body>\n\t        <script>\n\t            requirejs(['assets/js/a'], function(a){\n\t                alert(a.test);\n\t            });  \n\t        </script>\n\t    </body>\n\t</html>\n\n在浏览器中打开后，弹出aa。\n\n在实践这个简单示例的时候，需要注意requireJS的``baseUrl`` 设置：\n\n**细节之处**\n\n1、RequireJS 加载文件/模块都是相对于baseUrl的\n\n2、baseUrl默认使用data-main属性指定的脚本目录\n\n\t//baseUrl=\"assets/\"\n\t<script data-main=\"assets/main\" src=\"assets/vendor/require/require.js\"></script>\n\n\t//baseUrl=\"assets/js/\"\n\t<script data-main=\"assets/js/main\" src=\"assets/vendor/require/require.js\"></script>\n\n3、如果没有data-main属性，那么baseUrl等于该html所在的目录\n\n4、baseUrl可以通过RequireJS config进行设置\n\n5、优先级：config > data-main属性 -> html目录\n\n6、RequireJS加载JS时，可以不写js后缀\n\n7、加载文件/模块时，会自动包含前缀http或者https\n\n8、可以在config中使用paths配置，指定相对目录\n\n\t//这是我的项目目录结构\n\twww/\n\t  index.html\n\t  assets/\n\t    js/\n\t      a.js\n\t    vendor/\n\t      require/\n\t        require.js\n\t    main.js\n\n\t//配置文件\n\trequirejs.config({\n\t\tbaseUrl: 'assets',\n\t\tpaths: {\n\t\t\tjs: 'js'\n\t\t}\n\t});\n\t\n\t//可以直接使用js来映射目录，进行文件引用\n\trequirejs(['js/a'], function(a){\n\t  alert(a.test);\n\t}); \n\n**注意：**一定要保证使用paths的时候，一定要放在config之后。通过在配置paths时，如果是直接子目录，不需要斜杠。\n\n9、data-main指定的文件，是requirejs的入口点\n\n### 定义模块\n\n在requirejs中，我们可以采用多种方式定义模块，如下：\n\n1、简单的键值对:\n\n\tdefine({\n\t  color: 'black',\n\t  size: '18px'\n\t});\n\n2、定义函数：\n\n\tdefine(function(){\n\t  return {\n\t    color: 'black',\n\t    size: '18px'\n\t  };\n\t});\n\n3、定义函数并使用依赖：\n\n\tdefine(['a', 'b'], function(a, b){\n\t  return {\n\t    color: 'black',\n\t    size: '18px',\n\t    alert: function(){\n\t      return a.num + b.num;\n\t    }\n\t  };\n\t});\n\n4、将函数定义为模块：\n\n\tdefine(['a', 'b'], function(a, b){\n\t  return function(title){\n\t    return title ? title : a.title + b.title;\n\t  };\n\t});\n\n5、通过简单的CommonJs包装器定义模块：\n\n\tdefine(function(require, exports, module){\n\t  var a = require('a'),\n\t      b = require('b');\n\t  return function(){}; \n\t});\n\n6、定义包含名字的模块：\n\n\tdefine('moduleA', ['a', 'b'], function(a, b){\n\t  //do something...\n\t});\n\n### 配置项\n\n\trequirejs.config({\n\t  baseUrl: '', //基地址\n\t  paths: { //路径映射\n\t    'js': '../js'\n\t  },\n\t  bundles: { //\n\t    primary: ['main', 'util'],\n\t    secondary: ['text!secondary.html']\n\t  },\n\t  shim: { //从非模块的js中，导出模块\n\t    jquery: {\n\t      deps: [],\n\t      exports: 'jQuery'\n\t    }\n\t  },\n\t  map: { //方便版本控制\n\t    'some/newmodule': {\n\t      'foo': 'foo1.2'\n\t    },\n\t    'some/oldmodule': {\n\t      'foo': 'foo1.0'\n\t    }\n\t  },\n\t  config: { //配置module\n\t    bar: {\n\t      size: 'large'\n\t    }\n\t  },\n\t  packages: [], //需要从CommonJS packages中加载的模块\n\t  nodeIdCompat: true,\n\t  waitSeconds: 15, // 加载脚本超时时间（秒）\n\t  context: '', //设置上下文名称\n\t  deps:[], //需要加载的依赖\n\t  callback: function(){}, //当deps加载完时执行\n\t  enforceDefine: false, // 是否当脚本没有define时抛出错误\n\t  xhtml: false, //是否使用xhtml创建脚本元素\n\t  urlArgs: 'test=' + (new Date()).getTime(), //配置url参数\n\t  scriptType: 'text/javascript', //设置加载脚本的脚本类型\n\t  skipDataMain: false //是否使用data-main属性\n\t});"
  },
  {
    "path": "前端相关/如何用Node编写命令行工具.md",
    "content": "---\ntitle: 如何用Node编写命令行工具\ndate: 2017/02/21 14:47:11\n---\n\n# 0、 命令行工具\n当全局安装模块之后，我们可以在控制台下执行指定的命令来运行操作，如果npm一样。我把这样的模块称之为命令行工具模块（如理解有偏颇，欢迎指正）\n\n# 1、用Node编写命令行工具\n在Node中，我们很容易就能实现一个命令行工具。通过借助npm install -g安装，就能直接调用命令行工具了。\n\n### 1.1、创建项目\n首先，命令行也是一个node程序，那么首先通过npm init初始化一个Node项目。\n\n```json\n// package.json\n{\n  \"name\": \"newkit-cli\",\n  \"version\": \"0.0.1\",\n  \"description\": \"Newkit Management Tools\",\n  \"main\": \"index.js\",\n  \"scripts\": {\n    \"test\": \"test\"\n  },\n  \"author\": \"Jay\",\n  \"license\": \"MIT\"\n}\n```\n\n### 1.2、创建可执行代码\n\n在项目目录下，创建src目录，并在其中创建index.js文件\n\n```javascript\n//src/index.js文件内容\nconsole.log('cli');\n```\n通过``node src/index``就可以执行到段代码了，那如何用自定义命令来执行呢？ \n\n### 1.3、在package.json中配置自定义命令\n\n在package.json中可以配置bin节点，当全局安装的时候，该节点内容将会被注册为自定义命令。\n```json\n{\n  \"name\": \"newkit-cli\",\n  \"version\": \"0.0.1\",\n  \"description\": \"Newkit Management Tools\",\n  \"main\": \"index.js\",\n  \"bin\": {\n    \"nc\": \"./src/index.js\"\n  },\n  \"scripts\": {\n    \"test\": \"test\"\n  },\n  \"author\": \"Jay\",\n  \"license\": \"MIT\"\n}\n```\n### 1.4、测试命令\n\n假设我们已经写好了命令行工具了，那我们应该如何测试呢？\n\n我们可以通过``npm install -g``将当前模块安装到全局模块中。然后再执行nc命令来测试。\n\n通过如上步骤，我们发现并不能执行我们的index.js，这是为什么呢？\n\n因为我们并没有指定用什么工具来执行这条命令，所以应该怎么做呢？打开index.js，然后加上一句代码：\n\n```javascript\n#!/usr/bin/env node \n\nconsole.log('cli');\n```\n这句代码什么意思呢？这句代码告诉系统，使用node来启动我们的命令。此时再安装，然后执行nc，你会发现，控制台会打印出cli。也就是我们index中代码的执行结果。\n\n至此，我们的一个最简单的命令行执行就开发成功了。\n\n# 2、处理命令行参数\n\n单纯的执行一个命令，似乎不满足我们的实际运用场景，大部分时候我们会使用``nc version``、``nc xxx -a --b``之类的方式来使用命令。那应该如何获取这些命令呢？\n\n### 2.1、使用process来获取控制台参数\n\n将index.js代码修改一下，如下：\n```javascript\n#!/usr/bin/env node \n\nconsole.log('cli');\nconsole.log(process.argv);\n```\n安装之后，再次执行``nc xxx -a --b true``,会看到如下的输出：\n\n```\ncli\n[ 'C:\\\\Program Files\\\\nodejs\\\\node.exe',\n  'C:\\\\Users\\\\jh3r\\\\AppData\\\\Roaming\\\\npm\\\\node_modules\\\\newkit-cli\\\\src\\\\index.js',\n  'xxx',\n  '-a',\n  '--b',\n  'true' ]\n```\n从结果可以看到，我们所使用所有参数都会传递到程序中去，这个时候，我们就可以解析这些参数，来实现不同的输出了。\n\n### 2.2、使用Commander来开发命令行工具\n\n从上面的输出也可以看到，我们要手动去解析参数的话，还是一个比较复杂的操作。既然身处Node社区，那么完全使用社区流行的包来帮我们简化代码。\n\nCommander 是一款重量轻，表现力和强大的命令行框架。提供了用户命令行输入和参数解析强大功能。\n\nCommander的方便之处在于：自记录代码、自动生成帮助、合并短参数（“ABC”==“-A-B-C”）、默认选项、强制选项​​、命令解析、提示符\n\n我们可以在[https://github.com/tj/commander.js/](https://github.com/tj/commander.js/)找到Commander。\n\n继续改造index.js文件，修改内容为：\n```javascript\n#!/usr/bin/env node \nvar program = require('commander');\n\nprogram\n  .version('0.0.2') //提供命令行工具的版本号，可以通过-V获取到\n  // 使用option方法注册命令\n  .option('-i, --init [type]', 'Initial Newkit in current folder', (type) => {\n    console.log('process', type, program.init);\n  }, true)\n  .option('-u| --update <module>', 'Update module.', (moduleName) => {\n    //使用program.update 来获取默认值，如果有命令行参数，那么会作为回调函数的参数\n    console.log(moduleName, program.update);\n  }, 'app-common')\n\n  .parse(process.argv);\n```\n**注意：以上代码有较多注意的点**\n\n1. option方法参数是四个，第一个是命令，第二个是描述，第三个是回调，第四个是命令的默认值\n2. 第一个参数中的-i和-u是短命令，--init和--update是长命令。长短命令之间的分隔符可以是``|``和``,``，如果使用逗号分隔，那么可以通过program.init来获取默认值。\n3. 在代码中我们在命令中，注意到有``[type]``和``<module>``两种，前者是可选参数，后者的必选参数。\n\n除此之外，还可以使用command方法来实现Git风格的子命令，代码如下：\n\n```javascript\nprogram\n  .command('update <module>')\n  .action((module, options) => {\n    console.log(module);\n  });\n```\n*更多功能，请自行测试*\n\n### 2.3、使用yargs来开发命令行工具\n\n具体代码如下：\n\n```javascript\n#!/usr/bin/env node \nvar argv = require('yargs')\n  .option('i', {\n    alias : 'init',\n    demand: true,\n    default: '',\n    describe: 'Project Init',\n    type: 'string'\n  })\n  .usage('Usage: nc init')\n  .example('nc init', 'Initial newkit project')\n  .help('h')\n  .alias('h', 'help')\n  .epilog('copyright 2015')\n  .argv;\n\n//根据不同的参数来做处理\n\n```\nyargs更多信息请参阅：[https://github.com/yargs/yargs](https://github.com/yargs/yargs)\n\n# 3、注意事项\n\n1. 根据Unix的传统，程序执行成功返回0，否则返回1\n\n```javascript\nif(err){\n  return process.exit(1);\n}\nprocess.exit(0);\n```\n2. 系统信号\n\n```javascript\nprocess.on('SIGINT', function () {\n  console.log('Got a SIGINT');\n  process.exit(0);\n});\n//发送系统信号：$ kill -s SIGINT [process_id]\n```"
  },
  {
    "path": "前端相关/探索Decorator.md",
    "content": "---\ntitle: 探索Decorator\ndate: 2017/02/21 14:47:11\n---\n\n## 0、前言\n\n## 1、啥是Decorator？\n\n## 2、"
  },
  {
    "path": "前端相关/浏览器 Pointer Events.md",
    "content": "---\ntitle: 浏览器 Pointer Events\ndate: 2017/02/21 14:47:11\n---\n\n## 前言\n\nPointer Events是一套触控输入处理规格，支持Pointer Events的浏览器包括了IE和Firefox，最近Chrome也宣布即将支持该处理规则。\n\n## PointerEvent\n\n``PointEvent``对象继承自``MouseEvent``，使用上也比较类似。\n\n1. mousedown -> pointerdown\n2. mouseenter -> pointerenter\n3. mouseleave -> pointerleave\n4. mousemove -> pointermove\n5. mouseout -> pointerout\n6. mouseover -> pointerover\n7. mouseup -> pointerup\n\nPointerEvent提供了多有预期的鼠标事件属性，并添加了通用的附加属性，来帮助您区分输入类型和特点。\n\n1. height\n2. isPrimary\n3. pointerId\n4. pointerType\n5. pressure\n6. tiltX\n7. tiltY\n8. width\n\n在现在的JS编码中，推荐使用特性检测（以前是浏览器检测）来编写代码，我们可以用以下代码检测浏览器是否支持该特性：\n\n\tif (window.PointerEvent) {\n\t  // Pointer events are supported.\n\t}\n\n那接下来看一下具体的事件代码：\n\n\twindow.addEventListener('pointerdown', pointerdownHandler, false);\n\t\n\tfunction pointerdownHandler (evt) {\n\t\tconsole.log(evt)\n\t}\n\n通过输出，可以更直观的看到PointerEvent的各个属性。\n\n通过浏览器的navigator对象的maxTouthPoints，可以拿到当前设备支持的最大多点触控的数量：\n\n\tnavigator.maxTouchPoints\n\n从win8开始，IE提供了默认的触摸事件处理，如果想全部由js代码控制触摸事件，那么可以使用：\n\n\ttouch-action: none;\n\n来禁用默认值。\n\n## 参考资料\n\n[https://msdn.microsoft.com/en-us/library/ie/dn433244(v=vs.85).aspx](https://msdn.microsoft.com/en-us/library/ie/dn433244(v=vs.85).aspx)\n\n[http://www.w3.org/TR/pointerevents/](http://www.w3.org/TR/pointerevents/)"
  },
  {
    "path": "前端相关/浏览器关闭事件分析.md",
    "content": "---\ntitle: 浏览器关闭事件分析\ndate: 2017/02/21 14:47:11\n---\n\n# 0、导言\n\n很多时候，我们可能会遇到这样一类需求：\n\n1. 浏览器关闭时，弹出一个新页面\n2. 浏览器关闭时，发送统计信息（如页面浏览时长）\n3. 浏览器关闭时，让用户二次确认\n\n这个时候，我们就需要考虑如何判断浏览器关闭，如何阻止浏览器关闭，如何在浏览器关闭时，还是执行特定操作。\n\n# 1、浏览器关闭与刷新判断\n\n对于关闭与刷新判断，一般有以下几种做法：\n\n首先基础都是通过监听 ``onbeforeunload``、``onunload`` 等相关事件。\n\n然后判断刷新与关闭：\n\n1. 判断鼠标坐标\n2. 判断键盘操作\n3. 判断事件间隔时长\n\n其中，1和2一般是合起来使用。但也并不保险。关闭和刷新的快捷操作是可以自定义的。而且实现逻辑复杂。\n\n**在这里，我就重点来测试线通过判断事件间隔的方式来处理关闭与刷新的判断。** \n\n我们编写了如下测试代码\n\n```javascript\nwindow.onbeforeunload = function () {\n  console.log('onbeforeunload', Date.now());\n  debugger\n};\n\nwindow.onunload = function(){\n  console.log('onunload', Date.now());\n  debugger\n}\n```\n\n分别在浏览器上刷新和关闭，得到如下结果：\n\n| 浏览器  | 关闭/刷新 | onbeforeunload  | onunload  |\n|---|:---:|:---:|:---:|\n| Chrome | 关闭 | [x] | [x] |\n| Chrome | 刷新 | [x] | [x] |\n| Firefox | 关闭 | [x] | [x] |\n| Firefox | 刷洗 | [x] | [x] |\n| Edge | 关闭 | [x] | [] |\n| Edge | 刷新 | [x] | [x] |\n| IE11 | 关闭 | [x] | [x] |\n| IE11 | 刷新 | [x] | [x] |\n\nEdge关闭时，unload是否触发，还并不太确定（不太好验证）。\n\n接下来，我们来验证两个时间的触发时间，测试代码如下：\n\n```javascript\nwindow.onbeforeunload = function (evt) {\n  console.log('onbeforeunload', Date.now());\n  if (navigator.sendBeacon) {\n    navigator.sendBeacon('http://localhost:9999/?t=onbeforeunload', 'onbeforeunload');\n  }\n  window.start = Date.now();\n};\n\nwindow.onunload = function () {\n  console.log('onunload', Date.now());\n  if (navigator.sendBeacon) {\n    navigator.sendBeacon(`http://localhost:9999/?t=onunload&ts=${Date.now() - window.start}`, 'onunload');\n  }\n  window.open('http://10.16.85.170:8000/');\n}\n```\n\n通过 ``navigator.sendBeacon`` 将间隔时间发送到后台进行查看。遗憾的是，IE和Edge并不支持该方法。\n\n测试得出如下表格：\n\n| 浏览器  | 关闭/刷新 | onbeforeunload  | onunload  | 时间间隔 |\n|---|:---:|:---:|:---:|:---:|\n| Chrome52 | 关闭 | [x] | [x] | < 5ms |\n| Chrome52 | 刷新 | [x] | [x] | > 20ms |\n| Firefox46 | 关闭 | [x] | [x] | > 200ms |\n| Firefox46 | 刷洗 | [x] | [x] | 10~100ms |\n| Edge13 | 关闭 | [x] | [] | N/A |\n| Edge13 | 刷新 | [x] | [x] | < 5ms |\n| IE11 | 关闭 | [x] | [x] | > 10ms |\n| IE11 | 刷新 | [x] | [x] | < 5ms |\n\n根据这个表格，然后在按照自己的需要，就可以选择一个分割点来判断是刷新还是关闭。\n\n**当前未发现100%能判断清楚的方法，以上请酌情使用。**\n\n# 2、阻止浏览器关闭\n\n有一些场景，要求在浏览器关闭的时候，再次弹出一个确认框，那这个又应该如何实现呢？\n\n关闭时询问，是一个比较标准化的处理了，只需要我们对 ``unbeforeunload`` 事件的 ``event`` 参数设置返回值，即可达到再次确认的效果。实现代码如下：\n\n```javascript\nwindow.addEventListener('beforeunload', function(evt){\n  evt.returnValue =  '您确定要离开了么？';\n}, false);\n```\n\n**注意：火狐为了避免不必要的弹窗，如果页面没有交互，是不会进行二次确认的，通过不会显示returnValue给用户。相关链接： [https://developer.mozilla.org/zh-TW/docs/Web/API/WindowEventHandlers/onbeforeunload](https://developer.mozilla.org/zh-TW/docs/Web/API/WindowEventHandlers/onbeforeunload)**\n\n# 3、浏览器关闭/刷新时发送统计数据\n\n很多时候，我们想在浏览器关闭/刷新时发送一些统计数据，之前的话，我们可以采用如下一些做法：\n\n1. 在 ``onbeforeunload`` 中使用同步Ajax\n2. 发送Ajax，然后使用死循环，阻塞一个该事件。\n\n这个功能作为一个比较大众化的需求，W3C中也有了一个针对性的草案，那就是浏览器对象的 ``sendBeacon`` 方法。\n\n函数签名如下：\n\n```javascript\nnavigator.sendBeacon(url, data);\n```\n\n有了这个方法，我们就可以在合适的地方（诸如onbeforeunload）发送我们的统计数据，日志数据等等。\n\n**注意：该方法还是草案阶段，当前IE和EDGE暂不可用。**\n\n\n# 4、相关资料\n\n1. [MDN WindowEventHandlers.onbeforeunload](https://developer.mozilla.org/zh-TW/docs/Web/API/WindowEventHandlers/onbeforeunload)\n2. [MDN Navigator.sendBeacon()](https://developer.mozilla.org/en-US/docs/Web/API/Navigator/sendBeacon)"
  },
  {
    "path": "前端相关/浏览器内容安全策略解析.md",
    "content": "---\ntitle: 浏览器内容安全策略解析\ndate: 2017/02/21 14:47:11\n---\n\n"
  },
  {
    "path": "前端相关/浏览器历史history对象.md",
    "content": "---\ntitle: 浏览器历史history对象\ndate: 2017/02/21 14:47:11\n---\n\n# 0、导言\n\n在单页应用时代，有一个非常重要的概念，那就是前端路由。那它到底是怎么实现的呢？\n\n路由一般有如下两种方式：\n\n1. HASH路由（控制浏览器hash变化）\n2. URL路由（URL直接变化）\n\n**本文主要关注URL变化这种路由实现。** \n\n# 1、History对象\n\n当我们浏览网页时，我们会点击其中的一个链接进行跳转，其中一部分是直接替换掉当前页面，此时就产生了历史记录。\n\n在浏览器中，历史记录的存储我们无法直接控制，但是对于历史记录的使用，是可以通过 ``window.history`` 对象操作的。\n\n比如我们可以查看当前访问了多少个页面：\n\n```javascript\nconsole.log(window.history.length); \n```\n\n可以后退和前进：\n\n```javascript\nwindow.history.back();\nwindow.history.forward();\n```\n\n也可以以当前页面为基准，跳转到前N个或者后N个页面：\n\n```javascript\nwindow.history.go(2); // 前进两页\nwindow.history.go(-1); //后退1页\n```\n\n**注意：如果前进或者后退的索引上没有相对应的历史记录，那么将不会跳转，如 go(555)**\n\n# 2、HTML5 history\n\n在HTML5， ``history`` 有了新的方法，允许我们逐条的添加和修改历史记录条目。\n\n这些方法协同 ``window.onpopstate`` 事件，就构成了我们URL路由的基石。\n\n以下，我们就来看看有哪些新增的方法。\n\n## 2.1、pushState()\n\n使用该方法，可以推送一个状态到历史记录中去。函数使用方式如下：\n\n```javascript\nwindow.history.pushState({a:1, b:2}, '', '/abc.html');\n```\n\n其中参数一是一个JS对象，关联在历史条目中；\n参数二是标题字符串；（当前会被忽略，建议传递''）\n参数三是可选的页面地址（改变URL）\n\n**注意：参数三是一个字符串，但不能带有http://， 可以直接写 xx.html，也可以传入 /xx.html。**\n\n**注意2：虽然看起来效果有点类似 ``window.location = '#abc'`` ，但pushState()方法永远不会触发hashchange事件，即便新的地址只变更了hash**\n\n## 2.2、replaceState()\n\n``replaceState`` 和 ``pushState`` 非常类似，前者是修改，后者是新增。\n\n## 2.3、history.state 属性\n\n当我们正处在一个 ``state`` 状态下的时候，我们可以通过 ``history.state`` 来查看当前的 ``state`` 对象。\n\n如上例中的 ``{a:1, b:2}``\n\n## 2.4、window.onpopstate 事件\n\n如果仅仅只能推送状态到 ``history`` 中，那我们可实现的操作非常有效。但当结合 ``onpopstate`` 事件，我们就能够实现一个可控制URL变化的前端路由器。\n\n```javascript\nwindow.addEventListener('popstate', function(evt){\n  console.log(evt);\n}, false);\n\nwindow.history.pushState({key: 'k1'}, '', 'abc.html');\n```\n\n先执行以上代码，我们发现事件并没有触发，此时点击浏览器后退按钮，可以发现事件被触发。\n\n**注意：调用history.pushState()或者history.replaceState()不会触发popstate事件. popstate事件只会在其他浏览器操作时触发, 比如点击后退按钮(或者在JavaScript中调用history.back()方法)。**\n\n**注意2：当网页加载时,各浏览器对popstate事件是否触发有不同的表现,Chrome 和 Safari会触发popstate事件, 而Firefox不会。**\n\n**注意3：就算是进入非state页面（不是pushState或者replaceState作用过的），也会触发popstate事件。**\n\n\n# 3、URL-Router\n\n有了之前的这些基础，我们来看看，实现一个简单的前端路由需要多少代码？\n\n```javascript\n;(() => {\n  let urlRouter = {};\n  let container;\n  let routeMapCache;\n\n  let getPage = (url, callback) => {\n    let xhr = new XMLHttpRequest();\n    xhr.open('GET', url, true);\n    xhr.setRequestHeader('Accept', 'text/plain');\n    xhr.onreadystatechange = () => {\n      if (xhr.readyState === XMLHttpRequest.DONE) {\n        callback && callback(xhr.responseText);\n      }\n    }\n    xhr.send();\n  };\n\n  urlRouter.init = (routeMap, options) => {\n    routeMapCache = routeMap;\n    if (options.container instanceof HTMLElement) {\n      container = options.container;\n    } else {\n      container = document.querySelector(options.container);\n    }\n\n    // 处理状态变化\n    window.addEventListener('popstate', function (evt) {\n      let stateObj = history.state || evt.state;\n      console.log(evt, stateObj);\n      if (stateObj) {\n        urlRouter.go(stateObj.state);\n      }\n    }, false);\n\n    // 初始化时，处理默认状态\n    let path = window.location.pathname;\n    let stateKeys = Object.keys(routeMapCache);\n    for (let i = 0; i < stateKeys.length; i++) {\n      let stateObj = routeMapCache[stateKeys[i]];\n      if (stateObj.url === path) {\n        urlRouter.go(stateKeys[i]);\n        return;\n      }\n    }\n  };\n\n  urlRouter.go = (state) => {\n    let stateObj = routeMapCache[state];\n    if (!stateObj) {\n      throw new Error('state not found.');\n    }\n    stateObj.state = state;\n    window.history.pushState(stateObj, '', stateObj.url);\n    getPage(stateObj.path, (content) => {\n      container.innerHTML = content;\n    });\n  };\n\n  window.urlRouter = urlRouter;\n})();\n```\n\n如何使用？\n\n```javascript\nlet routeMap = {\n  'page1': { url: '/page1', path: 'page1.html' },\n  'page2': { url: '/page2', path: 'page2.html' },\n  'page3': { url: '/page3', path: 'page3.html' },\n};\n\n// 初始化路由\nwindow.urlRouter.init(routeMap, { container: '#page-content' });\n\n//路由跳转\n\nlet links = [].slice.call(document.querySelectorAll('#page-menu li a'));\nlinks.forEach(link => {\n  link.addEventListener('click', function (evt) {\n    evt.stopPropagation();\n    evt.preventDefault();\n    window.urlRouter.go(evt.target.getAttribute('href'));\n  }, false);\n});\n```\n\n具体Demo地址：[URL-Router Demo](https://github.com/hstarorg/HstarDemoProject/tree/master/Javascript_demo/url-router)\n"
  },
  {
    "path": "前端相关/简单学ES6.md",
    "content": "---\ntitle: 简单学ES6\ndate: 2017/02/21 14:47:11\n---\n\n##前言##\n随着ES6标准的定稿，众多的特性也趋于稳定，各大浏览器也在逐步实现这些特性，那么对ES6有更多的了解就无可厚非了。\n\n##准备##\n在学习ES6之前，我们需要有一个环境来测试ES6代码。在这里我推荐使用node的分支io.js。\n\n1. 如何安装？\n  1. 下载地址：[https://iojs.org/en/index.html](https://iojs.org/en/index.html)，如果各位小伙伴不习惯英文，可以把url中的en修改为cn。\n  2. 然后根据自己的操作系统版本，下载合适的安装包（主要指Windows系统）进行安装。\n  3. 安装过程就不一一赘述了，和一般软件一样。\n\n2. 如何验证安装成功？\n  1. 打开cmd，然后输入``iojs -v``,如果输出一个版本号，那么就代表io.js安装成功。（PS：我现在使用的是v1.2.0）\n  2. 也可以输入``iojs -p process.versions.v8``查看iojs所使用的V8（PS：不是V8发动机）的版本。（PS:我这儿显示4.1.0.14）\n\n##小窥ES6##\n在测试ES6代码前，我们可以先看下io.js对ES6的支持：[https://iojs.org/cn/es6.html](https://iojs.org/cn/es6.html)。\n\n**接下来，开始我们的ES6-Class之旅：**\n\n###1、class 基础 ###\n\n大家应该知道，在大部分面向对象的语言中，都有class的说法，那么在早期的Js中，面向对象的实现比较特殊，我们必须要用function来模拟。如：\n\n\t//ES5及以下\n\tfunction Point(x, y){\n\t  this.x = x;\n\t  this.y = y;\n\t}\n\tvar p1 = new Point(100, 100);\n\n然而在ES6中，我们可以直接使用class关键字，如：\n\n\t//ES6\n\t'use strict' //不能去掉，要不然iojs会提示不认识class。\n\tclass Point{\n\t  constructor(x, y){\n\t    this.x = x;\n\t    this.y = y;\n\t  }\n\t}\n\tvar p1 = new Point(100, 100);\n\tconsole.log(p1);\n\n将以上代码保存为1.js,那么执行如下命令：**``iojs --es_staging 1.js``** 就可以看到\"{x:100, y: 100}\"这个结果了。（PS:注意要在1.js的目录打开cmd）。\n\n接下来，看一个复杂点的，继承：\n\n\t//ES6\n\t'use strict'\n\tclass Point{\n\t  constructor(x, y){\n\t    this.x = x;\n\t    this.y = y;\n\t  }\n\t}\n\tvar p1 = new Point(100, 100);\n\tconsole.log(p1);\n\t\n\tclass ColorPoint extends Point{\n\t  constructor(x, y, color){\n\t    super(x, y);\n\t    this.color = color;\n\t  }\n\t}\n\tvar cp = new ColorPoint(50, 50, 'red');\n\tconsole.log(cp);\n\n\t//输出继承关系\n\tconsole.log(cp instanceof ColorPoint); //true\n\tconsole.log(cp instanceof Point);  //true\n\n可以看到，和大部分语言的继承都很类似，如果你有其他面向对象语言的基础，那么很容易就能理解。\n\n对Point和ColorPoint进行typeof，结果很明显也能看到是function。\n\n\tconsole.log(typeof Point);  // function\n\tconsole.log(typeof ColorPoint);  // function\n\n那如果对class进行函数调用呢？\n\n\tPoint(100, 100); //Error\n\n如上，必须通过new调用class，直接使用函数调用则会报错。\n\n再来对比以下代码：\n\n\t//标准的函数可以先写调用语句，后写申明语句。因为会定义前置\n\tfoo();\n\tfunction foo(){}\n\t//如果是class呢?\n\tnew Foo(); //Error,Foo is not defined\n\tclass Foo{}\n\n如上，如果是定义的class，那么必须要定义语句在前，调用在后。\n\n再来看以下的情形：\n\n\tfunction funThatUseBar(){\n\t  new Bar();\n\t}\n\t//funThatUseBar(); //Error,Bar is not defined\n\tclass Bar{}\n\tfunThatUseBar(); //ok\n\n如上，如果先使用了Bar，那么也是会报错的。必须要优先定义class。\n\n附上以上所有的js，会报错的语句，进行了注释。\n\n\t//ES6\n\t'use strict'\n\tclass Point{\n\t  constructor(x, y){\n\t    this.x = x;\n\t    this.y = y;\n\t  }\n\t}\n\tvar p1 = new Point(100, 100);\n\tconsole.log(p1);\n\t\n\tclass ColorPoint extends Point{\n\t  constructor(x, y, color){\n\t    super(x, y);\n\t    this.color = color;\n\t  }\n\t}\n\tvar cp = new ColorPoint(50, 50, 'red');\n\tconsole.log(cp);\n\t\n\t//*********************************************\n\t\n\t//输出继承关系\n\tconsole.log(cp instanceof ColorPoint); //true\n\tconsole.log(cp instanceof Point);  //true\n\t\n\tconsole.log(typeof Point);  // function\n\tconsole.log(typeof ColorPoint);  // function\n\t\n\t//Point(100, 100); //Error\n\t\n\t//************************************\n\t//标准的函数可以先写调用语句，后写申明语句。因为会定义前置\n\tfoo();\n\tfunction foo(){}\n\t\n\t//如果是class呢?\n\t//new Foo(); //Error,Foo is not defined\n\tclass Foo{}\n\t\n\t\n\t//*******************************************\n\t\n\tfunction funThatUseBar(){\n\t  new Bar();\n\t}\n\t//funThatUseBar(); //Error,Bar is not defined\n\tclass Bar{}\n\tfunThatUseBar(); //ok\n\n###2、类中的主体\n\nES6中、class的主体只能包含方法，不能包含数据属性。如果在类中包含变量定义，则会报错。class中的方法有三种类型：构造函数、静态方法、原型方法，如：\n\n\tclass Class1{\n\t  //构造\n\t  constructor(options){\n\t\n\t  }\n\t\n\t  // 静态方法，静态方法用static修饰\n\t  static staticMethod(){\n\t    return 'static method';\n\t  }\n\t\n\t  prototypeMethod(){\n\t    return 'prototype method';\n\t  }\n\t}\n\n其中，每个class和class原型的constructor都是相等的，同时class本质也是function\n\n\tconsole.log(Class1 === Class1.prototype.constructor) // true\n\tconsole.log(typeof Class1)  // function\n\n然后我们对类中的方法做测试\n\n\tvar p = console.log;\n\tp(typeof Class1.prototype.prototypeMethod); \n\tClass1.prototype.prototypeMethod() // 原型方法调用方式\n\tp(typeof Class1.staticMethod);  \n\tClass1.staticMethod() //静态方法调用方式\n\nGetters 和 Setters 的用法\n\n\tclass Class2{\n\t  get name(){\n\t    return 'jay';\n\t  }\n\t  set name(value){\n\t    console.log('set name = ' + value);\n\t  }\n\t}\n\t\n\tvar c2 = new Class2();\n\tc2.name = 'hu';  // \"set name = hu\"\n\tconsole.log(c2.name); // \"jay\"\n\n当使用了get和set时，那么针对属性的get和set会自动调用class中相关的方法。\n\n贴出所有Js代码：\n\n\t'use strict'\n\tclass Class1{\n\t  //构造\n\t  constructor(options){\n\t\n\t  }\n\t\n\t  // 静态方法\n\t  static staticMethod(){\n\t    return 'static method';\n\t  }\n\t\n\t  prototypeMethod(){\n\t    return 'prototype method';\n\t  }\n\t}\n\t\n\tconsole.log(Class1 === Class1.prototype.constructor);\n\tconsole.log(typeof Class1);\n\t\n\tvar p = console.log;\n\tp(typeof Class1.prototype.prototypeMethod);\n\tp(typeof Class1.staticMethod);\n\t\n\tclass Class2{\n\t  get name(){\n\t    return 'jay';\n\t  }\n\t  set name(value){\n\t    console.log('set name = ' + value);\n\t  }\n\t}\n\t\n\tvar c2 = new Class2();\n\tc2.name = 'hu';\n\tconsole.log(c2.name);\n\n###3、类的继承\n\n简单的继承关系，如下：\n\n\t'use strict'\n\tclass Class1{\n\t  toString(){\n\t    return 'parent class.';\n\t  }\n\t}\n\t\n\tclass SubClass extends Class1{\n\t  toString(){\n\t    return 'sub class.';\n\t  }\n\t}\n\t\n\tvar sc = new SubClass();\n\tconsole.log(sc.toString()); // \"sub class\"\n\n其中，sc是Class1的实例，也是SubClass的实例：\n\n\tconsole.log(sc instanceof Class1); //true\n\tconsole.log(sc instanceof SubClass); //true\n\n如果要调用父类的方法，怎么办呢？\n\n\tclass SubClass2 extends Class1{\n\t  toString(){\n\t    return super.toString();\n\t  }\n\t}\n\t\n\tvar sc2 = new SubClass2();\n\tconsole.log(sc2.toString());\n\n在继承关系中，子类的原型等于父类：\n\n\tconsole.log(Object.getPrototypeOf(SubClass2) === Class1); //true\n\n在子类中访问父类构造，使用super即可。\n\n##其他##\n1. 如果想一览所有的ES6新特性，可以参考[https://github.com/lukehoban/es6features](https://github.com/lukehoban/es6features)\n2. 如果想系统的学习ES6，那么推荐[http://es6.ruanyifeng.com/](http://es6.ruanyifeng.com/)\n3. 想了解更多Classes in ECMAScript 6，可参考[http://www.2ality.com/2015/02/es6-classes-final.html](http://www.2ality.com/2015/02/es6-classes-final.html)"
  },
  {
    "path": "前端相关/认识AMD、CMD、UMD、CommonJS.md",
    "content": "---\ntitle: 认识AMD、CMD、UMD、CommonJS\ndate: 2017/02/21 14:47:11\n---\n\n## 0、导言\n\nJavaScript的生态系统一直在稳步增长，当各种组件混合使用时，就可能会发现不是所有的组件都能“和平共处”，为了解决这些问题，各种模块规范就出来了。\n\n## 1、AMD（Asynchromous Module Definition - 异步模块定义）\n\nAMD是RequireJS在推广过程中对模块定义的规范化产出，AMD是异步加载模块，推崇依赖前置。\n\n\tdefine('module1', ['jquery'], ($) => {\n\t  //do something...\n\t});\n\n代码中依赖被前置，当定义模块（module1）时，就会加载依赖（jquery）\n\n## 2、CMD（Common Module Definition - 公共模块定义）\n\nCMD是SeaJS在推广过程中对模块定义的规范化产出，对于模块的依赖，CMD是延迟执行，推崇依赖就近。\n\n\tdefine((require, exports, module) => {\n\t  module.exports = {\n\t    fun1: () => {\n\t       var $ = require('jquery');\n\t       return $('#test');\n\t    } \n\t  };\n\t});\n\n如上代码，只有当真正执行到fun1方法时，才回去执行jquery。\n\n同时CMD也是延自CommonJS Modules/2.0规范\n\n## CommonJS\n\n提到CMD，就不得不提起CommonJS，CommonJS是服务端模块的规范，由于Node.js被广泛认知。\n\n根据CommonJS规范，一个单独的文件就是一个模块。加载模块使用require方法，该方法读取一个文件并执行，最后返回文件内部的module.exports对象。\n\n\t//file1.js\n\tmoudle.exports = {\n\t  a: 1\n\t};\n\t\n\t//file2.js\n\tvar f1 = require('./file1');\n\tvar v = f1.a + 2;\n\tmodule.exports ={\n\t  v: v\n\t};\n\nCommonJS 加载模块是同步的，所以只有加载完成才能执行后面的操作。像Node.js主要用于服务器的编程，加载的模块文件一般都已经存在本地硬盘，所以加载起来比较快，不用考虑异步加载的方式，所以CommonJS规范比较适用。但如果是浏览器环境，要从服务器加载模块，这是就必须采用异步模式。所以就有了 AMD  CMD 解决方案。\n\n## UMD（Universal Module Definition - 通用模块定义）\n\nUMD又是个什么玩意呢？UMD是AMD和CommonJS的一个糅合。AMD是浏览器优先，异步加载；CommonJS是服务器优先，同步加载。\n\n既然要通用，怎么办呢？那就先判断是否支持node.js的模块，存在就使用node.js；再判断是否支持AMD（define是否存在），存在则使用AMD的方式加载。这就是所谓的UMD。\n\n\t((root, factory) => {\n\t  if (typeof define === 'function' && define.amd) {\n\t    //AMD\n\t    define(['jquery'], factory);\n\t  } else if (typeof exports === 'object') {\n\t    //CommonJS\n\t    var $ = requie('jquery');\n\t    module.exports = factory($);\n\t  } else {\n\t    //都不是，浏览器全局定义\n\t    root.testModule = factory(root.jQuery);\n\t  }\n\t})(this, ($) => {\n\t  //do something...  这里是真正的函数体\n\t});\n\n\n\n\n"
  },
  {
    "path": "前端相关/记一次Bug排查（Spider）.md",
    "content": "---\ntitle: 记一次Bug排查（Spider）\ndate: 2017/02/21 14:47:11\n---\n\n## 0、写在之前\n\n### Spider是什么？\n\nSpider是基于Express框架结合socket.io（现已切换为ws）和Echarts2实现的即时性能监视工具。整体结构为前后端分离，通过express的static中间件来处理静态请求，Express本身则处理api请求。\n\n用法上，只需要按照指定的格式post数据给Spider，那么当请求Spider展示时，将会自动将数据展示为图表。\n\n项目地址：[http://trgit2/backend_framework/spider](http://trgit2/backend_framework/spider)\n\n展示地址：[http://developer.newegg.org/spider](http://developer.newegg.org/spider)\n\n### Spider能做什么？\n\nSpider可以提供一段时间（最大30分钟）内特定操作的消耗时间统计图（散点图）。大概展示效果如下图：\n\n![散点图](http://7xit2j.com1.z0.glb.clouddn.com/test1.png)\n\n## 1、问题初现\n\n该项目从15年3月发布，由于用户量较少，一直稳定运行。\n\n近期，MPS团队使用它来监控业务请求各个阶段的耗时情况，用于定位性能问题。此时，问题出现，spider运行一段时间（10~30分钟）后将会消耗大量服务器内存，导致内存不足而崩溃。\n\n此时，用户使用场景：\n\n1. 在c#程序中，收集各种类型操作的耗时数据，累计起来。\n2. 每隔1s将数据提交到spider后端。\n3. 打开监控页面，查看数据（可能会有多个监控端）\n\nspider处理过程：\n\n1. 接收到请求数据之后，将数据进行处理，并存入内存；\n2. 如果发现有监控端运行，那么推送数据给监控端（每次收到请求数据都会触发一次推送）\n3. 监控端根据最新的数据绘制图表展示。\n\n## 2、问题分析\n\n### 猜想1：Node代码写法问题，导致内存泄露\n\n因为JS的闭包容易内存泄露，首先怀疑代码写法问题。\n\n**验证：**经过仔细排查代码，并对有可能有泄露代码进行改写\n\n**结果：**问题未解决，未缓解\n\n### 猜想2：缓存数据的代码有内存泄露\n\n由于客户端提交的数据是直接写入内存的，而刚好又是消耗内存，那么开始怀疑缓存数据的代码。\n\n**验证：**重写了数据缓存代码（参考正常运行的缓存代码实现）\n\n**结果：**问题未解决，未缓解\n\n**此时，把最常见的情况都验证了，无解。思绪中断，接着做了哪些事情呢？**\n\n1. 查找有可能导致JS内存泄露的情况，一一确认。\n2. 安装JS的内存分析工具（条件苛刻，未安装成功）。\n3. 安装Node程序的监控包，进行大量本地测试（很难复现问题）。\n4. 此时陷入困境...\n5. 再次想方设法安装内存分析工具，在本地测试，未果（问题很难复现）。\n6. 线上测试，问题依旧，根据结果，发现大量字符串占用内存，导致内存不足。\n7. 分析代码，查找该字符串初现的位置。\n8. 开始新的猜测\n\n###猜想3：socket.io内存泄露\n\n从内存分析的结果来看，大量字符串占用了内存，该格式的字符串，是由socket.io分发到客户端的时候产生的。所以怀疑到了socket.io头上。\n\n通过查资料，发现socket.io确实有内存泄露的前科，一度认为这就是原因。\n\n**验证1：**升级socket.io到最新的版本\n\n**结果1：**有一定的好转，但不明显（坚持的时间稍微久一点，内存偶尔能回收一下）\n\n再次各种查资料，发现有提到强制node使用gc回收，可以处理非托管内存。\n\n**验证2：**增加gc回收代码，定时执行\n\n**结果2：**基本上看起来无效，和结果1并没有太大区别\n\n**此时，有点穷途末路了。问题还在，继续探索**\n\n**验证3：**考虑到项目本身不大，重新使用了一个新的WebSocket(ws)包来推送数据\n\n**结果3：**有明显好转，坚持的时间更长了（一度认为解决了问题）\n\n**可是好景不长，较长时间（几小时）后，问题再次出现**\n\n### 猜想4：再度怀疑缓存代码\n\n此时未直接验证，切换到业界一个比较流行的缓存库([lru-cache](https://github.com/isaacs/node-lru-cache))。\n\n### 猜想5：每次提交数据都会触发一次推送，推送频率较高，导致数据阻塞\n\n这个猜想，算来后面总结的。一开始怀疑过这个问题，想过降低推送频率，但没有实施。经@James分析也猜测问题可能会出现在此处，因为大量字符串占用内存，也满足这个猜想。想到就做：\n\n**验证：**将每次收到数据触发推送，修改为间隔一定时间（5s）推送一次。\n\n**结果：**内存趋于平稳，及时性在接收范围内。\n\n至此，问题基本上算是解决了。\n\n## 3、Bug分析\n\n此次问题，是由于服务端推送数据量较大，而且频率较高，上次推送未完成，就触发了下一次推送。导致消息阻塞，越积越多，所以就耗尽内存而亡。\n\n## 4、后续\n\n之后，spider运行一天多之后死掉过一次，但没有看到具体原因。重启之后，稳定运行至今。暂时性怀疑中途死掉的一次为意外（或者瞬时流量大增）\n\n## 5、总结\n\n从发现该问题，到解决该问题，耗时大概在一周。虽然期间还处理了其他任务，但整体来说解决这个问题还是消耗了很长的时间。\n\n**究其原因：**\n\n1. 对TCP底层传输数据不太熟悉\n2. 没有处理大量数据高频率推送的经验\n3. 发布时，只对程序进行了简单的本地测试，没有进行压力测试。\n\n**学到了什么：**\n\n1. 对代码负责，以更严谨的态度来对待，减少出问题\n2. 对WebSocket的负载和相关设计有了更深刻的理解\n3. 多做一些测试，更稳定的保持代码质量"
  },
  {
    "path": "前端相关/说说如何部署node程序.md",
    "content": "---\ntitle: 说说如何部署node程序\ndate: 2017/02/21 14:47:11\n---\n\n## 0、前言\n\nNode作为时下流行的服务端运行时，我们就不得不接触另外一个方面的内容，那就是部署。此文就来说一下node的部署问题。\n\n## 1、开发时部署\n\n在开发阶段，我们可以随意部署，直接通过 ``node xxx.js`` 就能很方便的启动项目。\n\n```\n这种方式有一个弊端，每次代码变更之后，我们需要手动去重启。\n这个时候，我们可以借助一些第三方工具来实现监控代码变更。\n如： gulp-develop-server、nodeman、node-supervisor等等。\n```\n\n## 2、上线时如何部署？\n\n### 2.1、原始做法\n\n既然开发时，可以用 ``node xxx.js``，那上线时也一样。妥妥的。\n\n```\n弊端：会启动一个黑窗口，不能关闭，登录不能被注销\n``` \n\n### 2.1、原始做法，升级版\n\n在Windows下，通过 ``start /b node xxx.js`` ，让程序后台运行。\n\n在Linux下，通过 ``node xxx.js &`` 或者 ``nohup node xxx.js > xxx.log &`` 来实现后台运行。\n\n```\n该方式，避免了阻塞当前控制台，但是其他弊端与2.1一致。\n```\n\n### 2.2、使用进程管理器工具\n\nNode社区中，有比较多进程管理工具。如 [forever](https://github.com/foreverjs/forever) , [pm2](https://github.com/Unitech/pm2), [strongloop](https://github.com/strongloop/strongloop) 等等。\n\n我这里推荐 ``pm2``，功能强大，操作简单，监控，自动重启，多进程都能支持。\n\n以下以 ``pm2`` 为例，演示一下部署方式：\n\n通过 ``npm install pm2 -g`` 安装 ``pm2``\n\n通过 ``pm2 start xxx.js`` 启动程序\n\n通过 ``pm2 stop|restart|delete`` 来停止，重启，删除程序。\n\n通过 ``pm2 list`` 可以查看部署的程序。\n\n更多操作，请参考：[http://pm2.keymetrics.io/](http://pm2.keymetrics.io/)\n\n```\n这种部署方式，还有一个问题，服务器重启后，无法自动启动。\n```\n\n### 2.3、使用进程管理器工具，升级版\n\n在Linux上，通过 ``pm2 startup [platform]`` 就能默认生成一个开机启动项。\n\n如果是Windows，我们可以通过 [pm2-windows-service](https://www.npmjs.com/package/pm2-windows-service) 或者是 [pm2-windows-startup](https://www.npmjs.com/package/pm2-windows-startup) 来把 ``pm2`` 做成Windows服务。\n\n了解更多：[http://pm2.keymetrics.io/docs/usage/startup/](http://pm2.keymetrics.io/docs/usage/startup/)\n\n### 2.4、Docker部署\n\n借助Docker提供的独立环境，以上方式均可以在docker中实现，而且就算直接用 ``node xxx.js`` 来启动应用，在docker中也是没问题的。\n\n自动开机启动，则成了docker的问题，而不是部署程序的问题。\n\n### 2.5、更多部署方式待探索...\n\n"
  },
  {
    "path": "前端相关/这些年我们处理过的跨域.md",
    "content": "---\ntitle: 这些年我们处理过的跨域\ndate: 2017-7-8 10:35:58\n---\n\n# 同源策略\n\n在说跨域之前，我们需要先了解下 [同源策略](https://developer.mozilla.org/zh-CN/docs/Web/Security/Same-origin_policy)。它是一个规范（Netscape 1995年提出），并没有指定具体的使用范围和实现方式。\n\n为了保证使用者信息的安全，防止恶意网站篡改用户数据，一些常见的Web技术都默认采用了同源策略（如Silverlight, Flash, XMLHttpRequest, Dom等）。\n\n*那如何判断同源呢？*\n\n1. 相同的协议\n2. 相同的域名\n3. 相同的的端口号\n\n我们用一个表格来展示同源的判断：\n\n| URL1 | URL2 | 是否同源 | 分析 |\n| --- | --- | --- | --- |\n| http://www.a.com | https://www.a.com | 不同源 | 协议不一致 |\n| http://www.a.com | http://www.b.com | 不同源 | 域名不一致 |\n| http://www.a.com | http://www.a.com:8080 | 不同源 | 端口不一致 |\n| http://www.a.com | http://test.a.com | 不同源 | 域名不一致 |\n| http://www.a.com | http://www.a.com/test | 同源 | 判断同源和path无关 |\n| http://www.a.com | http://www.a.com:80 | 同源 | 不带端口访问时，默认是80 |\n\n*如果不是同源会有哪些使用限制呢？*\n\n1. Cookie，WebStorage(LocalStorage, SessionStorage)，Cache(Application Cache, CacheStorage),Web DB(WebSql IndexDB)等都无法共享\n\n2. 无法彼此操作各自的DOM（Iframe）\n\n3. 无法发送Ajax请求\n\n4. 其他\n\n**注意：如果两个站点，具有相同的一级域名（如 www.a.com, test.a.com，一级域名都是a.com），那么可以通过各自设置document.domain='a.com' 来共享Cookie。**\n\n**注意2：如果是iframe非同源，虽然不能操作dom，但是能操作location.href。**\n\n# 什么是跨域？\n\n通过以上内容，我们了解到了什么是同源策略，以及怎么判断同源。那么与之相反，如果不满足同源，则就是跨域。\n\n在浏览器上，如果访问跨域资源，将会有诸多限制（为了安全），参考上面的同源限制。\n\n**注意：跨域限制是浏览器的机制，如果直接在服务端请求，是不会触发跨域限制的。**\n\n# 那些我们遇到的跨域\n\n## 1、图片跨域\n\n对于图片来说，大部分场景是不需要处理跨域限制的，因为一般来说，图片没有跨域限制。\n\n在也有例外，如果在 `Canvas` 中操作跨域的图片，那么就会触发跨域限制。解决办法是在返回图片的时候，添加 `Access-Control-Allow-Origin: orign | '*'` 来允许跨域。\n\n## 2、Iframe跨域\n\n这个也不太常见，如果网站本身和iframe嵌入的站点都是我们自己可以控制的，那么应直接使用 `postMessage` 来通信。如果浏览器较旧，不支持 `postMessage` ，可以考虑通过window.name来传递数据。\n\n> window.name 传递数据原理\n\n> 首先在iframe访问跨域的站点，这个站点，将数据写入到window.name中。\n\n> 然后主站点，修改iframe的location.href='about:blank' 或其他不跨域的站点。\n\n> 最后通过window.name获取数据\n\n**这是因为同一个iframe的window.name是相互共享的。在现代浏览器中，该方式可能会失效，此时请使用 postMessage。**\n\n## 3、字体跨域\n\n跨域使用字体文件，也会触发拦截。这个的解决办法和图片跨域一致，后端设置CORS头部即可。\n\n## 4、Ajax跨域\n\n这是我们经常会遇到的跨域问题，由于现在流行的开发模式，很多时候我们都需要处理这类型的跨域。\n\n*如何判断Ajax跨域*\n\n当我们在访问一个Ajax请求，控制台出现如下错误时，我们基本可以判断，是被跨域拦截了：\n\n```\nXMLHttpRequest cannot load xxxxx. No 'Access-Control-Allow-Origin' header is present on the requested resource. \n```\n\n很多时候，我们的API和Web并不在一个站点上（多个域名），而我们又必须要跨域访问。这个时候我们就需要想办法实现**跨域资源访问**。\n\n以下，我们就来看看如何实现跨域资源访问：\n\n### CORS（跨域资源访问）- 标准做法，强烈推荐\n\n开发模式的演进，导致我们很多的应用都是跨域访问。这个时候CORS规范也就应运而生了。使用它，我们可以直接对跨域资源进行访问，了解更多，请参考[CORS详解](https://github.com/hstarorg/HstarDoc/blob/master/%E5%89%8D%E7%AB%AF%E7%9B%B8%E5%85%B3/CORS%E8%AF%A6%E8%A7%A3.md)。\n\n该方式的核心是通过和后端API协商，看是否允许跨域访问。对于满足某些条件的请求，会先发送一个预请求。简单请求，也需要服务器允许跨域访问。\n\n最关键的的几个响应头如下：\n\n1. Access-Control-Allow-Origin: origin | '*' 允许某个指定的域访问，\\*表示不限制域。\n2. Access-Control-Allow-Methods: 'GET,POST,PUT,DELETE' 允许哪些类型的请求\n3. Access-Control-Allow-Headers: 'x-token' 允许的自定义Header。\n\n**注意：该方式由服务端设置，前端无需设置，也无法设置。只要服务端处理好了，前端不需要做任何处理即可使用。**\n\n### 反向代理（将跨域代理为同域，绕过）\n\n既然跨域有限制，那么我们可以考虑将跨域变成同域，这样不就没有限制了么？\n\n以 `Nginx` 为例，我们只需要将特定路径的请求转发给真正的后端API即可：\n\n```\nserver {\n    listen 8101;\n    root /dist;\n    index index.html;\n\n    location ~* \\.(eot|ttf|woff|woff2)$ {\n        add_header x-server $server_addr;\n        add_header Access-Control-Allow-Origin '*';\n    }\n    \n    location ^~ /api/v1 {\n        proxy_pass http://apis.xxx.com/api/v1;\n    }\n}\n```\n\n**注意：该方式需要在部署的时候做处理，前端需要修改请求api的地址为同域。**\n\n### 服务端转发（通过不跨域的请求跨域API，绕过）\n\n该方式，通过请求不跨域的api，然后在api中再呼叫真实的跨域api，由于是服务端请求，所以也就避开了跨域问题。整体看来，这种方式有点多此一举，不过如果把这个转发由统一的程序进行处理，还是挺不错的。\n\n**注意：该方式在后端API中处理，前端需要修改请求api地址为同域。**\n\n### JSONP（利用script无跨域限制，绕过）\n\n该方式利用Script请求资源不会触发跨域限制这个特点来实现。JSON原理，请参考[JSONP详解](https://github.com/hstarorg/HstarDoc/blob/master/%E5%89%8D%E7%AB%AF%E7%9B%B8%E5%85%B3/JSONP%E8%AF%A6%E8%A7%A3.md)。\n\n**注意：该方式需要前后端搭配，后端需要支持JSONP请求，前端需要采用JSONP的方式去请求数据。**\n\n**注意2：该方式由于实现原理限制，只能处理GET请求。**\n\n---\n\n**综上，遇到跨域请求，就先去找后端啊。前端真的独自搞不定啊。**\n\n# 总结\n\n跨域是项目开发中，非常常见的问题。就算是前端开发，也一定要理解跨域，了解跨域的处理方案。以便于能够真正的处理好开发任务（或许，这样和后端交(Si)流(Bi)也更有底气）。"
  },
  {
    "path": "前端相关/那些容易出错的Dom操作.md",
    "content": "---\ntitle: 那些容易出错的Dom操作\ndate: 2017/02/21 14:47:11\n---\n\n## 0、导言\n\n在用惯了 ``jQuery`` 这种利器之后，回到原生JS的Dom操作，一时间反而感觉有点陌生。\n\n最近在做的项目中，为了尽可能少的引用三方库，所有DOM操作都是用的原生JS来实现的。在这里也分享下在DOM操作中，容易混淆的一些知识点。\n\n原生JS可能会有兼容性问题，以下方法如非特别说明，均在 ``Chrome latest(53), Firefox latest(47), Edge25, IE11`` 中测试通过\n\n## 1、获取视口高度\n\n所谓视口高度，就是在浏览器中，我们可见区域的高度，它不会随着内容的变化而变化，只会跟着浏览器本身的大小变化而变化（工具栏高度也会影响浏览区域的大小）。\n\n先来一段完整的测试代码：\n\n```javascript\nwindow.addEventListener('resize', function(){\n  console.log('window.innerWidth:', window.innerWidth, ', window.innerHeight:', window.innerHeight);\n  console.log('document.documentElement.clientWidth:', document.documentElement.clientWidth, ', document.documentElement.clientHeight:', document.documentElement.clientHeight);\n  console.log('document.body.clientWidth:', document.body.clientWidth, ', document.body.clientHeight:', document.body.clientHeight);\n});\n```\n\n打开不同的页面（是否有滚动条，页面内容大于视口高度），分析得知：\n\n``window.innerWidth`` 和 ``window.innerHeight`` 是获取浏览器视口的宽高，包含滚动条。\n\n``document.documentElement.clientWidth`` 和 ``document.documentElement.clientHeight`` 是获取浏览器视口的宽高，但不包含滚动条。\n\n``document.body.clientWidth`` 和 ``document.body.clientHeight`` 是获取页面的宽高，不包含滚动条\n\n**通过也根据结果得出：IE、FF、Chrome的滚动条宽度/高度为 ``17px``， Edge25中滚动条宽度/高度为 ``12px``。**\n\n除此之外，还可以通过 ``window.outerWidth`` 和 ``window.outerHeight`` 获取浏览器的宽高，包含工具栏，标题栏等的宽高。\n\n**结论：**\n\n```javascript\nlet viewHeight = window.innerHeight || document.documentElement.clientHeight;\nlet viewWidth = window.innerWidth || document.documentElement.clientWidth;\n```\n\n## 2、获取iframe内容高度\n\n``iframe`` 本身等价于一个独立的窗口, 所以要获取iframe内容的高度和获取页面内容高度一致。\n\n在获取视口宽度时，我们知道，通过 ``document.body.clientHeight`` 可以获取 ``body`` 的高度，那么还有其他的方式可以获取么？\n\n还是先上一个测试代码：\n\n```javascript\nvar timerId;\nwindow.addEventListener('resize', function () {\n  window.clearTimeout(timerId);\n  timerId = setTimeout(function () {\n    console.log('document.documentElement.clientHeight:', document.documentElement.clientHeight);\n    console.log('document.documentElement.offsetHeight:', document.documentElement.offsetHeight);\n    console.log('document.documentElement.scrollHeight:', document.documentElement.scrollHeight);\n    console.log('document.body.clientHeight:', document.body.clientHeight);\n    console.log('document.body.offsetHeight:', document.body.offsetHeight);\n    console.log('document.body.scrollHeight:', document.body.scrollHeight);\n  }, 200);\n});\n```\n\n这个测试的结果如下：\n\n```javascript\n// Chrome\ndocumentElement.offsetHeight = documentElement.scrollHeight = body.scrollHeight\nbody.offsetHeight = body.clientHeight\ndocumentElement.offsetHeight > body.offsetHeight (大24px)\n\n// Firefox\ndocumentElement.offsetHeight = documentElement.scrollHeight\nbody.scrollHeight = body.offsetHeight = body.clientHeight\ndocumentElement.offsetHeight > body.offsetHeight (大24px)\n\n// Edge25\ndocumentElement.offsetHeight = body.scrollHeight > documentElement.scrollHeight (大1px)\nbody.clientHeight = body.offsetHeight\ndocumentElement.offsetHeight > body.clientHeight (大24px)\n\n// IE11\ndocumentElement.offsetHeight = documentElement.scrollHeight\nbody.scrollHeight = body.offsetHeight = body.clientHeight\ndocumentElement.offsetHeight > body.offsetHeight (大24px)\n```\n\n根据以上结果，暂时还无法得出结论，所以我们接着测试：\n\n**测试一：设置body的margin:0**\n\n以上四个都有大24px，先来分析下这个是怎么来的。\n\n初步分析是由于 ``body`` 默认的 ``margin: 8px`` 导致的，当设置body的 ``margin: 0`` 之后，这个差距变成了 ``16px``。\n\n**Edge25比较特殊，相关数据变成了：**\n\n```javascript\ndocumentElement.offsetHeight = documentElement.scrollHeight < body.scrollHeight （小1px）\nbody.clientHeight = body.offsetHeight\ndocumentElement.offsetHeight > body.clientHeight (大16px)\n```\n\n**测试二：给body设置固定高度**\n\n此时，我们给body设定一个固定高度，得出以下结果：\n\n```javascript\ndocumentElement.offsetHeight = body.clientHeight = body.offsetHeight = 我们设定的高度\n```\n\n**测试三：增加超高的 absolute 元素**\n\n当absolute元素高度不超过整个页面高度时，无影响。\n\n当absolute元素高度超过整个页面高度时，结果如下：\n\n```javascript\n// Chrome\nbody.scrollHeight = 设定的absolute元素高度\n\n// Firefox, IE11\ndocumentElement.scrollHeight = 设定的absolute元素高度\n\n// Edge25\ndocumentElement.scrollHeight = body.scrollHeight = 设定的absolute元素高度\n```\n\n综合以上测试，在这里得出一个获取页面高度的代码如下：\n\n```javascript\n\nlet pageHeight = Math.max(documentElement.scrollHeight, body.scrollHeight);\n\n```\n\n**注意，以上方案并不具有通用性，还需根据自己的场景来灵活选择。**\n\n## 3、URL编码\n\n我们在浏览器中进行URL编码一般有两个方法，``encodeURI`` 和 ``encodeURIComponent``，那它们之间有啥区别呢？\n\n不多说，先来段代码就知道了：\n\n```javascript\nlet str = '_- key fda';\n\nconsole.log(encodeURI(str)); //_-%20key%20fda\nconsole.log(encodeURIComponent(str)); //_-%20key%20fda\n\nstr = 'https://www.google.com/search?q=Path+must+be+a+string.+Received+null&oq=Path';\nconsole.log(encodeURI(str)); //https://www.google.com/search?q=Path+must+be+a+string.+Received+null&oq=Path\nconsole.log(encodeURIComponent(str)); //https%3A%2F%2Fwww.google.com%2Fsearch%3Fq%3DPath%2Bmust%2Bbe%2Ba%2Bstring.%2BReceived%2Bnull%26oq%3DPath\n```\n\n由此可以看出，当str是简单的非url字符串时，两者并没有差异。但当我们要对一个完整的URL进行编码的时候，就需要使用 ``encodeURIComponent`` 。\n\n## 4、新Tab打开链接实现\n\n一般做法是模拟一个 ``a`` 标签，然后打开，代码如下：\n\n```javascript\nvar a = document.createElement('a');\na.setAttribute('href', url);\na.setAttribute('target', '_blank');\n// 以下两行为兼容IE9而实现，IE9要求必须在body中的a才可以跳转\na.style.display = 'none';\ndocument.body.appendChild(a);\n\na.click();\ndocument.body.removeChild(a);\n```\n\n## 5、设置iframe内容\n\n如果设置局部内容，直接获取 ``contentDocument`` 就可以做到，那如果要填充一个完整的HTML文档，该如何做呢？\n\n```javascript\n// 找到document\nlet fd = document.getElementById('previewFrame').contentDocument;\nfd.open(); // 打开输入流\nfd.write('');\nfd.write(fullHtml); // 写入完整的HTML内容\nfd.close(); // 关闭输入流\n```\n\n## 6、获取滚动高度\n\n老规矩，先来段测试代码：\n\n```javascript\nvar timerId;\nwindow.addEventListener('scroll', function () {\n  window.clearTimeout(timerId);\n  timerId = setTimeout(function () {\n    console.log('document.documentElement.scrollTop:', document.documentElement.scrollTop);\n    console.log('document.body.scrollTop:', document.body.scrollTop);\n  }, 200);\n});\n```\n\n通过在浏览器中测试，得到以下结果：\n\n```javascript\n// Chrome and Edge\ndocumentElement.scrollTop 始终等于 0\ndocument.body.scrollTop 是真实的滚动高度\n\n// Firefox and IE11\ndocumentElement.scrollTop 是真实的滚动高度\ndocument.body.scrollTop 始终为0\n```\n\n因此当我们要计算滚动高度的时候，可以采用如下代码：\n\n```javascript\nlet scrollTop = document.documentElement.scrollTop || document.body.scrollTop;\n```\n\n## 7、获取屏幕大小\n\n这个就比较简单了，代码如下：\n\n```javascript\n// 屏幕宽度\nscreen.width\n\n// 屏幕高度 \nscreen.height\n```"
  },
  {
    "path": "前端相关/那些年我们认识的iframe.md",
    "content": "---\ntitle: 那些年我们认识的iframe\ndate: 2017/02/21 14:47:11\n---\n\n"
  },
  {
    "path": "数据库之路/[20141114]这些年你需要注意的SQL.md",
    "content": "---\ntitle: 这些年你需要注意的SQL\ndate: 2014/11/14 00:00:00\n---\n\n## #1、使用对象时，请显式的指定对象的架构者（默认为dbo）\n\n**分析：** 在SQL SERVER中，如果用户User1访问表table1，那么查询分析器必须决定是检索 User1.table1 还是 dbo.table1 。所以每次User1访问同一张表table1时，查询分析器都必须对查询计划\n重编译，影响执行速度。\n\n## #2、尽量避免使用SELECT *，建议使用 SELECT <Field List>\n\n**分析：** 采用SELECT * 语法会导致DB对列进行一个遍历，同时可能会查询多余字段数据(本着用啥查啥的原则，建议使用SELECT &lt;Field List>)，导致查询性能下降。 \n\n## #3、如果一个T-SQL语句涉及到多个表，则引用的每个列必须制定该列所属的对象\n\n**分析：** 避免造成数据查询异常\n\n## #4、Insert语句中必须指定插入列的列表\n\n**分析：** 避免表列变化导致插入语句失败\n\n## #5、在非事物和特别要求完整性的上下文中，使用NOLOCK查询\n\n**分析：** 考虑到并发性，提高查询效率\n\n**示例：**\n\n```sql\nSELECT TOP 100 COL1,COL2 FROM TABLE1 t1 WITH (NOLOCK)\n```\n\n## #6、通过SELECT语句对变量赋值时，如果未使用聚合函数，请加上TOP 1\n\n**分析：** 防止结果非预期\n\n**示例：** (预期结果为2，因为第二次Name2是后添加的。)\n\n```sql\nCREATE TABLE test6\n( \n      Id int \n    ,Name nvarchar(32) \n)\n\nINSERT INTO test6\t(Id, Name)\nSELECT     1, N'Name1'\nUNION all\nSELECT\t   2, N'Name2' \n\nINSERT INTO test6\t(Id, Name)\nSELECT\t   3, N'Name2'\n\n--错误的写法\nDECLARE @Id int\nSELECT \n    @Id=Id \nFROM test6 \nWHERE \n    Name = 'Name2'\n\n--正确的写法\nDECLARE @Id int\nSELECT TOP 1\n    @Id=Id \nFROM test6 \nWHERE \n    Name = 'Name2'\n``` \n\n## #7、对于排序后取字段TOP 1的值，建议使用聚合\n**分析:** 可以用上索引，而且不需要Table Scan\n\n**示例：**\n\n```sql\n--错误的写法\nSELECT TOP 1 col1 FROM table1 ORDER BY col1 DESC\n\n--正确的写法\nSELECT MAX(col1) FROM table1\n```\n\n## #8、有关char和varchar，char和nchar,varchar和nvarchar的区别\n\n**分析：**\n\n1. char是固定长度，如果数据不够，会在存储时自动补空格\n2. varchar是可变长度，会有三个字节来存储字段信息，可以设置最大长度\n3. nchar和varchar这种以N开头的表示存储unicode编码字符\n4. 在特定字符集下，如果定义数据格式为char或者是varchar，那么存储特殊字符（包括中文）会乱码\n5. 关于数据库函数LEN()，是用于返回指定字符串表达式的字符数，<span style=\"color:red;\"><b>其中不包含尾随空格。</b></span>\n6. 关于DATALENGTH函数，<span style=\"color:red;\"><b>返回用于表示任何表达式的字节数。</b></span>\n\n**示例：**\n\n```sql\nDECLARE @s1 CHAR(5)\n        ,@s2 VARCHAR(5)\n        ,@s3 NCHAR(5)\n        ,@s4 NVARCHAR(5)\n\n--看看这个结果是什么？\nSET @s1 = 'test'\nSET @s2 = 'test'\nSET @s3 = 'test'\nSET @s4 = 'test'\nSELECT \n    LEN(@s1)\n    ,LEN(@s2)\n    ,LEN(@s3)\n    ,Len(@s4)\nSELECT \n    DATALENGTH(@s1)\n    ,DATALENGTH(@s2)\n    ,DATALENGTH(@s3)\n    ,DATALENGTH(@s4)\n\n--如果这样呢？\nSET @s1 = '我是中文'\nSET @s2 = '我是中文'\nSET @s3 = '我是中文'\nSET @s4 = '我是中文'\nSELECT \n    LEN(@s1) \n    ,LEN(@s2) \n    ,LEN(@s3) \n    ,Len(@s4)\nSELECT \n    DATALENGTH(@s1)\n    ,DATALENGTH(@s2)\n    ,DATALENGTH(@s3)\n    ,DATALENGTH(@s4)\n\n--一般情况下，SELECT @s1,@s2,@s3,@s4不会显示乱码，是由于安装SQL SERVER的时候后默认字符集是支持unicode字符的。如果遇到不支持的字符集，就需要显示定义字段类型为带N的类型，同时在赋值的时候使用N'中文'这种形式。\n```\n\n## #9、禁止在使用了事物的情况下，不编写防止造成未提交或者未回滚事务的情况的处理代码\n\n**分析：** 数据库阻塞，你懂的...罪过大大的！\n\n## #10、警惕表变量的使用\n\n**示例：**\n\n```sql\n--思考下结果是什么？\nIF NOT exists (SELECT 1)\nBEGIN\n    PRINT 'enter'\n  DECLARE @table TABLE \n  ( \n        name nvarchar(32) \n  )\nEND\nSELECT name FROM @table\n```\n  \n**分析：**\n\n1. 在表变量的使用中，会出现如JavaScript一样的定义前置，相当于不管你在哪个条件（也不关心是否能走到这个分支）中定义表变量，那么这个表变量在整个作用域中都是有效的。\n2. 临时表表现正常\n3. 表变量和一般的变量有点不一样的地方，表变量也会在tempdb中创建表。示例如下：\n\n```sql\nCREATE TABLE #TempTable (TT_Col1 INT)\nDECLARE @TableVariable TABLE (TV_Col1 INT)\n\nSELECT TOP 2 * \nFROM tempdb.sys.tables\nORDER BY create_date DESC \n```\n\n\n## #11、判断是否存在（或者不存在）符合条件的记录使用 EXISTS 关键字。\n\n**分析：**\n\n```sql\nIF (SELECT COUNT(*) FROM Table WITH (NOLOCK))>0\nBEGIN\n  --Do something\nEND\n--应该用：\nIF EXISTS(SELECT TOP 1 1 FROM Table WITH (NOLOCK)\nBEGIN\n  --Do something\nEND  \n```\n\n## #12、字符串比较时，SQL SERVER会忽略末尾的空格。\n\n**示例：**\n\n```sql\nSELECT 1 \nWHERE 't' ='t        '\n```\n\n## #13、注意NULL的特殊性\n\n**分析：**\n\n1.NULL既不能被=匹配，也不能被<>(!=)匹配，只能用IS NULL 或者是 ISNULL()\n\n**示例：**\n\n```sql\nCREATE TABLE #tb(col1 int)\nINSERT INTO #tb(col1)\nSELECT NULL \nUNION \nSELECT 1    \nUNION\nSELECT 2\n\nSELECT COUNT(*) FROM #tb \nWHERE col1 <> 1 OR col1 = 1 --2\n\nSELECT COUNT(*) FROM #tb --3\n```\n\n## 14、COUNT(0),COUNT(*),COUNT(column)的区别\n\n**分析：**\n\n1. COUNT(0),COUNT(*)计数时会包含NULL值\n2. COUNT(column)计数时，如果需要该列为NULL，则会忽略计数\n\n**示例:**\n```sql\nCREATE TABLE #tb(col1 int)\nINSERT INTO #tb(col1)\nSELECT NULL \nUNION \nSELECT 1    \nUNION\nSELECT 2\n\nSELECT COUNT(*) FROM #tb --3\nSELECT COUNT(0) FROM #tb --3\nSELECT COUNT(col1) FROM #tb --2\n```\n\n## #15、通过合理的方法避免在 SELECT 语句中使用 DISTINCT\n\n**分析：**\n\n1. DISTINCT 是数据查询中一个非常慢的操作，所以尽可能的避免\n\n**示例：**\n```sql\nSELECT DISTINCT\n    A.au_fname\n    ,A.au_lname\nFROM dbo.authors AS A WITH (NOLOCK) \n  INNER JOIN dbo.titleAuthor AS T WITH (NOLOCK)  --一对多的关系\nON T.au_id = A.au_id\n\n--避免DISTINCT的写法\nSELECT au_fname\n    ,au_lname\nFROM dbo.authors AS A WITH (NOLOCK) \nWHERE EXISTS (\n        SELECT TOP 1 1\n        FROM dbo.titleAuthor AS T WITH (NOLOCK)\n        WHERE T.au_id = A.au_id\n```"
  },
  {
    "path": "数据库之路/说说你所熟知的MSSQL中的substring函数.md",
    "content": "---\ntitle: 说说你所熟知的MSSQL中的substring函数\ndate: 2017/02/21 14:47:10\n---\n\n说说你所熟知的MSSQL中的substring函数\n\n### 函数签名：\n\n```sql\nsubstring\n\t--expression (varchar,nvarchar,text,ntext,varbinary,or image)\n\t--Starting position (bigint)\n\t--Length (bigint)\n```\n\n从函数名称来看，是截取字符串内容。\n从函数签名来看，不仅能截取字符串内容，还能截取二进制内容\n\t\n### 那么，你觉得如下应该sql语句应该是什么结果呢？\n\n```sql\nselect subString('123456',0,1)\nselect subString('123456',1,1)\nselect subString('123456',-1,2)\nselect subString('123456',-1,3) \n```\n\n### 如果想好了，但不确定，那赶紧打开工具执行看看吧\n\t\n你答对了吗？为什么会是那些结果，能解释吗？\n\n### 三大知识点：\n\n1. MSSQL中，下标从1开始，注意：不是大多数编程语言采用的0。\n2. substring函数的调用substring(str,startIndex,length)\n\t效果上是转换为substring(str,startIndex,endIndex)来运算的，\n\tendIndex=startIndex+length。\n3. 对于区间取值，采取的是前闭后开的策略，也有是说包含开始下标，但是不包含结束下标。\n\n### 那么能解释上面的代码了吗？\n\n"
  },
  {
    "path": "最佳实践系列/Express异步进化史.md",
    "content": "---\ntitle: Express异步进化史\ndate: 2017-9-9 12:59:49\n---\n# 1、导言\n\n在 `Javascript` 的世界里，异步（由于JavaScript的单线程运行，所以JavaScript中的异步是可以阻塞的）无处不在。\n\n[Express](http://expressjs.com/) 是 `node` 环境中非常流行的Web服务端框架，有很大比例的 `Node Web应用` 采用了 `Express`。\n\n当使用 `JavaScript` 编写服务端代码时，我们无可避免的会大量使用到异步。随着 `JavaScript、Node` 的进化，我们的异步处理方式，也就随之进化。\n\n接下来，我们就来看看 `Express` 中异步处理的进化过程。\n\n# 2、JavaScript的异步处理\n\n在异步的世界里，我们需要想办法获取的异步方法完毕的通知，那在 `JavaScript` 中，会有哪些方式呢？\n\n## 2.1、回调\n\n回调是 `JS` 中最原始，也是最古老的异步通知机制。\n\n```js\nfunction asyncFn(callback) {\n  // 利用setTimeout模拟异步\n  setTimeout(function () {\n    console.log('执行完毕');\n    callback(); // 发通知\n  }, 2000);\n}\n\nasyncFn(function () {\n  console.log('我会在2s后输出');\n});\n```\n\n## 2.2、事件监听\n\n要获取结果的函数，监听某个时间。在异步方法完成后，触发该事件，达到通知的效果。\n\n## 2.3、发布/订阅\n\n通过观察者模式，在异步完成时，修改发布者。这个时候，发布者会把变更通知到订阅者。\n\n## 2.4、Promise\n\n`Promise` 是回调函数的改进。使用它， 我们可以将异步平行化，避免回调地狱。\n\n```js\nfunction asyncFn() {\n  return new Promise((resolve, reject) => {\n    // 利用setTimeout模拟异步\n    setTimeout(function () {\n      console.log('执行完毕');\n      resolve(); // 发通知（是否有感觉到回调的影子？）\n    }, 2000);\n  });\n}\n\nasyncFn()\n  .then(function () {\n    console.log('我会在2s后输出');\n  });\n```\n\n## 2.5、生成器（Generator）\n\nGenerator 函数是 ES6 提供的一种异步编程解决方案。\n\n以下代码只是简单演示，实际上 `Generator` 的使用过程，相对是比较复杂的，这是另外一个话题，本文暂且不表。\n\n```js\nfunction asyncFn() {\n  return new Promise((resolve, reject) => {\n    // 利用setTimeout模拟异步\n    setTimeout(function () {\n      console.log('执行完毕');\n      resolve(); // 发通知（是否有感觉到回调的影子？）\n    }, 2000);\n  });\n}\n\nfunction* generatorSync() {\n  var result = yield asyncFn();\n}\n\nvar g = generatorSync();\ng.next().value.then(()=>{\n  console.log('我会在2s后输出');\n});\n```\n\n## 2.6、async...await\n\n可以说是当前 `JavaScript` 中，处理异步的最佳方案。\n\n```js\nfunction asyncFn() {\n  return new Promise((resolve, reject) => {\n    // 利用setTimeout模拟异步\n    setTimeout(function () {\n      console.log('执行完毕');\n      resolve(); // 发通知（是否有感觉到回调的影子？）\n    }, 2000);\n  });\n}\n\nasync function run(){\n  await asyncFn();\n  console.log('我会在2s后输出');\n}\n\nrun();\n```\n\n# 3、Express中的异步处理\n\n在Express中，我们一般常用的是方案是：`回调函数、Promise、以及async...await`。\n\n为了搭建演示环境，通过 `express-generator` 初始化一个express项目。一般的服务端项目，都是路由调用业务逻辑。所以，我们也遵循这个原则：\n\n打开 `routs/index.js`，我们会看到如下内容，以下Demo就以此文件来做演示。\n```js\nvar express = require('express');\nvar router = express.Router();\n\n/* GET home page. */\nrouter.get('/', function(req, res, next) {\n  res.render('index', { title: 'Express' });\n});\n\nmodule.exports = router;\n```\n\n## 3.1、回调函数处理Express异步逻辑\n\n在 `Express` 中，路由可以加载多个中间件，所以我们可以把业务逻辑按照中间件的写法进行编写。这样通过一层层的next，就能非常方便的拆分异步逻辑。\n\n```js\nvar express = require('express');\nvar router = express.Router();\n\nfunction asyncFn(req, res, next) {\n  setTimeout(() => {\n    req.user = {}; // 设置当前请求的用户\n    next();\n  }, 2000);\n}\n\nfunction asyncFn2(req, res, next) {\n  setTimeout(() => {\n    req.auth = {}; // 设置用户权限\n    next();\n  }, 2000);\n}\n\nfunction asyncFn3(req, res, next) {\n  setTimeout(() => {\n    res.locals = { title: 'Express Async Test' }; // 设置数据\n    res.render('index');  // 响应\n  }, 2000);\n}\n\n/* GET home page. */\nrouter.get('/', asyncFn, asyncFn2, asyncFn3); // 一步步执行中间件\n\nmodule.exports = router;\n```\n\n## 3.2、Promise 处理Express异步逻辑\n\n该方案中，将多个业务逻辑，包装为返回 `Promise` 的函数。通过业务方法进行组合调用，以达到一进一出的效果。\n\n```js\nvar express = require('express');\nvar router = express.Router();\n\nfunction asyncFn(req, res) {\n  return new Promise((resolve, reject) => {\n    setTimeout(() => {\n      req.user = {}; // 设置当前请求的用户\n      resolve(req);\n    }, 2000);\n  });\n}\n\nfunction asyncFn2(req) {\n  return new Promise((resolve, reject) => {\n    setTimeout(() => {\n      req.auth = {}; // 设置用户权限\n      resolve();\n    }, 2000);\n  });\n}\n\nfunction asyncFn3(res) {\n  return new Promise((resolve, reject) => {\n    setTimeout(() => {\n      res.locals = { title: 'Express Async Test' }; // 设置数据\n      res.render('index');  // 响应\n    }, 2000);\n  });\n}\n\nfunction doBizAsync(req, res, next) {\n  asyncFn(req)\n    .then(() => asyncFn2(req))\n    .then(() => asyncFn3(res))\n    .catch(next); // 统一异常处理\n};\n\n/* GET home page. */\nrouter.get('/', doBizAsync);\n\nmodule.exports = router;\n\n```\n\n## 3.3、async...await 处理Express异步逻辑\n\n实际上，该方案也是需要 `Promise` 的支持，只是写法上，更直观，错误处理也更直接。\n\n**需要注意的是，Express是早期的方案，没有对async...await进行全局错误处理，所以可以采用包装方式，进行处理。**\n\n```js\nvar express = require('express');\nvar router = express.Router();\n\nfunction asyncFn(req) {\n  return new Promise((resolve, reject) => {\n    setTimeout(() => {\n      req.user = {}; // 设置当前请求的用户\n      resolve(req);\n    }, 2000);\n  });\n}\n\nfunction asyncFn2(req) {\n  return new Promise((resolve, reject) => {\n    setTimeout(() => {\n      req.auth = {}; // 设置用户权限\n      resolve();\n    }, 2000);\n  });\n}\n\nfunction asyncFn3(res) {\n  return new Promise((resolve, reject) => {\n    setTimeout(() => {\n\n    }, 2000);\n  });\n}\n\nasync function doBizAsync(req, res, next) {\n  var result = await asyncFn(req);\n  var result2 = await asyncFn2(req);\n  res.locals = { title: 'Express Async Test' }; // 设置数据\n  res.render('index');  // 响应\n};\n\nconst tools = {\n  asyncWrap(fn) {\n    return (req, res, next) => {\n      fn(req, res, next).catch(next); // async...await在Express中的错误处理\n    }\n  }\n};\n\n/* GET home page. */\nrouter.get('/', tools.asyncWrap(doBizAsync)); // 需要用工具方法包裹一下\n\nmodule.exports = router;\n```\n\n# 4、总结\n\n虽然 `koa` 对更新、更好的用法（koa是generator，koa2原生async）支持的更好。但作为从 `node 0.x` 开始跟的我，对 `Express` 还是有特殊的好感。\n\n以上的一些方案，已经与 `koa` 中使用无异，配合 `Express` 庞大的生态圈，无异于如虎添翼。\n\n"
  },
  {
    "path": "正则表达式/你真的理解正则修饰符吗.md",
    "content": "---\ntitle: 你真的理解正则修饰符吗\ndate: 2017-05-27 10:25:04\n---\n\n# 0、前言\n\n一段代码引发的思考：\n\n```js\nvar r = /\\d/g;\nconsole.log(r.test('1'));\nconsole.log(r.test('1'));\nconsole.log(r.test('2'));\nconsole.log(r.test('2'));\n```\n\n先看看如上的代码，不要执行，自己先猜测下结果。\n\n# 1、正则修饰符\n\n正则在各个语言中，实现的标准并不完全一致。我们这里就讨论在 `JavaScript` 中的实现。\n\n在 `JavaScript` 中，正则有四个修饰符： `global, ignoreCase, multiline, unicode`，详细请参考：[MDN RegExp](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp)。\n\n它们的含义如下：\n\n* global(g) 针对字符串中所有可能的匹配来测试正则表达式\n* ignoreCase(i) 匹配时忽略大小写\n* multiline(m) 多行输入将被视为多行（此时开始^和结尾匹配$可以在每行中进行匹配）\n* unicode(u) 对字符串采用unicode进行匹配\n\n## 修饰符：`i`\n\n`i` 比较易懂，在匹配的时候会忽略大小写，示例如下：\n\n```js\nvar text = 'abc';\nvar r = /abc/;\nconsole.log(r.test('abc')); // true\nconsole.log(r.test('Abc')); // false\nr = /abc/i;\nconsole.log(r.test('abc')); // true\nconsole.log(r.test('Abc')); // true\n```\n\n## 修饰符：`m` \n\n当匹配多行时，默认是全文本匹配，使用 `m` 之后，将对每一行进行单独匹配。\n\n```js\n// 定义一个多行文本\nvar text = `a b c\n\nA B C`;\nvar r = /c$/; // 匹配以C结尾的文本\nconsole.log(r.test(text)); // false 全文本匹配，匹配不上\nr = /c$/m;\nconsole.log(r.test(text)); // true 多行匹配，c是第一行的末尾，匹配成功\n```\n\n## 修饰符：`u`\n\n使用修饰符 `u`，将采用 `Unicode` 模式进行匹配，必须要在正则中包含unicode才能看到效果：\n\n```js\nvar r = /\\u{61}/; // 匹配61次u字符\nconsole.log(r.test('a')); // false, 明显匹配不上\nr = /\\u{61}/u; // Unicode模式，\\u{61} = 'a'\nconsole.log(r.test('a')); // true, Unicode下能匹配\nr = /\\u{61}{3}/u; // 匹配3个a，注意正则写法\nconsole.log(r.test('aaa')); // true\n```\n\n**注意：在/u模式下，正则中 \\u{xxx} 是一个整体，不可拆分。**\n\n# 2、正则修饰符：`g`\n\n接下来，进入本文的重点，修饰符 `g` 的匹配模式。\n\n首先，我们先回到开头的那段代码，我想大部分人的答案可能是：`true true true true` 吧。\n\n实际上，正确答案是：`true false true false`，是不是觉得不可思议？接着往下看。\n\n## RegExp.prototype.test()\n\n`RegExp.prototype.test()` 方法的逻辑是：当找到第一个匹配项时，返回 `true`。查找整个字符串都没有找到匹配项，返回 `false`。\n\n那具体又是如何查找的呢？这里我们就要看 `RegExp` 的另外一个方法了：`RegExp.prototype.exec()`。\n\n注意观察这个方法的返回值：\n\n```js\nvar r = /\\d/;\n// 注意看，返回值是一个数组，除了匹配到的元素之外，还有一个 index 属性\nconsole.log(r.exec('123')); // [\"1\", index: 0, input: \"123\"]\n```\n\n关键就是 `exec` 返回值中的 `index` 属性，这个属性标识从输入文本的哪一个索引处开始查找匹配项。\n\n如果不加 `g`，每次都是从索引0处开始查找。\n\n```js\nvar r = /\\d/;\nconsole.log(r.exec('123')); // [\"1\", index: 0, input: \"123\"]\nconsole.log(r.exec('123')); // [\"1\", index: 0, input: \"123\"]\nconsole.log(r.exec('123')); // [\"1\", index: 0, input: \"123\"]\n```\n\n那如果加 `g` 呢？\n\n```js\nvar r = /\\d/g;\nconsole.log(r.exec('123')); // [\"1\", index: 0, input: \"123\"]\nconsole.log(r.exec('123')); // [\"2\", index: 1, input: \"123\"]\nconsole.log(r.exec('123')); // [\"3\", index: 2, input: \"123\"]\nconsole.log(r.exec('123')); // null\n```\n\n从结果我们可以看出，这个 `index` 是在变化的，当找不到匹配项时，会返回 null。\n\n**总结一下：修饰符 `g` 的作用，是标识是否需要全局存储这个index。**\n\n有了这个理解，那么回到之前的问题，那就说得通了。我们使用了 `g` 修饰符，那么会存储上一次的 `index`，当执行第二次 `console.log(r.test('1'));` 时，索引为1，当然就匹配不上了，所以就返回了 `false`。\n\n那问题又来了，为什么第三次，又返回了 `true` 呢？\n\n还是先看代码：\n\n```js\nvar r = /\\d/g;\nconsole.log(r.exec('12')); // [\"1\", index: 0, input: \"12\"]\nconsole.log(r.exec('12')); // [\"2\", index: 1, input: \"12\"]\nconsole.log(r.exec('12')); // null\nconsole.log(r.exec('12')); // [\"1\", index: 0, input: \"12\"]\n```\n\n**当发现已经匹配不上元素时，会将这个 `index` 重新设置为 `0`。**\n\n这也就解释了最开始的整个代码。\n"
  },
  {
    "path": "测试相关/MOCHA测试代码汇总.md",
    "content": "---\ntitle: MOCHA测试代码汇总\ndate: 2017/02/21 14:47:11\n---\n\n# 0x0、导言\n\nMocha是应用最广泛的JS测试框架，但是现在，它的维护者公开说，Mocha快死了，[原文Twitter地址](https://twitter.com/b0neskull/status/820848476393091072)。\n\n死不死的理我们太远，我们先来回味一波。。。\n\n# 0x1、关于单元测试\n\n*什么是单元测试？*\n\n**维基百科：** 单元测试（英语：Unit Testing）又称为模块测试, 是针对程序模块（软件设计的最小单位）来进行正确性检验的测试工作\n\n**个人理解：** 编写测试代码/脚本，通过执行测试代码来保证某一功能（一般是方法）的结果可预期。\n\n*如何做单元测试？*\n\n抛开 `mocha`, 我们应该怎么做单元测试呢？\n\n我们会考虑书写一段代码或者脚本，来调用我们写好的方法，通过 `Console` 输出来查看最终结果。\n\n使用单元测试，能够有效的保证代码的正确性，并且有利于我们之后的大胆重构。必须有用例保证，就不怕改出大量新bug。\n\n当然，这仅仅是最原始的测试方式，一般情况下，我们会选择使用单元测试工具。\n\n# 0x2、Mocha\n\n`Mocha(摩卡)` 是一个多功能的，支持浏览器和 Node 的 JavaScript 测试工具。仅仅是测试工具，当然还不能满足我们的需要，我们一般还会结合一些插件来进行使用，如下：\n\n1. chai BDD/TDD风格的断言库\n2. chai-http Http请求包\n\n*如何用 `Mocha` 来做单元测试呢？*\n\n首先是引入断言库，然后定义测试块，如下：\n\n```javascript\nconst assert = require('assert');\n\ndescribe('Test object exist', () => {\n  it('first test', () => {\n    assert.equal(1, 2, '实际值和期望值不一致');\n  });\n});\n```\n\n*多种风格断言演示*\n\n测试本身比较容易理解，就我来说，我比较容易忘记断言库的写法，接下来就以 `chai` 为例，体验下几种方式的断言代码：\n\n第一步，我们要先引入断言库，允许多种风格的断言：\n\n```javascript\nconst chai = require('chai');\nconst assert = chai.assert; // TDD\nconst expect = chai.expect; // BDD\nchai.should(); // BDD\n```\n\n接着，我们来一一演示断言的使用：\n\n1. 判断类型\n\n```javascript\nconst chai = require('chai');\nconst assert = chai.assert; // TDD\nconst expect = chai.expect; // BDD\nchai.should(); // BDD\n\ndescribe('Test object exist', () => {\n  it('object is exist', () => {\n    let a = 'abc';\n    let b = 1;\n    let c = true;\n    let d = /xxx/;\n    let e = new Date();\n    let f = function () { };\n    let g = {};\n    let h = [];\n    assert.isString(a); // a必须是字符串\n    assert.isNotString(f); // f必须不是字符串\n    expect(b).to.be.a('number'); // b必须是一个数字\n    expect(c).to.be.a('boolean'); // c必须是boolean\n    d.should.be.a('RegExp'); // d必须是正则\n    e.should.be.a('date'); // e必须是Date\n    assert.isArray(h); // h必须是Array\n    assert.isObject(g); // g必须是对象\n  });\n});\n\n```\n\n2. 判断属性存在与否/属性值是否满足预期\n\n```javascript\nconst chai = require('chai');\nconst assert = chai.assert; // TDD\nconst expect = chai.expect; // BDD\nchai.should(); // BDD\n\ndescribe('Test Property', () => {\n  it('property and value', () => {\n    let a = {\n      b: 1,\n      c: false,\n      deep: {\n        test: '1'\n      }\n    };\n\n    // 对象a必须包含属性b\n    assert.property(a, 'b');\n    expect(a).has.property('b');\n    a.should.has.property('b');\n\n    // 对象a不能包含属性d\n    assert.notProperty(a, 'd');\n    expect(a).not.has.property('d');\n    a.should.not.has.property('d');\n\n    // 对象必须有嵌套属性deep.test\n    assert.deepProperty(a, 'deep.test');\n    expect(a).has.deep.property('deep.test');\n    expect(a).has.property('deep').has.property('test');\n    a.should.has.deep.property('deep.test');\n    a.should.has.property('deep').has.property('test'); \n\n    // 对象必须不包含嵌套属性deep.test2\n    assert.notDeepProperty(a, 'deep.test2');\n    expect(a).not.has.deep.property('deep.test2');\n    a.should.not.has.deep.property('deep.test2');\n\n    // 对象属性值必须等于指定值，注意，是===判断\n    assert.propertyVal(a, 'b', 1);\n    expect(a).has.property('b', 1);\n    a.should.has.property('b', 1);\n\n    // 对象属性值必须不等于指定值\n    assert.propertyNotVal(a, 'c', true);\n    expect(a).not.has.property('c', 0);\n    a.should.not.has.property('c', undefined);\n\n    // 对象必须包含多个指定的属性\n    assert.property(a, 'b').property('c').property('deep.test2')\n    expect(a).has.property('a').has.property('c');\n  });\n});\n```\n\n3. 比较目标值与期望值\n\n```javascript\nconst chai = require('chai');\nconst assert = chai.assert; // TDD\nconst expect = chai.expect; // BDD\nchai.should(); // BDD\n\ndescribe('Test Value', () => {\n  it('value equal or not equal', () => {\n    let a = 'abc';\n    let b = [1, '2', false];\n    let c = { a: 1, b: { c: 2 } };\n\n    // 直接比较\n    assert.equal('abc', a);\n    expect(a).eql('abc');\n    a.should.eq('abc');\n\n    let cCopy = Object.assign({}, c);\n\n    assert.deepEqual(cCopy, c);\n    expect(cCopy).deep.equal(c);\n    cCopy.should.be.deep.equal(c);\n  });\n\n  it('test multi equal function', () => {\n    let c = { a: { b: 'str' } };\n    let cCopy = Object.assign({}, c);\n\n    cCopy.should.be.eql({ a: { b: 'str' } }); // 内容相等即可，不判断引用（别名：eqls, deep.equal, deep.eq, deep.equals）\n    cCopy.should.be.equal(a = cCopy); // 严格完全相等， === 判断（别名：eq, equals）\n  });\n});\n\n```\n\n4. 目标是否存在\n\n```javascript\nconst chai = require('chai');\nconst assert = chai.assert; // TDD\nconst expect = chai.expect; // BDD\nconst should = chai.should(); // BDD\n\ndescribe('Test Value', () => {\n  it('value exist', () => {\n    let a = 'hi';\n    let b = null;\n    let c;\n\n    // 对象存在\n    assert.isOk(a);\n    expect(a).to.be.exist;\n    a.should.to.be.exist;\n\n    // 对象不存在\n    assert.isNotOk(b);\n    assert.isNotOk(c);\n    expect(b).to.be.not.exist;\n    expect(c).to.be.not.exist;\n    should.not.exist(b);\n    should.not.exist(c);\n  });\n});\n```\n\n5. 判断数组长度\n\n```javascript\nconst chai = require('chai');\nconst assert = chai.assert; // TDD\nconst expect = chai.expect; // BDD\nconst should = chai.should(); // BDD\n\ndescribe('Test Length', () => {\n  it('array length', () => {\n    let arr = [1, 2, 3];\n\n    // 判断数组长度\n    assert.lengthOf(arr, 3);\n    expect(arr).length(3);\n    arr.should.length(3);\n\n    // 小于4\n    expect(arr).length.below(4);\n    expect(arr).length.lessThan(4);\n    arr.should.length.below(4);\n\n    // 大于2\n    expect(arr).length.above(2);\n    arr.should.length.above(2);\n  });\n});\n```\n\n6. 空判断\n\n```javascript\nconst chai = require('chai');\nconst assert = chai.assert; // TDD\nconst expect = chai.expect; // BDD\nconst should = chai.should(); // BDD\n\ndescribe('Test Empty', () => {\n  it('array|string|object empty', () => {\n    let arr = [];\n    let a = '';\n    let b = {};\n\n    // 判断数组为空\n    assert.isTrue(arr.length === 0); // assert没有直接空判断，需要转换一下思路\n    expect(arr).empty;\n    arr.should.empty;\n    // 其他类型判断（注意：null和undefined不能用此方式判断）\n    expect(a).empty;\n    b.should.be.empty;\n  });\n});\n```\n\n**注意：还有其他较多的用法，如果理解了上面的这几种，按照同样的思路，结合api就基本能使用其他的模式了。一般情况下，以上的几种断言也足够我们使用了。**\n\n**注意2：更多断言，请参考： [chai断言API](http://chaijs.com/api/)**"
  },
  {
    "path": "测试相关/使用chai-http实现API测试.md",
    "content": "---\ntitle: 使用chai-http实现API测试\ndate: 2017/02/21 14:47:11\n---\n\n# 0x1、导言\n\n一个前后端分离的完整的项目中，一般少不了 `API TEST`，那我们如何来做API相关的测试呢？\n\n我们可以使用客户端工具（如PostMan），来进行模拟请求，还可以写一个小程序来请求待测试的API。\n\n既然也算是测试，为什么我们不直接和一般的 `unit test` 使用同样的工具呢？\n\n在我们使用 `mocha` 测试工具函数的同时，我们也可以结合 `chai-http` 来实现API的测试。\n\n# 0x2、关于 `chai-http`\n\n`chai-http` 官方定义是：一个HTTP响应的断言库，是 `Chai` 断言库的一个补充。（原文：HTTP Response assertions for the Chai Assertion Library. ）\n\n使用它，我们可以模拟发起HTTP请求，然后使用断言语法来判断响应是否满足需求。\n\n```javascript\nchai.request('rootpath')\n  .put('/user/me')\n  .send({ password: '123', confirmPassword: '123' })\n  .end(function (err, res) {\n     expect(err).to.be.null;\n     expect(res).to.have.status(200);\n  });\n```\n\n# 0x3、API测试演示"
  },
  {
    "path": "测试相关/利用Karma、Mocha搭建测试环境.md",
    "content": "---\ntitle: 利用Karma、Mocha搭建测试环境\ndate: 2017/02/21 14:47:11\n---\n\n"
  },
  {
    "path": "测试相关/利用Nightwatch.js实现e2e测试.md",
    "content": "---\ntitle: 利用Nightwatch.js实现e2e测试\ndate: 2017/02/21 14:47:11\n---\n\n"
  },
  {
    "path": "编写高质量JS代码的68个有效方法-读书笔记/[20140926]编写高质量JS代码的68个有效方法（一）.md",
    "content": "---\ntitle: 编写高质量JS代码的68个有效方法（一）\ndate: 2014/09/26 13:36:14\n---\n\n##No.1、了解你使用的JavaScript版本\n**Tips**：\n\n1. 决定你的应用程序支持JavaScript的哪些版本。\n2. 确保你使用的任何JavaScript的特性对于应用程序将要运行的所有环境都是支持的。\n3. 总是在执行严格模式检查的环境中测试严格代码。\n4. 当心连接那些在不同严格模式下有不同预期的脚本。\n\nJavaScript的普及使得它在1997年成为国际标准，官方名称为ECMAScript。除了ECMAScript标准存在多个版本之外，还存在一些JavaScript实现支持非标准特性，其他JavaScript实现不支持的情况。所以需要注意你所写的JavaScript代码所支持的版本。\n\n\t/*[Sample]如下代码，在IE下会Syntax error，但是在Chrome中则是定义常量*/\n\tconst PI=3.14;PI=3;PI\n由于JavaScript的主要生态系统--**Web浏览器**并不支持让程序员指定某个JavaScript版本来执行代码。在ES5中，引入了另外一种版本控制的考量--**严格格式（strict mode）**，这个特性允许你选择在受限制的JavaScript版本中禁用JavaScript语言中问题较多或易于出错的特性。由于JS语法涉及向后兼容，所以在没有严格检查的环境中也能执行严格代码。\n\t\n\t/*[Sample]如何使用严格模式，在程序/函数体的开始处加入'use strict'\n\t\t使用字符串字面量作为指令看起来比较怪异，但好处是可以向后兼容，因为执行字符串字面量没有任何副作用\n\t/*\n\tfunction f(x){\n\t\t'use strict';\n\t\tvar arguments=[];//SyntaxError:Unexpected eval or arguments in strict mode\n\t}\n\"use strict\"指令只有在脚本或者函数顶部才生效，这也是使用严格模式的一个陷进。脚本连接将变得颇为敏感。假如有多个js文件，一些需要执行在严格模式下，一些不需要执行在严格模式下，如何处理呢？\n\n1. 将需要严格模式检查的文件和不需要严格模式检查的文件分开连接\n2. 通过将自身包裹在立即调用的函数表达式中的方式来连接多个文件\n\n\t/*file1.js*/\n\tfunction fun1(){\n\t\tvar arguments=[];\n\t}\n\n\t/*file2.js*/\n\t'use strict';\n\tfunction fun2(){\n\t\tconsole.log('strict mode!');\n\t}\n\n\t/*按照方式二连接后的文件内容应该是*/\n\t/*fileMerge.js*/\n\t(function(){\n\t\tfunction fun1(){\n\t\t\tvar arguments=[];\n\t\t}\n\t})();\n\t(function(){\n\t\t'use strict';\n\t\tfunction fun2(){\n\t\t\tconsole.log('strict mode!');\n\t\t}\n\t})();\n\n##No.2、理解JavaScript的浮点数\n**Tips**：\n\n1. JavaScript的数字都是双精度的浮点数。\n2. JavaScript的整数仅仅是双精度浮点数的一个子集，而不是一个单独的数据类型。\n3. 位运算将数字视为32位的有符号整数。\n4. 当心浮点运算中的精度陷进。\n\n大部分语言都有几种数值数据类型，但是JavaScript只有一种\n\t\n\ttypeof 1;    //'number'\n\ttypeof 1.1;  //'number'\n\ttypeof -1;   //'number'\n\n对于位运算，JavaScript不会直接将操作数作为浮点数运算，会先转换为32位整数再进行运算\n\n\t8|1;    //9\n\t8.1|1;  //9\n如何快速从10进制转换到2~36进制？\n\n\t(100).toString(2);    //1100100\n\t(100).toString(10);   //100\n\t(100).toString(35);   //2u\n\t(100).toString(36);   //2s\n\n注意parseInt和parseFloat的用法\n\n**警告（以下为非标准特性，各浏览器执行有差异）：**\n\n1. 如果要转换的字符串已0x或者0X开头，那么parseInt('0xAB')等价于parseInt('0xAB',16)\n2. 如果遇到0开头，那么parseInt('013')等价于parseInt('013',8)\n3. **强烈建议在使用parseInt时指定进制**\n\n\tparseInt('9x');    //9 会自动忽略不能转换的字符\n\tparseInt('x9');    //NaN 发现第一个字符就不能转换，返回NaN\n\tparseInt('1100100',2);    //100 可以在parseInt的第二个参数指定当前字符串的进制\n\tparseInt('2xxx',2);    //NaN 遇到无法转换的情况，返回NaN\n\tparseInt('08');    //IE下:0,Chrome35下:8 \n\n浮点数是出了名的不精确，你能知道以下代码的执行结果吗？\n\n\t0.1+0.2;           //0.30000000000000004\n\t(0.1+0.2)+0.3;     //0.6000000000000001\n\t0.1+(0.2+0.3);     //0.6\n\t0.3-0.2;           //0.09999999999999998\n\n当我们关心精度时，要小心浮点数的局限性。有效的方法是尽可能的采用整数值运算，整数在运算时不需要舍入。\n\n##No.3、当心隐式的强制转换\n**Tips**：\n\n1. 类型错误可能被隐式的强制转换所隐藏。\n2. 重载的运算符+是进行加法运算还是字符串连接取决于其参数类型。\n3. 对象通过valueOf方法强制转换为数字，通过toString方法强制转换为字符串。\n4. 具有valueOf方法的对象应该实现toString方法，返回一个有valueOf方法产生的数字的字符串表示。\n5. 测试一个值是否为未定义的值，应该使用typeof或者与undeined进行比较而不是使用真值运算。\n\n\t3+true;   //4 true转换为数字1\n\t'fun'(1); //TypeError:string is not a function\n\tnull.x;   //TypeError: Cannot read property 'x' of null\n\t2+3;      //5\n\t2+'3';    //'23' 偏爱字符串，遇到字符串，那么优先用字符串连接\n\t1+2+'3';  //'33' 加法运算是从左到右，所以等价于(1+2)+'3'\n\t1+'2'+3;  //'123' \n\t'17'*3;   //51\n\t'8'|'1'   //9\n\n如何测试一个值是NaN？\n\n\tvar x=NaN;\n\tx===NaN;   //false，NaN不等于自身\n\n如果知道带测试的值是数字，那么可以使用标准库函数isNaN\n\t\n\tisNaN(NaN);  //true\n\n但是对于其他绝对不是NaN，但会被强制转换为NaN的值，使用isNaN方法是无法区分的。\n\t\n\tisNaN('foo');  //true\n\tisNaN(undefined);  //true\n\tisNaN({});   //true\n\tisNaN({valueOf:'foo'});  //true\n\n幸运的是，有一个既简单有可靠但有点不直观的方法测试它：\n\n**JS中，NaN是唯一一个不等于其自身的值。**\n\t\n\tvar x=NaN;\n\tx!==x //true\n\t\n\t/*测试x是否是NaN，是返回true，否则返回false*/\n\tfunction isReallyNaN(x){\n\t\treturn x!==x;\n\t}\n\n如何控制对象的强制转换？\n\t\n\t'J'+{toString:function(){return 'S'}};  //'JS' \n\t2*{valueOf:function(){return 3;}};  //6\n\t\n\tvar obj={\n\t\ttoString:function(){\n\t\t\treturn '[object Obj]';\n\t\t},\n\t\tvalueOf:function(){\n\t\t\treturn 1;\n\t\t}\n\t}\n\t'object:'+obj;  //'object:1'\n\n\t解释：\n\t1. 在需要数字的场合，优先判断valueOf，没有的话，则采用toString。\n\t2. 如果对象同时拥有valueOf和toString方法，同时又一定是需要数字的场合，那么JavaScript盲目的选择valueOf方法而不是toString方法来解决这种含糊的情况。\n\t3. 针对2：最好避免使用valueOf方法，除非对象的确需要一个数字的抽象，并且obj.toString()能产生一个obj.valueOf()的字符串的表示。\n\t\n关于真值运算：\n\n**JavaScript中有7个假值：false、0、-0、''、NaN、null和undefined，其他都为真值**\n\n##No.4、原始类型优于封装对象\n**Tips**：\n\n1. 当做相等比较是，原始类型的封装对象与其原始值行为不一样。\n2. 获取和设置原始类型值的属性会隐式地创建封装对象。\n\n除了对象以外，JavaScript有5个原始值类型：布尔值、数字、字符串、null和undefined。（令人困惑的是，对于null类型进行typeof操作得到的结果为\"object\"，然而，ECMAScript标准描述其为一个独特的类型。）\n\n\tvar s='hello';  \n\tvar sObj=new String(s);\n\ttypeof s;    //'string'\n\ttypeof sObj;   //'object' 包装对象的类型是object\n\n\tvar sObj1=new String(s);\n\tvar sObj2=new String(s);\n\tsObj1==sObj2;   //false\n\tsObj1===sObj2;  //false\n\n\t解释：可以理解为引用类型，每个对象是单独的对象，其引用是不一致的，所以只等于自身。\n\nJavaScript对基本类型有隐式封装，所以我们可以如下书写代码：\n\t\n\t'test'.toUpperCase(); //'TEST'\n \n\t'test'.test='test';\n\t'test'.test;   //undefined\n\n\t解释：对基本类型调用方法/设置属性时，会产生隐式封装。\n\t原始值->封装类型（产生封装对象）->封装对象执行方法/设置属性->返回原始值->抛弃封装对象。\n\t所以更新封装不会造成持久的影响，同时对原始值设置属性是没有意义的。\n\n\n##No.5、避免对混合类型使用==运算符\n**Tips**：\n\n1. 当参数类型不同时，==运算符应用了一套难以理解的隐式强制转换规则。\n2. 使用===运算符，使读者不需要设计任何的隐式强制转换就能明白你的比较运算。 \n3. 当比较不同类型的值时，使用你自己的显式强制转换使程序的行为更清晰。\n\t\n看代码：\n\t\n\t'1.0e0'=={valueOf:function(){return true;}}; //true 因为通过隐式转换，就变成了1==1，所以结果为true。\n\t\n\t转换为字符串：''+1; //'1'\n\t转换为数字  : +'1'; //1\n\n\tvar date=new Date('1999/12/31');\n\tdate=='1991/12/31';//false\n\tdate=='Fri Dec 31 1999 00:00:00 GMT+0800 (China Standard Time)';//true\n\t\n\t解释：世界上有太多的数据表现形式，JS需要知道你使用的是哪一种，==运算符并不能推断和统一所有的数据格式，所以更好的策略是显式自定义应用程序转换的逻辑，并使用严格相等运算符。"
  },
  {
    "path": "编写高质量JS代码的68个有效方法-读书笔记/[20141011]编写高质量JS代码的68个有效方法（二）.md",
    "content": "---\ntitle: 编写高质量JS代码的68个有效方法（二）\ndate: 2014/10/11\n---\n\n##No.6、了解分号插入的局限性\n**Tips：**\n\n1. 仅在“}”标记之前、一行的结束和程序的结束处推导分号\n2. 仅在紧接着的标记不能被解析的时候推导分号\n3. 在以(、[、+、-或/字符开头的语句前绝不能省略分号\n4. 当脚本连接的时候，在脚本之间显式的插入分号\n5. 在return、throw、break、continue、++或--的参数之前绝不能换行\n6. 分号不能作为for循环的头部和空语句的分隔符而被推导出\n7. **个人总结：尽量不要省略分号，不要让JS自动推导**\n\n分号仅在}标记之前、一个或多个换行之后和程序输入的结尾被插入，看代码：\n\t\n\t// 能自动推导分号\n\tfunction square(x){\n\t\tvar n = +x\n\t\treturn n*n\n\t}\n\n\t// Error，不能自动推导\n\tfunction square(x){var n = +x return n*n}\n\n分号仅在随后的输入标记不能被解析时插入，看代码：\n\t\n\ta=b\n\t(f());\n\t此时，代码等价于 ab(f());\n\t但是：\n\ta=b\n\tf()\n\t则会被解析为a=b f();\n\n在以(、[、+、-或/字符开头的语句前，绝不能省略分号，看代码：\n\n\ta=b\n\t['a','b','c'].forEach(function(key){\n\t\tconsole.log(key)\n\t})\n\t等价于\n\ta=b['a','b','c'].forEach(function(key){\n\t\tconsole.log(key);\n\t});\n\n\ta=1\n\t/Error/i.test('test')\n\t等价于\n\ta=1/Error/i.test('test');\n\n合并脚本时不能省略分号，看代码：\n\t\n\t//file1.js\n\t(function(){console.log('file1')})()\n\n\t//file2.js\n\t(function(){console.log('file2')})()\n\n\t//合并后 --输出file1，然后报错\n\t(function(){console.log('file1')})()(function(){console.log('file2')})()\n\n为了防止自己写的库在合并时内其他代码干扰，所以一般写法为如下代码：\n\t\n\t;(function(){ \n\t\t/*Code*/ \n\t})();\n\n在return、throw、break、continue、++或--的参数之前绝不能换行，看代码：\n\n\ta\n\t++\n\tb\n\t等价于：\n\ta;++b;\n\t\nfor循环中不要省略分号\n\t\n\t//Parse Error\n\tvar total=0\n\tfor(var i=0,total=1\n\t\ti<n\n\t\ti++){\n\t\ttotal*=i\n\t}\n\n**综上，再次强调，不加分号看起来代码轻量，但稍不注意就会引起很多bug，所以，建议都加上分号，不要让JS环境自行推导**\n\n##No.7、视字符串为16位的代码单元序列\n\n\t待定...\n\n##No.8、尽量少用全局对象\n**Tips：**\n\n1. 避免申明全局变量\n2. 尽量申明局部变量\n3. 避免对全局对象添加属性\n4. 使用全局对象来做平台特性检测\n\n定义全局变量会污染共享的公命名空间，并可能导致意外的命名冲突。全局变量不利于模块化，因为它会导致程序中独立组件间的不必要耦合。\n\n##No.9、始终声明局部变量\n**Tips：**\n\n1. 始终使用var声明新的局部变量\n2. 考虑使用lint工具来帮助检查未绑定的变量\n\n如果存在比全局变量更麻烦的事情，那就是意外的全局变量。由于不适用var申明的变量，统统为全局变量，所以一定要使用var来定义变量，防止变量污染。\n\n\tfunction test(){\n\t\ttest='test';\n\t}\n\ttest();\n\twindow.test;// 'test'\n\n##No.10、避免使用with\n**Tips：**\n\n1. 避免使用with语句\n2. 使用简短的变量名代替重复访问的对象\n3. 显式地绑定局部变量到对象属性上，而不要使用with语句隐式地绑定他们\n"
  },
  {
    "path": "编写高质量JS代码的68个有效方法-读书笔记/[20141030]编写高质量JS代码的68个有效方法（三）.md",
    "content": "---\ntitle: 编写高质量JS代码的68个有效方法（三）\ndate: 2014/10/30\n---\n\n##No.11、熟练掌握闭包\n**Tips：**\n\n1. 函数可以引用定义在其外部的作用域变量。\n2. 闭包比创建它们的函数有更长的生命周期。\n3. 闭包在内部存储其外部变量的引用，并能读写这些变量。\n\n\t//第一个事实：JavaScript允许你引用在当前函数以外定义的变量。\n\tfunction testClosures(){\n\t\tvar all = 'Test';\n\t\tfunction test(m){\n\t\t\treturn all + ' and ' + m;\n\t\t}\n\t\treturn test('closures');\n\t}\n\ttestClosures(); //'Test and closures'\n\n\t//第二个事实：即使外部函数已返回，当前函数仍然可以引用在外部函数所定义的变量。\n\tfunction testClosures(){\n\t\tvar all = 'Test';\n\t\tfunction test(m){\n\t\t\treturn all + ' and ' + m;\n\t\t}\n\t\treturn test;\n\t}\n\tvar t = testClosures(); \n\tt('closures'); //'Test and closures'\n\n\t//第三个事实：闭包可以更新外部变量的值\n\tfunction TestClass(){\n\t\tvar all;\n\t\treturn {\n\t\t\tset: function(value){\n\t\t\t\tall = value;\n\t\t\t},\n\t\t\tget: function(){\n\t\t\t\treturn all;\n\t\t\t}\n\t\t};\n\t}\n\tvar t = new TestClass();\n\tt.set('555');\n\tt.get();\n\n闭包的优缺点：\n优点： 变量保护、封装性，能够实现字段的可访问性(示例如下)\n\t\n\tfunction ModelClass(){\n\t\t//Property\n\t\tvar name,age=23;\n\t\treturn {\n\t\t\tsetName: function(value){ //设置名称\n\t\t\t\tname = value;\n\t\t\t},\n\t\t\tgetName: function(){ //获取名称\n\t\t\t\treturn name;\n\t\t\t},\n\t\t\tgetAge: function(){ //只读\n\t\t\t\treturn age;\n\t\t\t}\n\t\t};\n\t}\t\t\n\n缺点： 常驻内存，会增加内存使用量，使用不当和容易造成内存泄露。\n\n## No.12、理解变量申明提升\n1. 代码块中的函数申明会提升到函数顶部\n2. 重复申明变量被视为单个变量\n3. 考虑手动提升局部变量的申明，避免混淆（将函数内所需变量集中申明到函数顶部）\n\nJavaScript支持词法作用域，而不支持块级作用域\n\n\tfunction test(){\n\t\talert(a); //undefined\n\t\tvar a = 1;\n\t\talert(a);  //1\n\t}\n\ttest();\n\t以上代码等价于：\n\tfunction test(){\n\t\tvar a;\n\t\talert(a); //undefined\n\t\ta = 1;\n\t\talert(a);  //1\n\t}\n\ttest();\n\n一个例外是 *try...catch* :catch块中的变量作用域只在catch中。\n\t\n\tfunction test(){\n\t\tvar x = '1';\n\t\ttry{\n\t\t\tthrow ''\n\t\t}catch(x){\n\t\t\talert('error');\n\t\t\tx = '2';\n\t\t}\n\t\talert(x); // 1\n\t}\n\ttest();\n\n## No.13、使用立即调用的函数表达式创建局部作用域\n1. 理解绑定与赋值的区别\n2. 闭包通过引用而不是值捕获它们的外部变量\n3. 使用立即调用的函数表达式（IIFE）来创建具有作用域\n4. 当心在立即调用的函数表达式中包裹代码块可能改变其行为的情形\n\n看看以下代码段输出什么？\n\n\tfunction test(){\n\t\tvar arr = [1,2,3,4,5];\n\t\tvar result = [];\n\t\tfor(var i = 0, len = arr.length; i < len; i++){\n\t\t\tresult[i] = function(){\n\t\t\t\treturn arr[i];\n\t\t\t}\n\t\t}\n\t\treturn result;\n\t}\n\tvar result = test();\n\tresult[0](); \n\n可以通过立即调用表达式来解决JavaScript缺少块级作用域。如上代码可修改为:\n\n\tfunction test(){\n\t\tvar arr = [1,2,3,4,5];\n\t\tvar result = [];\n\t\tfor(var i = 0, len = arr.length; i < len; i++){\n\t\t\t(function(){\n\t\t\t\tvar j = i;\n\t\t\t\tresult[i] = function(){\n\t\t\t\t\treturn arr[j];\n\t\t\t\t}\n\t\t\t})(i);\n\t\t}\n\t\treturn result\n\t}\n\tvar result = test();\n\tresult[0]();\n\n##No.14、当心命名函数表达式笨拙的作用域\n1. 在Error对象和调试器中使用命名函数表达式改进栈跟踪\n2. 在ES3和有问题的JS环境中，函数表达式作用域会被Object.prototype污染\n3. 谨记在错误百出的JS环境中会提升命名函数表达式声明，并导致命名函数表达式的重复存储\n4. 考虑避免使用命名函数表达式或在发布前删除函数名\n5. 如果将代码发布到正确实现的ES5的环境中，没什么好担心的\n\n匿名和命名函数表达式的官方区别在于后者会绑定到与其函数名相同的变量上，该变量将作为该函数内部的一个局部变量。这可以用来写递归函数表达式。\n\n\tvar f = function find(tree, key){\n\t\tif(!tree){\n\t\t\treturn null;\n\t\t}\n\t\tif(tree.key === key){\n\t\t\treturn tree.value;\n\t\t}\n\t\t//函数内部可以访问find\n\t\treturn find(tree.left, key) || find(tree.right, key);\n\t}\n\n**结论：尽量避免使用命名函数表达式**\n\n## No.15、当心局部块函数声明笨拙的作用域\n1. 始终将函数声明置于程序或被包含的函数的最外层以避免不可移植的行为\n2. 使用var声明和有条件赋值语句替代有条件的函数声明\n\n\tfunction f(){\n\t\treturn 'global';\n\t}\n\tfunction test(x){\n\t\tvar result = [];\n\t\tif(x){\n\t\t\tfunction f(){\n\t\t\t\treturn 'local';\n\t\t\t}\n\t\t\tresult.push(f());\n\t\t}\n\t\tresult.push(f());\n\t\treturn result;\n\t}\n\ttest(true);\n\ttest(false);\n\n**结论：尽量将函数块定义为变量，防止函数提前**\n\n"
  },
  {
    "path": "编写高质量JS代码的68个有效方法-读书笔记/[20141129]编写高质量JS代码的68个有效方法（四）.md",
    "content": "---\ntitle: 编写高质量JS代码的68个有效方法（四）\ndate: 2014/11/29\n---\n\n##No.16、避免使用eval创建局部变量\n**Tips：**\n\n1. 避免使用eval函数创建的变量污染调用者作用域。\n2. 如果eval函数代码可能创建全局变量，将此调用封装到嵌套的函数中已防止作用域污染。\n\n执行eval时，eval中的变量才会被加到作用域中（函数作用域）\n\t\n\tfunction fun1(){\n\t\teval('var y = 1;');\n\t\tconsole.log('fun1->y:'+y); // 'fun->y:1'\n\t}\n\tfun1();\n\tconsole.log('global->y:'+y); //throw Error\n\n不要直接将不可控参数交给eval执行，可能会改变作用域对象。\n\n\t//Bad code\n\tvar g = 'global';\n\tfunction fun2(code){\n\t\teval(code);\n\t}\n\tfun2('var g=\"local\"');\n\tconsole.log(g) //'local'\n\n\t//Right code\n\tvar g = 'global';\n\tfunction fun2(code){\n\t\t(function(){\n\t\t\teval(code);\n\t\t})();\n\t}\n\tfun2('var g=\"local\"');\n\tconsole.log(g) //'global',嵌套作用域\n\n以上Right Code，如果执行不带var的变量申明，那么也是会影响全局的g对象的。\n\n## No.17、间接调用eval函数优于直接调用\n**Tips：**\n\n1. 将eval函数同一个毫无意义的字面量包裹在序列表达式中以达到强制使用间接调用eval函数的目的\n2. 尽可能间接调用eval函数，而不要直接调用eval函数\n\n直接调用eval，那么编译器无法优化JS代码。\n如何间接调用eval？\n\n\t(0,eval)(code) \n\n##No.18、理解函数的调用、方法调用及构造函数调用之间的不同\n**Tips：**\n\n1. 方法调用将被查找方法属性的对象作用调用接收者\n2. 函数调用将全局对象作为其接受者。一般很少使用该函数调用语法来调用方法\n3. 构造函数需要通过new运算符调用，并产生一个新的对象作为其接收者\n\n在全局对象上直接定义的function被称为函数，调用则是函数调用\n\n\tvar fun1 = function(p){\n\t\tconsole.log(p);\n\t};\n\t\n\tfunction fun2(p){\n\t\tconsole.log(p);\n\t}\n\t//函数调用\n\tfun1('p1');\n\tfun2('p2');\n\n如果对象的属性是函数，那么称之为方法，使用模式则是方法调用\n\n\tvar obj = {\n\t\tname: 'Hello ',\n\t\tfun1: function(name){\n\t\t\tconsole.log(this.name + name);\n\t\t}\n\t};\n\t//方法调用\n\tobj.fun1('Jay');\n\n**注意：fun1中通过this来访问obj的name属性**\n\n构造函数调用将一个全新的对象作为this变量的值\n\n\tfucntion User(name, age){\n\t\tthis.Name = name;\n\t\tthis.Age = age;\n\t}\n\t//此时，user是一个全新的对象\n\tvar user = new User('Jay', 23);\n\t\n##No.19、熟练掌握高阶函数\n**Tips：**\n\n1. 高阶函数是那些将函数作为参数或返回值的函数\n2. 熟练掌握现有库的高阶函数\n3. 学会发现可以被高阶函数所取代的常见编码模式\n\n需求：将数组元素全部转换为大写\n\n\t//常规做法\n\tvar arr = ['abc', 'test', '123'];\n\tfor(var i =0, len = arr.length; i < len; i++){\n\t\tarr[i] = arr[i].toUpperCase();\n\t}\n\tconsole.log(arr);\n\n\t//高阶函数\n\tvar arr = ['abc', 'test', '123'];\n\tarr = arr.map(function(item){\n\t\treturn item.toUpperCase();\n\t});\n\tconsole.log(arr);\n\n**注意：需要注意高阶函数使用时的返回值，有些是更改原始对象，有些是返回新对象**\n\n##No.20、使用call方法自定义接收者来调用方法\n**Tips：**\n\n1. 使用call方法自定义接收者（个人理解为作用域）来调用函数\n2. 使用call方法可以调用在给定对象中不存在的方法\n3. 使用call方法定义高阶函数允许使用者给回调函数指定接收者\n\n\tfunction fun1(){\n\t\tthis.name = 'Test';\n\t}\n\tvar obj = {\n\t\tname: 'Jay'\n\t};\n\tconsole.log(obj.name);\n\tfun1.call(obj);\n\tconsole.log(obj.name);\n\ncall函数的调用方式：\n\n\tf.call(obj, p1, p2, p3);"
  },
  {
    "path": "编写高质量JS代码的68个有效方法-读书笔记/[20141205]编写高质量JS代码的68个有效方法（五）.md",
    "content": "---\ntitle: 编写高质量JS代码的68个有效方法（五）\ndate: 2014/12/05\n---\n\n##No.21、使用apply方法通过不同数量的参数调用函数\n**Tips：**\n\n1. 使用apply方法自定一个可计算的参数数组来调用可变参数的函数\n2. 使用apply方法的第一个参数给可变参数的方法提供一个接收者\n\n\t//示例：计算给定数据的最大值\n\tfunction getMaxNum(){\n\t\tvar max = arguments[0];\n\t\tfor(var i = 1, len = arguments.length;i < len; i++){\n\t\t\tif(max < arguments[i]){\n\t\t\t\tmax = arguments[i];\n\t\t\t}\n\t\t}\n\t\treturn max;\n\t}\n\tgetMaxNum.apply(null,[1,3,4]);\n\n**该方法和call()方法功能基本类似，差别在于参数写法不一样。**\n\n##No.22、使用arguments创建可变参数的函数\n**Tips:**\n\n1. 使用隐式的arguments对象实现可变参数的函数\n2. 考虑对可变参数的函数提供一个额外的固定元数的版本，从而使用者无需借助apply方法。\n\n每一个函数内部都有一个arguments对象包含所有传递的参数\n\n\tfunction fun1(){\n\t\tconsole.log(arguments);\n\t}\n\tfun1('1');\n\tfun1(1,'2','str');\n\n\n##No.23、永远不要修改arguments的值\n**Tips：**\n\n1. 永远不要修改arguments的值\n2. 使用[].slice.call(arguments)将arguments对象赋值到一个真正的数组中再进行修改\n\narguments看起来像是数组，但是它并不是标准的数组，所以不支持数组的原型方法\n\n\tfunction fun1(nums){\n\t\tvar lastParam = arguments.pop(); //报错，undefined is not a function。\n\t\tconsole.log(arguments);\n\t}\n\t\n\tfun1([1, 2, 3]);\n\n正确的做法是，将arguments转换为真正的数组，再进行操作，代码如下：\n\n\tfunction fun1(nums){\n\t\tvar argArr = [].slice.call(arguments);\n\t\tvar lastParam = argArr.pop();\n\t\tconsole.log(arguments);\n\t}\n\t\n\tfun1([1, 2, 3]);\n\n**注意：永远不要修改arguments对象是更为安全的。**\n\n##No.24、使用变量保存arguments的引用\n**Tips：**\n\n1. 当引用arguments时当心函数嵌套层级\n2. 绑定一个明确作用域的引用到arguments变量，从而可以再嵌套的函数中引用它\n\n首先，先来看一段代码的输出：\n\n\tfunction fun1(){\n\t\tvar i = 0;\n\t\tconsole.log(arguments);\n\t\treturn {\n\t\t\tnext:function(){\n\t\t\t\treturn arguments[i++]; \n\t\t\t}\n\t\t}\n\t}\n\tvar f = fun1(1,2,3,4);\n\tconsole.log(f.next()); //猜猜是啥？\n\narguments是函数中的隐式变量，每个函数都会有这样的一个隐式对象。所以最后一个console的结果可想而知。所以遇到这种场景，是建议用变量保存arguments的引用，也能让嵌套函数正确的进行对象引用，正确代码如下：\n\n\tfunction fun1(){\n\t\tvar i = 0;\n\t\tvar args = arguments;\n\t\treturn {\n\t\t\tnext:function(){\n\t\t\t\treturn args[i++]; \n\t\t\t}\n\t\t}\n\t}\n\tvar f = fun1(1,2,3,4);\n\tconsole.log(f.next());\n\n##No.25、使用bind方法提取具有确定接收者的方法\n**Tips：**\n\n1. 要注意，提取一个方法不会将方法的接收者绑定到该方法的对象上\n2. 当给高阶函数传递对象方法时，使用匿名函数在适当的接收者上调用该方法\n3. 使用bind方法创建绑定到适当接收者的函数\n\n老规矩，看代码：（代码1）\n\n\tvar buffer = {\n\t\tentries: [],\n\t\tadd: function(value){\n\t\t\tthis.entries.push(value);\n\t\t},\n\t\tconcat: function(){\n\t\t\treturn this.entries.join('');\n\t\t}\n\t};\n\n该代码在直接使用时是没有问题的，思考下，由于高阶函数将函数/方法作为变量传递，那么可以有如下用法：（代码2）\n\n\tvar arr = ['Jay', '.M', '.Hu'];\n\tarr.forEach(buffer.add);\n\tconsole.log(buffer.concat()); //思考下这个结果是什么？\n\n以上代码在arr.forEach处已经报错，Cannot read property 'push' of undefined。因为这个时候的涉及到this的指向问题。我们可以改造下buffer代码，输出this让我们看看：（代码3）\n\n\tvar buffer = {\n\t\tentries: [],\n\t\tadd: function(value){\n\t\t\tconsole.log(this);\n\t\t\tthis.entries.push(value);\n\t\t},\n\t\tconcat: function(){\n\t\t\treturn this.entries.join('');\n\t\t}\n\t};\n\n从输出结果我们可以看到这个this，在（代码2）的执行环境中，指向的是window对象，所以导致了报错，那么如何避免这样的问题呢？针对forEach，我们有三个方法：(代码4)\n\n\t//方式一，去掉this，直接用buffer对象引用\n\tvar buffer = {\n\t\tentries: [],\n\t\tadd: function(value){\n\t\t\tbuffer.entries.push(value);\n\t\t},\n\t\tconcat: function(){\n\t\t\treturn buffer.entries.join('');\n\t\t}\n\t};\n\n\t//方式二，指定接收者，forEach方法提供，其他方法不一定提供\n\tvar arr = ['Jay', '.M', '.Hu'];\n\tarr.forEach(buffer.add, buffer);\n\tconsole.log(buffer.concat());\n\n\t//方式三，通过用函数包装调用，来实现指定接收者\n\tvar arr = ['Jay', '.M', '.Hu'];\n\tarr.forEach(function(s){\n\t\tbuffer.add(s);\n\t});\n\tconsole.log(buffer.concat());\n\n针对这样的问题，ES5标准库中提供了一个bind()函数来实现这样的方法。只需要如下代码：\n\t\n\tvar arr = ['Jay', '.M', '.Hu'];\n\tarr.forEach(buffer.add.bind(buffer));\n\tconsole.log(buffer.concat());\n\n该bind()函数，利用buffer.add.bind(buffer)创建了一个新函数而不是修改了buffer.add函数。新函数行为就像原来函数的行为，但它的接收者被重新指定了。所以调用bind方法是安全的，即使是一个可能在程序的其他部分被共享的函数。\n\n\n"
  },
  {
    "path": "编写高质量JS代码的68个有效方法-读书笔记/[20141213]编写高质量JS代码的68个有效方法（六）.md",
    "content": "---\ntitle: 编写高质量JS代码的68个有效方法（六）\ndate: 2014/12/13\n---\n\n##No.26、使用bind方法实现函数柯里化\n**Tips：**\n\n1. 使用bind方法实现函数柯里化，即创建一个固定需求参数子集的委托函数\n2. 传入null或undefined作为接收者的参数来实现函数柯里化，从而忽略其接收者\n\n**什么是函数柯里化？**\n\n**将函数与其参数的一个子集绑定的技术称为函数柯里化，它是一种简洁的、使用更少引用来实现函数委托的方式。**\n\n\t//有一个组装URL的JS函数\n\tfunction bulidURL(protocol, domain, path){\n\t\treturn protocol + '://' + domain + '/' + path;\n\t}\n\n\t//需要一个path数组转换为url数组，那么一般做法是：\n\tvar urls = paths.map(function(path){\n\t\treturn bulidURL('http', 'www.hstar.org', path);\n\t});\n\n\t如果用bind实现函数柯里化，则是：\n\tvar buildURL2 = buildURL.bind(null, 'http', 'www.hstar.org');\n\tvar urls = paths.map(buildURL2);\n\n\t其中由于buildURL不引用this，那么在bind中使用null，忽略函数本身的接收者，然后用bind实现柯里化。\n\t使用buildURL.bind的参数+buildURL2的参数结合起来调用buildURL方法。\n\t可以在bulidURL中写console(arguments)来查看参数合集。\n\n##No.27、使用闭包而不是字符串来封装代码\n**Tips：**\n\n1. 当将字符串传递给eval函数以执行它们的API时，绝不要在字符串中包含局部变量引用\n2. 接受函数调用的API优于使用eval函数执行字符串的API\n\nJS中，函数是一个将代码作为数据结构存储的便利方式，这些代码可以后面被执行。所以可以在JS中编写富有表现力的高阶函数，如map，forEach。\n\n比较不好的设计，使用eval函数执行字符串。\n\t\n\t//定义一个函数，使用eval执行字符串\n\tfunction fun1(code){\n\t\teval(code);\n\t}\n\n\t//用法一：\n\tvar val = 0;\n\tfun1('console.log(val)');\n\n\t//用法二：\n\tfunction fun2(){\n\t\tvar val = 1;\n\t\tfun1('console.log(val)');\n\t}\n\tfun2(); //Error:val is not defined\n\n**警告：在使用eval的时候，作用域是全局作用域（window），如用法一的调用，刚好能够出正常结果；如果转移到函数体内，如用法二的调用，则会出现错误；最坏的情况是用法二调用时，全局作用域上刚好有个同名的变量（本例中为val），那么将会让结果无法预期。**\n\n好的做法，就是直接传递函数\n\n\tfunction fun1(){\n\t\t\n\t}\n\tfunction fun2(p, action){\n\t\tif(p === 1){\n\t\t\taction();\n\t\t}\n\t}\n\t\n\tfun2();\n\n##No.28、不要依赖函数对象的toString方法\n**Tips：**\n\n1. 调用函数的toString方法时，并没有要求JavaScript引擎能够精确的获取到函数的源代码\n2. 由于在不同的引擎下调用toString方法的结果可能不同，所以绝不要信赖函数源代码的详细细节\n3. toString方法的执行结果并不会暴露存储在闭包中的局部变量值\n4. 通常情况下，应该避免使用函数对象的toString方法\n\nJavaScript函数有一个非凡的特性，即将其源代码重现为字符串的能力。但是ECMAScript标准对toString返回的字符串没有任何要求，所以不同引擎产生的结果可能不同。甚至返回到字符串和该函数并不相关\n\n\n##No.29、避免使用非标准的栈检查属性\n**Tips：**\n\n1. 避免使用非标准的arguments.caller和arguments.callee属性，因为它们不具备良好的移植性\n2. 避免使用非标准的函数对象caller属性，因为在包含全部栈信息方面，它是不可靠的\n\n基本错误（不推荐使用）\n\n\tfunction getCallStack(){\n\t\tvar stack = [];\n\t\tfor(var f = getCallStack.caller; f; f = f.caller){\n\t\t\tstack.push(f);\n\t\t}\n\t\treturn stack;\n\t}\n\n**警告：该函数非常脆弱，如果某函数叜调用栈中出现了不止一次，那么栈检查会陷入死循环。同时使用caller在ES5的严格模式下会error。**"
  },
  {
    "path": "编写高质量JS代码的68个有效方法-读书笔记/[20141220]编写高质量JS代码的68个有效方法（七）.md",
    "content": "---\ntitle: 编写高质量JS代码的68个有效方法（七）\ndate: 2014/12/20\n---\n\n##No.30、理解prototype、getPrototypeOf和__proto__之间的不同\n**Tips：**\n\n1. C.prototype属性是new C() 创建的对象的原型\n2. Object.getPrototypeOf(obj)是ES5中检索对象原型的标准函数\n3. obj.__ proto__是检索对象原型的非标准方法\n4. 类是由一个构造函数和一个关联的原型组成的一种设计模式\n\n简单点说，就是prototype属性直接是创建的对象的原型；getPrototypeOf()是一个标准函数，来获取对象原型；而__ proto__则是不标准的原型属性。\n\n\t//定义一个类型\n\tfunction User(name, age){\n\t\tthis.name = name;\n\t\tthis.age = age;\n\t}\n\t//实例化类型\n\tvar user = new User('Jay', 23);\n\n\t//原型属性prototype作用在类对象上\n\tUser.prototype\n\t//非标准__proto__作用在对象实例上\n\tuser.__proto__\n\t//getPrototypeOf则是Object的一个方法，参数为实例对象\n\tObject.getPrototypeOf(user)\n\n\tObject.getPrototypeOf(user) === User.prototype; // true\n\tUser.prototype === user.__proto__; // true\n\t\n\n##No.31、使用Object.getPrototypeOf()函数而不要使用__ proto__属性\n**Tips：**\n\n1. 使用符合标准的Object.getPrototypeOf()函数而不要使用非标准的__ proto__属性\n2. 在支持__ proto__属性的非ES5环境中实现Object.getPrototypeOf()函数\n\n由于非标准属性不具有完全兼容性，所以容易出一些奇奇怪怪的问题，不建议使用。\n在支持__ proto__的非ES5标准环境下，使用下面代码来实现Object.getPrototypeOf()函数：\n\n\tif(typeof Object.getPrototypeOf === 'undefined'){\n\t\tObject.getPrototypeOf = function(obj){\n\t\t\tvar t = typeof obj;\n\t\t\tif(!obj || (t !== 'object' && t !== 'function')){\n\t\t\t\tthrow new TypeError('Not an object.');\n\t\t\t}\n\t\t\treturn obj.__proto__;\n\t\t}\n\t}\n \n##No.32、始终不要修改__ proto__属性\n**Tips：**\n\n1. 始终不要修改__ proto__属性\n2. 使用Object.create函数给对象设置自定义原型\n\n__ proto__很特殊，具有修改对象原型链的能力。修改了__ proto__属性可能会造成以下几个问题：\n\n1. 可移植性问题。并不是所有平台都支持改变对象原型的特性\n2. 性能问题。会使得引擎对JS代码的优化失效\n3. 行为不可预测。修改了__ proto__可能会破坏原有的继承体系\n\n\n##No.33、使构造函数和new操作符无关\n**Tips：**\n\n1. 通过使用new操作符或Object.create方法在构造函数中调用自身使得该构造函数与调用语法无关\n2. 当一个函数期望使用new操作符调用时，清晰地文档化该函数\n\n同31，我们来看一下User对象：\n\n\tfunction User(name, age){\n\t\tthis.name = name;\n\t\tthis.age = age;\n\t}\n\t//如果使用new，那么会创建全新对象\n\tvar user = new User('Jay', 23);\n\n\t//如果忘记使用new呢？\n\tvar user = User('Jay', 23)\n\t//这个时候，该句代码，相当于调用函数，此时this在一般情况下是window，在ES5严格模式下是undefined。\n\t//当是window的时候，则会污染全局变量name和age，造成无法预期的问题。\n\t//当是undefined的时候，则会直接导致一个即时错误。\n\t//由于User没有显式return，导致等号左边的user的值为undefined。\n为了避免以上问题，可能使用以下两种方式：\n\n\t//方式一：\n\t//通过在函数体判断，然后调用自身的方式来实现，一定会使用new。缺点是它需要额外的函数调用，对性能有影响。\n\tfunction User(name, age){\n\t\tif(!(this instanceof User)){\n\t\t\treturn new User(name, age);\n\t\t}\n\t\tthis.name = name;\n\t\tthis.age = age;\n\t}\n\n\t//方式二：\n\t//通过判断this，将正确的接收者赋值给self，其他函数体内需要用this的地方，全部用self代替。缺点是使用了再ES5环境中有效的Object.create()。\n\tfunction User(name, age){\n\t\tvar self = this instaceof User ? this : Object.create(User.prototype);\n\t\tself.name = name;\n\t\tself.age  =age; \n\t}\n\n\t//方式二补充，由于Object.create()只在ES5中生效，为了在旧环境中使用的话，可以使用以下方式扩充Object.create()。\n\tif(typeof Object.create === 'undefined'){\n\t\tObject.create = function(prototype){\n\t\t\tfunction C(){}\n\t\t\tC.prototype = prototype;\n\t\t\treturn new C();\n\t\t}\n\t}\n\n##No.34、在原型中存储方法\n**Tips：**\n\n1. 将方法存储在实例对象中将创建该函数的多个副本，因为每个实例都有一份副本\n2. 将方法存储于原型中优于存储在实例对象中\n\n将方法存储在原型上，那么多个实例对象会共享该原型方法。如果存储在实例上的，每创建一个实例则会创建一个函数副本，会占用更多的内存。\n\n##No.35、使用闭包存储私有数据\n**Tips：**\n\n1. 闭包变量是私有的，只能通过局部引用获取\n2. 将局部变量作为私有数据从而通过方法实现信息隐藏\n\n不多说，直接上代码：\n\n\tfunction User(name, age){\n\t\t// 私有对象\n\t\tvar privateObj = {\n\t\t\tname: name,\n\t\t\tage: age,\n\t\t\tsex: '男'\n\t\t}\n\t\t// 公开属性\n\t\treturn {\n\t\t\tname: privateObj.name,\n\t\t\tage: privateObj.age,\n\t\t\tsetAge: function(age){\n\t\t\t\tprivateObj.age = age;\n\t\t\t}\n\t\t}\n\t}\n\t\n\tvar user = new User('Jay', 23);\n\tconsole.log(user.name); // 'Jay'\n\tconsole.log(user.age);  // 23\n\tconsole.log(user.sex);  // undefined\n\tuser.setAge(25);\t\t\n\tconsole.log(user.age);  // 23\n\n思考：为什么最后一个user.age 是 23？？？\n\n修改如下呢：\n\n\tfunction User(name, age){\n\t\t// 私有对象\n\t\tvar privateObj = {\n\t\t\tname: name,\n\t\t\tage: age,\n\t\t\tsex: '男'\n\t\t}\n\t\t// 公开属性\n\t\treturn {\n\t\t\tname: privateObj.name,\n\t\t\tage: function(){\n\t\t\t\treturn privateObj.age;\n\t\t\t}\n\t\t\tsetAge: function(age){\n\t\t\t\tprivateObj.age = age;\n\t\t\t}\n\t\t}\n\t}"
  },
  {
    "path": "编写高质量JS代码的68个有效方法-读书笔记/[20141227]编写高质量JS代码的68个有效方法（八）.md",
    "content": "---\ntitle: 编写高质量JS代码的68个有效方法（八）\ndate: 2014/12/27\n---\n\n##NO.36、只将实例状态存储在实例对象中\n**Tips：**\n\n1. 共享可变数据可能会出问题，因为原型是被其所有的实例共享的\n2. 将可变的实例存储在实例对象中\n\n一般来说，由于原型属性指向的对象是所有实例共享的。所以不建议在原型指向的对象中存储共享数据。下面给一个简单的例子：\n\n\tvar Person = function(name){\n\t\tthis.name = name;\n\t};\n\tPerson.prototype = {\n\t\tchildren: [],\n\t\taddChild: function(childName){\n\t\t\tthis.children.push(childName);\n\t\t},\n\t\tgetChildren: function(){\n\t\t\treturn this.children;\n\t\t}\n\t};\n\t\n\tvar p1 = new Person('P1');\n\tvar p2 = new Person('P2');\n\tp2.addChild('P2_C1');\n\tconsole.log(p1.getChildren());\n\n结果比较明显。p2的孩子成p1的了。标准做法是将children存储在实例对象中。\n\n\tvar Person = function(name){\n\t\tthis.name = name;\n\t\tthis.children = [];\n\t};\n\tPerson.prototype = {\n\t\taddChild: function(childName){\n\t\t\tthis.children.push(childName);\n\t\t},\n\t\tgetChildren: function(){\n\t\t\treturn this.children;\n\t\t}\n\t};\n\n##No.37、认识到this变量的隐式绑定问题\n**Tips：**\n\n1. this变量的作用域总是有其最近的封闭函数所确定\n2. 使用一个局部变量（通常命名为self,me,that）使得this的绑定对于内部函数是可用的。\n\n老规矩，看一个简单的示例：\n\n\tvar testObj = {\n\t\ta1: 0,\n\t\tfun1: function(){\n\t\t\tfunction fun2(){\n\t\t\t\tconsole.log(this.a1);\n\t\t\t}\n\t\t\tfun2();\n\t\t}\n\t};\n\ttestObj.fun1();\n\n为什么会这样呢？因为this变量是以不同的方式被绑定的。每个函数都有一个this变量的隐式绑定。this变量是隐式的绑定到最近的封闭函数。针对以上的问题，可以有集中方法来处理，参考如下：\n\n\t//通过将this用变量self保存的方式实现\n\tvar testObj = {\n\t\ta1: 0,\n\t\tfun1: function(){\n\t\t\tvar self = this;\n\t\t\tfunction fun2(){\n\t\t\t\tconsole.log(self.a1);\n\t\t\t}\n\t\t\tfun2();\n\t\t}\n\t};\n\ttestObj.fun1();\n\n\t//通过call方法指定接收者（也可以用apply）\n\tvar testObj = {\n\t\ta1: 0,\n\t\tfun1: function(){\n\t\t\tfunction fun2(){\n\t\t\t\tconsole.log(this.a1);\n\t\t\t}\n\t\t\tfun2.call(this);\n\t\t}\n\t};\n\ttestObj.fun1();\n\n\t//通过bind来实现\n\tvar testObj = {\n\t\ta1: 1,\n\t\tfun1: function(){\n\t\t\tfunction fun2(){\n\t\t\t\tconsole.log(this.a1);\n\t\t\t}\n\t\t\tfun2.bind(this)();\n\t\t}\n\t};\n\ttestObj.fun1();\n\n\n##No.38、在子类的构造函数中调用父类的构造函数\n**Tips：**\n\n1. 在子类构造函数中显式地传入this作为显式的接收者调用父类的构造函数\n2. 使用Object.create函数来构造子类的原型对象以避免调用父类的构造\n\nJS中实现的继承：\n\n\tvar Animal = function(){\n\t\tthis.weight = 50;\n\t};\n\tAnimal.prototype.eat = function(){\n\t\tconsole.log('eat food...');\n\t};\n\t\n\tvar Dog = function(){\n\t\tAnimal.call(this);\n\t\tDog.prototype = Object.create(Animal.prototype);\n\t};\n\t\n\tvar dog = new Dog();\n\tconsole.log(dog.weight);\n\n\n##No.39、不要重用父类的属性名\n**Tips：**\n\n1. 留意父类使用的所有属性名\n2. 不要再子类中重用父类的属性名\n\n由于JS中，属性都是key-value存储，那么同名的属性指向同样的地址，所以以下代码：\n\n\tvar Animal = function(){\n\t\tthis.weight = 50;\n\t\tthis.id = ++Animal.nextId;\n\t};\n\tAnimal.nextId = 0;\n\tAnimal.prototype.eat = function(){\n\t\tconsole.log('eat food...');\n\t};\n\t\n\tvar Dog = function(){\n\t\tAnimal.call(this);\n\t\tthis.id = ++ Dog.nextId;\n\t\tDog.prototype = Object.create(Animal.prototype);\n\t};\n\tDog.nextId = 0;\n\t\n\tvar dog = new Dog();\n\tconsole.log(dog.id);\n\n两个类都试图给实例属性id写数据。\n\n##No.40、避免继承标准类\n**Tips：**\n\n1. 继承标准类往往会由于一些特殊的内部属性（如[[Class]]）而被破坏\n2. 使用属性委托优于继承标准类\n\n扩展标注库使得其功能更强大是很有诱惑力的，但不幸的是它们的定义具有很多特殊的行为，所以很难写出正确的子类。\n\n\tvar ArrayEx = function(){\n\t\tfor(var i = 0, len = arguments.length; i<len ; i++){\n\t\t\tthis[i] = arguments[i];\n\t\t}\n\t};\n\tArrayEx.prototype = Object.create(Array.prototype);\n\t\n\tvar ar = new ArrayEx('1', '2');\n\tconsole.log(ar.length) //猜猜结果是什么？\n\n原因分析：length属性只对在内部标记为“真正的”数组对象才起作用。直接继承的对象并没有继承\nArray的标记标签属性[[Class]]。测试如下：\n\n\tvar ar = new ArrayEx('1', '2');\n\tconsole.log(Object.prototype.toString.call(ar)); //[object Object]\n\tconsole.log(Object.prototype.toString.call([])); //[object Array]\n\t\nECMAScript标准库中干掉大多数构造函数都有类似的问题。基于这个原因，最好避免继承一下的标准类：\nArray,Boolean,Date,Function,Number,RegExp或String。\n\n要想实现类似的功能，可以采用属性委托的方式：\n\n\tvar ArrayEx = function(){\n\t\tthis.array = []\n\t\tfor(var i = 0, len = arguments.length; i<len ; i++){\n\t\t\tthis.array[i] = arguments[i];\n\t\t}\n\t};\n\tArrayEx.prototype.forEach = function(f, thisArg){\n\t\tif(typeof thisArg === 'undefined'){\n\t\t\tthisArg = this;\n\t\t}\n\t\tthis.array.forEach(f, thisArg);\n\t};\n\t\n\tvar ar = new ArrayEx('1sfdfsd', '2fdsfs');\n\tar.forEach(function(item, i){\n\t\tconsole.log(item);\n\t});\n"
  },
  {
    "path": "编写高质量JS代码的68个有效方法-读书笔记/[20150110]编写高质量JS代码的68个有效方法（九）.md",
    "content": "---\ntitle: 编写高质量JS代码的68个有效方法（九）\ndate: 2015/01/10\n---\n\n##No.41、将原型视为实现细节\n**Tips：**\n\n1. 对象是接口，原型是实现\n2. 避免检查你无法控制的对象的原型结构\n3. 避免检查实现在你无法控制的对象内部的属性\n\n我们可以获取对象的属性值和调用其方法，这些操作都不是特别在意属性存储在原型继承结构的哪个位置。只要其属性值保存很定，那么这些操作的行为也不变。简言之，原型是一种对象行为的实现细节。\n\n正是由于以上的特性，所以如果修改了实现细节，那么依赖于这些对象的使用者就会被破坏，而且还很难诊断这类bug。所以一般来说，对于使用者，最好不要干涉那些属性。\n\n##No.42、避免使用轻率的猴子补丁\n**Tips：**\n\n1. 避免使用轻率的猴子补丁\n2. 记录程序库所执行的所有猴子补丁\n3. 考虑通过将修改设置于一个导出函数中，使猴子补丁成为可选的\n4. 使用猴子补丁为缺失的标准API提供polyfills\n\n**何为猴子补丁？**\n\n由于对象共享原型，因为每一个对象都可以增加、删除或修改原型的属性。这个有争议的实践通常被称为猴子补丁。\n\n猴子补丁的吸引力在于它的强大，如果数组缺少一个有用的方法，那么我们可以自己扩展它。但是在多个库同时对数组进行不兼容扩展时，问题就来了，有可能调用方法之后的结果和预期不一致。\n\n危险的猴子补丁有一个特别可靠而且有价值的使用场景：polyfill。补齐标准所支持的方法。\n\n##No.43、使用Object的直接实例构造轻量级的字典\n**Tips:**\n\n1. 使用对象字面量构建轻量级字典\n2. 轻量级字典应该是Object.prototype的直接子类，以使for...in循环免受原型污染\n\nJavaScript对象的核心是一个字符串属性名称与属性值的映射表。\n\t\n\tvar dict = {\n\t  key1: 'value1',\n\t  key2: 'value2'\n\t};\n\tfor(var key in dict){\n\t  console.log('key='+ key + ',value=' + dict[key]);\n\t}\n\n在使用for...in时，要小心原型污染。\n\n\tfunction Dict(){\n\t  Dict.prototype.count = function(){\n\t    var c = 0;\n\t    for(var p in this){\n\t      c++;\n\t    }\n\t    return c;\n\t  }  \n\t}\n\t\n\tvar dict = new Dict();\n\tdict.name = 'jay';\n\tconsole.log(dict.count()); //结果是2，因为for...in会枚举出所有的属性，包括原型上的。\n\n所有人都不应当增加属性到Object.prototype上，因为这样做可能会污染for...in循环，那么我们通过使用Object的直接实例，可以将风险仅仅局限于Object.prototype。\n\n##No.44、使用null原型以防止原型污染\n**Tips：**\n\n1. 在ES5中，使用Object.create(null)创建的自由原型的空对象是不太容易被污染的\n2. 在一些较老的环境中，考虑使用{__proto__: null}\n3. 要注意``__proto__``既不标准，也不是完全可移植的，并且可能会在未来的JavaScript环境中去除\n4. 绝不要使用``__proto__``名作为字典的key，因为一些环境将其作为特殊的属性对待\n\n对构造函数的原型属性设置null或者是undefined是无效的：\n\n\tfunction Dict(){\n\t  \n\t}\n\tDict.prototype = null;\n\tvar dict = new Dict();\n\tconsole.log(Object.getPrototypeOf(dict) === null); // false\n\tconsole.log(Object.getPrototypeOf(dict) === Object.prototype); //true\n\n在ES5中，提供了标准方法来创建一个没有原型的对象：\n\n\tvar dict = Object.create(null);\n\tconsole.log(Object.getPrototypeOf(dict) === null); // true\n\n在不支持Object.create函数的旧的JS环境中，可以使用如下方式创建没有原型的对象：\n\n\tvar dict = {__proto__: null}\n\tconsole.log(Object.getPrototypeOf(dict) === null); // true\n\n**注意：在支持Object.create函数的环境中，尽可能的坚持使用标准的Object.create函数**\n\n##No.45、使用hasOwnProperty方法来避免原型污染\n**Tips：**\n\n1. 使用hasOwnProperty方法避免原型污染\n2. 使用词法作用域和call方法避免覆盖hasOwnProperty方法\n3. 考虑在封装hasOwnProperty测试样板代码的类中实现字典操作\n4. 使用字典类避免将``__proto__``作为key来使用\n\n即使是一个空的对象字面量也继承了Object.prototype的大量属性：\n\n\tvar dict = {}\n\tconsole.log('a' in dict); // false\n\tconsole.log('toString' in dict); // true\n\tconsole.log('valueOf' in dict); // true\n\n不过，Object.prototype提供了方法来测试字典条目：\n\n\tvar dict = {}\n\tconsole.log(dict.hasOwnProperty('a')); // false\n\tconsole.log(dict.hasOwnProperty('toString')); // false\n\tconsole.log(dict.hasOwnProperty('valueOf')); // false\n\n但是，如果在字典中存储一个同为“hasOwnProperty”的属性，那么：\n\t\n\tvar dict = {\n\t  hasOwnProperty: null\n\t}\n\tconsole.log(dict.hasOwnProperty('a')); // TypeError\n\n最安全的方法则是使用call：\n\n\tvar dict = {\n\t  hasOwnProperty: null\n\t}\n\tconsole.log({}.hasOwnProperty.call(dict, 'hasOwnProperty')); // true、\n\n最后，我们来看一个复杂的但更安全的字典类：\n\n\tfunction Dict(elements){\n\t  this.elements = elements || {};\n\t  this.hasSpecialProto = false;\n\t  this.specialProto = undefined;\n\t}\n\t\n\tDict.prototype.has = function(key){\n\t  if(key === '__proto__'){\n\t    return this.hasSpecialProto;\n\t  }\n\t  return {}.hasOwnProperty.call(this.elements, key);\n\t};\n\t\n\tDict.prototype.get = function(key){\n\t  if(key === '__proto__'){\n\t    return this.specialProto;\n\t  }\n\t  return this.has(key) ? this.elements[key] : undefined;\n\t};\n\t\n\tDict.prototype.set = function(key, value){\n\t  if(key === '__proto__'){\n\t    this.hasSpecialProto = true;\n\t    this.specialProto = value;\n\t  }else{\n\t    this.elements[key] = value;\n\t  }\n\t};\n\t\n\tDict.prototype.remove = function(key){\n\t  if(key === '__proto__'){\n\t    this.hasSpecialProto = false;\n\t    this.specialProto = undefined;\n\t  }else{\n\t    delete this.elements[key];\n\t  }\n\t};\n\t\n\t// 测试代码\n\tvar dict = new Dict();\n\tconsole.log(dict.has('__proto__')); // false"
  },
  {
    "path": "编写高质量JS代码的68个有效方法-读书笔记/[20150123]编写高质量JS代码的68个有效方法（十）.md",
    "content": "---\ntitle: 编写高质量JS代码的68个有效方法（十）\ndate: 2015/01/23\n---\n\n##No.46、使用数组而不要使用字典来存储有序集合\n**Tips:**\n\n1. 使用for...in 循环来枚举对象属性应当与顺序无关\n2. 如果聚集运算字典中的数据，确保聚集操作与顺序无关\n3. 使用数组而不是字典来存储有序集合\n\n由于标准允许JavaScript引擎自由选择顺序，那么如果用字典存储有序数据，就会导致兼容性问题。\n\n##No.47、绝不要在Object.prototype中增加可枚举的属性\n**Tips：**\n\n1. 避免在Object.prototype中增加属性\n2. 考虑编写一个函数代理Object.prototype方法\n3. 如果你是在需要在prototype中增加属性，请使用ES5中的Object.defineProperty方法将它们定义为不可枚举的属性\n\n``for...in``循环非常便利，但是容易受到原型污染。如果在Object.prorotype中增加可枚举属性的话，将会导致大多数``for...in``循环受到污染。\n\n如果是在是要在Object.prototype上定义属性的话，可以使用如下代码：\n\n\tObject.defineProperty(Object.prototype, 'allKeys', {\n\t  value: function(){\n\t    var arr = [];\n\t    for(var key in this){\n\t      arr.push(key);\n\t    }\n\t    return arr;\n\t  },\n\t  writable: true,\n\t  enumerable: false, //设置属性为不可枚举\n\t  configurable: true\n\t});\n\n测试代码：\n\n\tvar obj = {a: 1, b: 2};\n\tconsole.log(obj.allKeys()); // ['a', 'b']\n\n##No.48、避免在枚举期间修改对象\n**Tips：**\n\n1. 当使用``for...in`` 循环枚举一个对象的属性时，确保不要修改该对象\n2. 当迭代一个对象时，如果该对象的内容可能会在循环期间被改变，应该使用while循环或经典的for循环来代替``for...in``\n3. 为了在不断变化的数据结构中能够预测枚举，考虑使用一个有序的数据结构，例如数组，而不要使用字典\n\n在大部分编译型语言中，如果在迭代时修改对象属性，是会出现编译错误的。在js中，没有这样的编译机制，但是也尽量保证不要修改迭代对象。\n\n如果在被枚举时添加了新对象，并不一定能保证新添加的对象能被访问到：\n\n\tvar obj = {a: 1, b: 2};\n\tfor(var p in obj){\n\t  console.log(p);\n\t  obj[p + '1'] = obj[p] + 1;\n\t}\n\n遇到这样的场景，应当使用while和标准的for循环。\n\n##No.49、数组迭代要优先使用for循环而不是``for...in``循环\n**Tips:**\n\n1. 迭代数组的索引属性应当总是使用for循环而不是``for...in``循环\n2. 考虑在循环之前将数组的长度存储在一个局部变量中以避免重新计算数组长度\n\n猜测下面一段代码的结果？\n\t\n\tvar arr = [5, 6, 8, 10, 9];\n\tvar sum = 0;\n\tfor(var a in arr){\n\t  sum += a;\n\t}\n\tconsole.log(sum);\n\n要达到正确的结果，那么应该使用for循环\n\n\tvar arr = [5, 6, 8, 10, 9];\n\tvar sum = 0;\n\tfor(var i = 0, len = arr.length; i < len; i++){\n\t  sum += arr[i];\n\t}\n\tconsole.log(sum); //38\n\n再看一个比较极端的例子：\n\n\tvar arr = [5, 6, 8, 10, 9];\n\tarr.len = 4;\n\tfor(var p in arr){\n\t  console.log(p);\n\t}\n\n这个时候用``for...in``,完全是达不到预期效果的\n\n再来看一个对于数组长度缓存的测试代码：\n\n\tvar count = 0;\n\tconsole.time('t1');\n\twhile(count < 10000){\n\t  var arr = [5, 6, 8, 10, 9];\n\t  var sum = 0;\n\t  count++;\n\t  for(var i = 0, len = arr.length; i < len; i++){\n\t    sum += arr[i];\n\t  }\n\t}\n\tconsole.timeEnd('t1');\n\t\n\tcount = 0;\n\tconsole.time('t2');\n\twhile(count < 10000){\n\t  var arr = [5, 6, 8, 10, 9];\n\t  var sum = 0;\n\t  count++;\n\t  for(var i = 0; i < arr.length; i++){\n\t    sum += arr[i];\n\t  }\n\t}\n\tconsole.timeEnd('t2');\n\n结果，请自行复制代码执行。。。\n\n##No.50、迭代方法优于循环\n**Tips：**\n\n1. 使用迭代方法（如Array.prototype.forEach和Array.prototype.map）替换for循环使得代码更可读，并且避免了重复循环控制逻辑\n2. 使用自定义的迭代函数来抽象未被标准库支持的常见循环模式\n3. 在需要提前终止循环的情况下，仍然推荐使用传统的循环。另外some和every方法也可用于提前退出\n\n在使用循环的时候，在确定循环的终止条件时容易引入一些简单的错误：\n\n\tfor(var i = 0; i <= n; i++){}\n\tfor(var i = 1; i< n; i++){}\n\n比较庆幸的是，闭包是一种为这些模式建立迭代抽象方便的、富有表现力的手法。\n\n我们可以用以下代码来代替：\n\n\tvar arr = [1, 2, 3];\n\tarr.forEach(function(v, i){\n\t  console.log(v);\n\t});\n\n如果要创建新数组，那么可以用以下方式：\n\n\tvar arr = [1, 2, 3];\n\tvar arrNew = [];\n\t//方式一\n\tarr.forEach(function(v, i){\n\t  arrNew.push(v);\n\t});\n\t//方式二\n\tfor(var i = 0, len = arr.length; i < len; i++){\n\t  arrNew.push(arr[i]);\n\t}\n\n为了简化这种普遍操作，ES5中引入了Array.prototype.map方法：\n\n\tvar arr = [1, 2, 3];\n\tvar arrNew = arr.map(function(v){\n\t  return v;\n\t});\n\n同样，如果想提取满足条件的元素，ES5也提供了filter方法：\n\n\tvar arr = [1, 2, 3];\n\tvar arrNew = arr.filter(function(v){\n\t  return v > 1;\n\t});\n\tconsole.log(arrNew);\n\n在ES5中，针对数组也提供了some和every ,可以用来终止循环，但是实际意义等同于C#的Linq方法All和Any：\n\n\tvar arr = [1, 2, 3];\n\t\n\t//数组元素有一个>1就返回true，并终止循环\n\tvar b = arr.some(function(a){\n\t  return a>1;\n\t});\n\tconsole.log(b); //true\n\t\n\t//数组元素每个都<3，则返回true，否则返回false，并提前终止循环\n\tb = arr.every(function(a){\n\t  return a<3;\n\t});\n\tconsole.log(b); //false\n"
  },
  {
    "path": "编写高质量JS代码的68个有效方法-读书笔记/[20150214]编写高质量JS代码的68个有效方法（十一）.md",
    "content": "---\ntitle: 编写高质量JS代码的68个有效方法（十一）\ndate: 2015/02/14\n---\n\n##No.51、在类数组对象上附庸通用的数组方法\n**Tips:**\n\n1. 对于类数组对象，通过提取方法对象并使用其call方法来复用通用的Array方法\n2. 任意一个具有索引属性和恰当length属性的对象都可以使用通用的Array方法\n\nArray.proteotype中的标准方法被设计成其他对象可复用的方法，即使这些对象没有继承Array。很实际的一个例子就是 ``arguments`` ,示例如下：\n\n\t//define\n\tfunction fun(){\n\t  console.log(arguments);  // [1, 2, 3]\n\t  console.log(arguments instanceof Array) // false\n\t  arguments.forEach(function(argv){  //TypeError\n\t    console.log(argv)\n\t  });\n\t}\n\t\n\t//call\n\tfun(1, 2, 3);\n\n从结果来看，输出arguments和数组非常相似，通过instanceof来看，确实不是数组，所以arguments是类数组对象，但是在执行forEach的时候却TypeError。why？\n\n因为 ``arguments`` 没有继承Array.prototype,所以并不能直接调用forEach方法，但是可以提取forEach方法的引用并使用其call来调用，代码如下：\n\n\t//define\n\tfunction fun(){\n\t  [].forEach.call(arguments, function(argv){\n\t    console.log(argv);\n\t  });\n\t}\n\t\n\t//call\n\tfun(1, 2, 3);\n\n除了arguments之外，dom的NodeList也是类数组对象：\n\n\tvar nodes = document.getElementsByTagName('a');\n\tconsole.log(nodes);\n\tconsole.log(nodes instanceof Array); // false\n\n那么，到底怎样使得一个对象“看起来像数组”呢？有以下两个规则：\n\n1. 具有一个范围在0到2^32 - 1 的整型length属性\n2. length属性大于该对象的最大索引。索引是一个范围在0到2^32 -2 的整数，它的字符串表示的是该对象的一个key。\n\n鉴于以上规则，那么我们可以自己创建类数组对象：\n\n\tvar arrayLike = {0: 'a', 1: 'b', 2: 'c', length: 3};\n\tvar result = [].map.call(arrayLike, function(el){\n\t  return el.toUpperCase();\n\t});\n\tconsole.log(result); // ['A', 'B', 'C']\n\n特例，数组连接方法concat不是完全通用的。因为它会检查对象的[[Class]]属性，要想连接类数组对象，我们就需要先将类数组处理为数组：\n\n\tvar arrLike = {0: 'a', length: 1};\n\tvar arr = [].slice.call(arrLike);\n\tconsole.log(['A'].concat(arr)); // ['A', 'a']\n\n##No.52、数组字面量优于数组构造函数\n**Tips:**\n\n1. 如果数组构造函数的第一个参数是数字则数组的构造函数行为是不同的\n2. 使用数组字面量替代数组构造函数\n\n原因如下：\n\n**[] 比 new Array简洁**\n\n\tvar arr = [];\n\tvar arr = new Array();\n\n**使用new Array()，必须要确保没有人重新包转过Array变量**\n\n\tfunciton f(Array){\n\t\treturn new Array(1, 2, 3, 4, 5);\n\t}\n\tf(String); //new String(1)\n\n**使用new Array()，必须要确保没有人修改过全局的Array变量**\n\n\tArray = String\n\tnew Array(1, 2, 3); // new String(1)\n\n**使用new Array时，由于第一个参数类型不同，会导致二义性**\n\n\tnew Array('hello') 和 ['hello'] 等价\n\t[1] 和 new Array(1) 不等价，前者创建包含元素的1的数组，后则创建长度为1的数组。\n\n**所以，优先使用字面量，因为数组字面量具有更规范、更一致的语义。**\n\n##No.53、保持一致的约定\n**Tips：**\n\n1. 在变量命名和函数签名中使用一致的约定\n2. 不要偏离用户在他们的开发平台中很可能遇到的约定\n\n有良好的编码习惯，使用业界常规的编码规范，同时注意参数的顺序等。一句话概述：**保持代码的一致性**。\n\n##No.54、将undefined看做“没有值”\n**Tips：**\n\n1. 避免使用undefined表示任何非特定值\n2. 使用描述性的字符串值或命名布尔属性的对象，而不要使用undefined 或 null来代表特定应用标志\n3. 提供参数默认值应该采用测试undefined的方式，而不是检查arguments.length。\n4. 在允许0、NaN或空字符串为有效参数的地方，绝不要通过真值测试来实现参数默认值。\n\nundefined很特殊，当JavaScript无法提供具体的值时没救产生undefined。\n如只定义变量，不赋值；或者是对象中不存在属性；再者，函数无return语句都会产生undefined。\n\n\tvar x;\n\tconsole.log(x); //undefined\n\tvar o = {};\n\tconsole.log(o.p1); //undefined\n\tfunction fun(){\n\t  \n\t}\n\tconsole.log(fun()); //undefined\n\n未给函数参数提供实参则该函数参数值为undefined\n\n\tfunction fun(x){\n\t\treturn x;\n\t}\n\tconsole.log(fun()); //undefined\n\n将undefined看做缺少某个特定的值是公约。将它用于其他目的具有很高的风险：\n\n\t//假设highlight为设置元素高亮\n\telement.highlight('yellow'); //设置为黄色\n\t\n\t//如果要设置为随机颜色\n\t//方式一、如果遇到undefined则设置为随机\n\telement.highlight(undefined);\n\t\n\t//这样的方式通常会产生歧义\n\telement.highlight(config.highlightColor);\n\t//使用如上语句时，我们的期望一般是没有提供配置则使用默认色，但是由于undefined代表随机，那么破坏了这种常规思维。让代码变得难以理解。\n\t\n\t//更好的做法\n\telement.highlight('random');\n\t//或者是\n\telement.highlight({random: true});\n\n另一个提防undefined的地方是可选参数的实现。\n\n\tfunction fun(a, b){\n\t  if(arguments.length < 2){\n\t    b = 'xx';\n\t  }\n\t}\n\n如果使用 fun(a);调用，基本符合预期；但是如果使用fun(a, 'undefind');则不会执行if之内的语句，导致结果错误，如果测试是否为undefined有助于打造更为健壮的API。\n\n针对可选参数这个问题，另外一个合理的替代方案是：\n\n\tfunction fun(a, b){\n\t  b = b || 'xxx';\n\t}\n\n但是要注意，真值测试并不总是安全的。如果一个函数应该接受空字符串，0，NaN为合法值，那么真值测试就不该使用了。\n\n\t//Bad Use\n\tfunction Point(x, y){\n\t  this.x = x || 200;\n\t  this.y = y || 200;\n\t}\n\n以上代码有什么问题呢，因为使用 new Point(0, 0);会导致使用默认值，这样就偏离了预期。所以需要更严格的测试：\n\n\tfunction Point(x, y){\n\t  this.x = x === undefined ? 200 : x;\n\t  this.y = y === undefined ? 200 : y;\n\t}\n\n##No.55、接收关键字参数的选项对象\n**Tips：**\n\n1. 使用选项对象似的API更具可读性、更容易记忆\n2. 所有通过选项对象提供的参数应当被视为可选的\n3. 使用extend函数抽象出从选项对象中提取值的逻辑\n\n首先来看一个复杂的函数调用：\n\n\tfun(200, 200, 'action', 'green', true);\n\n一眼望去，完全不知所云。在体会到C#的可选参数的便利性的时候，肯定会想JavaScript要是有这样的用法就好了。\n\n幸运的是，JavaScript提供了一个简单、轻量的惯用法：选项对象。基本达到了可选参数的效果。\n\n\tfun({\n\t  width: 200,\n\t  height: 200,\n\t  action: 'action',\n\t  color: 'green',\n\t  ignoreError: true\n\t});\n\n相对来说，更繁琐一点，但是更易于阅读。另外一个好处就是，参数都是可选的。\n\n如果有必选参数，那么在设计API的时候。建议将它们独立于选项之外，其他语言也可借鉴这种思路。\n\n\t// options 为可选参数\n\tfunction fun(width, height, options){\n\t}\n\n通过extend组合可选参数和默认参数，可以让函数变得简洁和健壮。\n\n\tfunction fun(width, height, options){\n\t  var defaults = {\n\t    color: 'green',\n\t    ignoreError: false,\n\t    action: ''\n\t  }\n\t  //$.extend 可以理解为jQuery的方法\n\t  options = $.extend({}, defaults, options);\n\t  //do something...\n\t}"
  },
  {
    "path": "编写高质量JS代码的68个有效方法-读书笔记/[20150304]编写高质量JS代码的68个有效方法（十二）.md",
    "content": "---\ntitle: 编写高质量JS代码的68个有效方法（十二）\ndate: 2015/03/04\n---\n\n##No.56、避免不必要的状态\n**Tips：**\n\n1. 尽可能地使用无状态的API\n2. 如果API是有状态的，标示出每个操作与哪些状态有关联\n\n无状态的API简洁，更容易学习和使用，也不需要考虑其他的状态。如：\n\n\t'test'.toUpperCase(); // 'TEST'\n\n有状态的API往往会导致额外的声明，并增加复杂度。\n\n\n##No.57、使用结构类型设计灵活的接口\n**Tips：**\n\n1.  使用结构类型（也称为鸭子类型）来设计灵活的对象接口\n2.  结构接口更灵活、更轻量，所以应该避免使用继承\n3.  针对单元测试，使用mock对象即接口的替代实现来提供可复验的行为\n\n直接上代码：\n\n\tfunction Wiki(format){\n\t  this.format = format;\n\t}\n\t\n\tWiki.prototype.show = function(source){\n\t  var page = this.format(source);\n\t  return {\n\t    title: page.getTitle(),\n\t    author: page.getAuthor(),\n\t    content: page.getContent()\n\t  }\n\t}\n\n将format设计为结构类型，可以极大的增加设计的灵活性。\n\n##No.58、区分数组对象和类数组对象\n**Tips：**\n\n1. 绝不重载与其他类型有重叠的结构类型\n2. 当重载一个结构类型与其他类型时，先测试其他类型\n3. 当重载其他对象类型时，接收真数组而不是类数组对象\n\n**API绝不应该重载与其他类型有重叠的类型**\n\n最简单的判断数组与类数组，代码如下：\n\n\tx instanceof Array\n\n但是，在一些允许多个全局对象的环境中可能会有标准的Array构造函数和原型对象的多份副本。那么就有可能导致以上的测试结果不可信，所以在ES5引入了Array.isArray函数来判断是否是Array对象，通过检查对象内部[[Class]]属性值是否为Array来判定。在不支持ES5的环境中，可以使用标准的Object.prototype.toString方法测试一个对象是否为数组。\n\n\tfunction isArray(x){\n\t  return toString.call(x) === '[object Array]';\n\t}\n\n##No.59、避免过度的强制转换\n**Tips：**\n\n1. 避免强制转换和重载的混用\n2. 考虑防御性地监视非预期的输入\n\n看以下的函数：\n\t\n\tfunction square(x){\n\t  return x*x;\n\t}\n\t\n\tconsole.log(square('3'));  // 9 \n\n强制转换无疑是很方便的。但很多时候却会导致含糊不清。\n\n\tfunction fun(x){\n\t  x = Number(x);\n\t  if(typeof x === 'number'){\n\t    return x-1;\n\t  }else{\n\t    return x;\n\t  }\n\t}\n\n由于进行了Number(x)，那么后面的else是无法执行到的。如果不知道这个函数的细节，那么使用该函数则具有一定的模糊性。\n事实上，如果我们要更小心的设计API，我们可以强制只接受数字和对象。\n\n\tfunction fun(x){\n\t  if(typeof x === 'number'){\n\t    return x-1;\n\t  }else if(typeof x === 'object' && x){\n\t    return x;\n\t  }else{\n\t    throw new TypeError('expected number or array-like.');\n\t  }\n\t}\n\n这种风格更加谨慎的示例，被称为防御性编程。\n\n##No.60、支持方法链\n**Tips：**\n\n1. 使用方法链来连接无状态的操作\n2. 通过在无状态的方法中返回新对象来支持方法链\n3. 通过在有状态的方法中返回this来支持方法链\n\n无状态的API部分能力是讲复杂操作分解为更小的操作。如replace：\n\n\tfunction escapeHtml(str){\n\t  return str.replace(/&/g, '&amp;')\n\t            .replace(/</g, '&lt;');\n\t}\n\n如果不采用方法链方式，代码应该是以下这样：\n\n\tfunction escapeHtml(str){\n\t  var str1 = str.replace(/&/g, '&amp;');\n\t  var str2 = str1.replace(/</g, '&lt;');\n\t  return str2;\n\t}\n\n同样的功能，将会产生多个临时变量。消除临时变量使得代码更加可读，中间结果只是得到最终结果中的一个重要步骤而已。\n\n在有状态的API中设置方法链也是可行的。技巧是方法在更新对象时返回this，而不是undefined。如：\n\n\telement.setBackgroundColor('gray')\n\t       .setColor('red')\n\t       .setFontweight('bold');  "
  },
  {
    "path": "编写高质量JS代码的68个有效方法-读书笔记/[20150312]编写高质量JS代码的68个有效方法（十三）.md",
    "content": "---\ntitle: 编写高质量JS代码的68个有效方法（十三）\ndate: 2015/03/12\n---\n\n##No.61、不要阻塞I/O事件队列\n\n**Tips：**：\n\n1. 异步API使用回调函数来延缓处理代价高昂的操作以避免阻塞主应用程序\n2. JavaScript并发的接收事件，但会使用一个事件队列按序地处理事件处理程序\n3. 在应用程序事件队列中绝不要使用阻塞的I/O\n\nJavaScript程序是构建在事件之上的。在其他一些语言中，我们可能常常会实现如下代码：\n\n\tvar result = downFileSync('http://xxx.com'); \n\tconsole.log(result);\n\n以上代码，如果downFileSync需要5分钟，那么程序就会停下来等待5分钟。这样的函数就被称为同步函数（或阻塞函数）。如果在浏览器中实现这样的函数，那么结果就是浏览器卡住，等待下载完成后，再继续响应。那么，这将极大的影响体验。所以，在JavaScript中，一般使用如下方式：\n\n\tdownFileAsync('http://xxx.com', function(result){\n\t  console.log(result);\n\t});\n\tconsole.log('async');\n\n以上代码执行中，就算下载文件要5分钟，那么程序也会立马打印出“async”，然后在下载完成的时候，打印result出来。这样才能保证执行环境能正确响应客户的操作。\n\nJavaScript并发的一个最重要的规则是绝不要在应用程序事件队列中使用阻塞I/O的API。在浏览器中，甚至基本没有任何阻塞的API是可用的。其中XMLHttpRequest库有一个同步版本的实现，被认为是一种不好的实现，影响Web应用程序的交互性。\n\n在现代浏览器（IE10+(含)、Chrome、FireFox）中，提供了Worker的API，该API使得产生大量的并行计算称为可能。\n\n*如何使用？*\n\n首先，编写两个文件,第一个是task.js,如下：\n\n\t//task.js\n\tconsole.time('t1');\n\tvar sum = 0;\n\tfor(var i = 0; i < 500000000; i++){\n\t  sum += i;\n\t}\n\tconsole.log('test');\n\tconsole.timeEnd('t1');\n\tpostMessage('worker result:' + sum);\n\n然后是index.html，用于调用worker，代码如下：\n\n\t// index.html\n\t<button onclick=\"alert('aa')\">Test</button>\n\t<script>\n\t  var worker = new Worker('test.js'); \n\t  worker.onmessage = function(evt){\n\t    console.log(evt.data);\n\t  };\n\t</script>\n\n在index.html的JavaScript脚本中。使用``var worker = new Worker('test.js'); ``来实例化一个Worker，Worker的构造为：new Worker([string] url),然后注册一个onmessage事件，用于处理test.js的通知，就是test.js中的postMessage函数。test.js中的每一次执行postMessage函数都会触发一次Worker的onmessage回调。\n\n在静态服务器中访问index.html,可以看到输出为：\n\n\ttest\n\tt1: 2348.633ms\n\tworker result:124999999567108900\n\n再来看看Worker的优缺点，我们可以做什么：\n\n1. 可以加载一个JS进行大量的复杂计算而不挂起主进程，并通过postMessage，onmessage进行通信\n2. 可以在worker中通过importScripts(url)加载另外的脚本文件\n3. 可以使用 setTimeout(), clearTimeout(), setInterval(), and clearInterval()\n4. 可以使用XMLHttpRequest来发送请求\n5. 可以访问navigator的部分属性\n\n有那些局限性：\n\n1. 不能跨域加载JS\n2. worker内代码不能访问DOM\n3. 各个浏览器对Worker的实现不大一致，例如FF里允许worker中创建新的worker,而Chrome中就不行\n4. 不是每个浏览器都支持这个新特性\n\n更多信息，请参考：\n\n1. [https://developer.mozilla.org/zh-CN/docs/Web/Guide/Performance/Using_web_workers](https://developer.mozilla.org/zh-CN/docs/Web/Guide/Performance/Using_web_workers)\n2. [http://www.cnblogs.com/feng_013/archive/2011/09/20/2175007.html](http://www.cnblogs.com/feng_013/archive/2011/09/20/2175007.html)\n\n\n##No.62、在异步序列中使用嵌套或命名的回调函数\n**Tips：**：\n\n1. 使用嵌套或命名的回调函数按顺序地执行多个异步操作\n2. 尝试在过多的嵌套的回调函数和尴尬的命名的非嵌套回调函数之间取得平衡\n3. 避免将可被并行执行的操作顺序化\n\n想象一下如下需求，异步请数据库查找一个地址，并异步下载。由于是异步，我们不可能发起两个连续请求，那么js代码很可能是这样的：\n\n\tdb.lookupAsync('url', function(url){\n\t  downloadAsync(url, function(result){\n\t    console.log(result);\n\t  });\n\t});\n\n我们使用嵌套，成功解决了这个问题，但是当这样的依赖很多时，我们的代码可能是这样：\n\n\tdb.lookupAsync('url', function(url){\n\t  downloadAsync('1.txt', function(){\n\t    downloadAsync('2.txt', function(){\n\t      downloadAsync('3.txt', function(){\n\t        //do something...\n\t      });\n\t    });\n\t  });\n\t});\n\n这样就陷入了回调地狱。要减少过多的嵌套的方法之一就是将回调函数作为命名的函数，并将它们需要的附加数据作为额外的参数传递。比如：\n\n\tdb.lookupAsync('url', downloadUrl);\n\t\n\tfunction downloadUrl(url){\n\t  downloadAsync(url, printResult);\n\t}\n\t\n\tfunction printResult(result){\n\t  console.log(result);\n\t}\n\n这样能控制嵌套回调的规模，但是还是不够直观。实际上，在node中解决此类问题是用现有的模块，如async。\n\n##No.63、当心丢弃错误\n**Tips：**：\n\n1. 通过编写共享的错误处理函数来避免复制和粘贴错误处理代码\n2. 确保明确地处理所有的错误条件以避免丢弃错误\n\n一般情况下，我们的错误处理代码如下：\n\n\ttry{\n\t  a();\n\t  b();\n\t  c();\n\t}catch(ex){\n\t  //处理错误\n\t}\n\n对于异步的代码，不可能将错误包装在一个try中，事实上，异步的API甚至根本不可能抛出异常。**异步的API倾向于将错误表示为回调函数的特定参数，或使用一个附加的错误处理回调函数（有时被称为errbacks）**。代码如下:\n\n\tdownloadAsync(url, function(result){\n\t  console.log(result);\n\t}, function(err){ //提供一个单独的错误处理函数\n\t  console.log('Error:' + err);\n\t});\n\n多次嵌套时，错误处理函数会被多次复制，所以可以将错误处理函数提取出来，减少重复代码，代码如下：\n\n\tdownloadAsync('1.txt', function(result){\n\t  downloadAsync('2.txt', function(result2){\n\t    console.log(result + result2);\n\t  }, onError);\n\t}, onError);\n\n**在node中，异步API的回调函数第一个参数表示err，这已经成为一个大众标准**\n\n##No.64、对异步循环使用递归\n**Tips：**：\n\n1. 循环不能是异步的\n2. 使用递归函数在时间循环的单独轮次中执行迭代\n3. 在事件循环的单独伦次中执行递归，并不会导致调用栈溢出\n\n针对异步下载文件，如果要使用循环，大概是如下代码：\n\n\tfunction downloadFilesSync(urls){\n\t  for(var i = 0, len = urls.length; i < len; i++){\n\t    try{\n\t      return downloadSync(urls[i]);\n\t    }catch(ex){\n\t    }\n\t  }\n\t}\n\n以上代码并不能正确工作，因为方法一调用，就会启动所有的下载，并不能等待一个完成，再继续下一个。\n\n要实现功能，看看下面的递归代码：\n\n\tfunction downloadFilesSync(urls){\n\t  var len = urls.length;\n\t  function tryNextURL(i) {\n\t    if (i >= n) {\n\t      console.log('Error');\n\t      return; //退出\n\t    }\n\t    downloadAsync(urls[i], function(result){\n\t      console.log(result);\n\t\t  //下载成功后，尝试下一个。\t\n\t\t  tryNextURL(i + 1);\n\t    });\n\t  }\n\t  tryNextURL(0);// 启动递归\n\t}\n\n类似这样的实现，就能解决批量下载的问题了。\n\n##No.65、不要再计算时阻塞事件队列\n**Tips：**：\n\n1. 避免在主事件队列中执行代码高昂的算法\n2. 在支持Worker API的平台，该API可以用来在一个独立的事件队列中运行长计算程序\n3. 在Worker API 不可用或代价高昂的环境中，考虑将计算程序分解到事件循环的多个轮次中\n\n打开浏览器控制台，执行 ``while(true){}``，会是什么效果？\n\n**好吧，浏览器卡死了！！！**\n\n如果有这样的需求，那么优先选择使用Worker实现吧。由于有些平台不支持类似Worker的API，那么可选的方案是将算法分解为多个步骤。代码如下：\n\n\t//首先，将逻辑分为几个步骤\n\tfunction step1(){console.log(1);}\n\tfunction step2(){console.log(2);}\n\tfunction step3(){console.log(3);}\n\tvar taskArr = [step1, step2, step3];\n\t\n\tvar doWork = function(tasks){\n\t  function next(){\n\t    if(tasks.length === 0){\n\t      console.log('Tasks finished.');\n\t      return;\n\t    }\n\t    var task = tasks.shift();\n\t    if(task){\n\t      task();\n\t      setTimeout(next, 0);\n\t    }   \n\t  }\n\t  setTimeout(next, 0);\n\t}\n\t//启动任务\n\tdoWork(taskArr);\n\n##No.66、使用计数器来执行并行操作\n**Tips：**：\n\n1. JavaScript应用程序中的事件发生是不确定的，即顺序是不可预测的\n2. 使用计数器避免并行操作中的数据竞争\n\n先看一个简单的示例：\n\n\tfunction downFiles(urls){\n\t  var result = [],len = urls.length;\n\t  if(len === 0){\n\t    console.log('urls argument is a empty array.');\n\t    return;\n\t  }\n\t  urls.forEach(function(url){\n\t    downloadAsync(url, function(text){\n\t      result.push(text);\n\t      if(result.length === len){\n\t        console.log('download all files.');\n\t      }\n\t    });\n\t  });\n\t}\n\n有什么问题呢？result的结果和urls是顺序并不匹配，所以，我们不知道怎么使用这个result。\n\n如何改进？请看如下代码，使用计数器，代码如下：\n\n\tfunction downFiles(urls){\n\t  var result = [],len = urls.length;\n\t  var count = 0;// 定义计数器\n\t  if(len === 0){\n\t    console.log('urls argument is a empty array.');\n\t    return;\n\t  }\n\t  urls.forEach(function(url, i){\n\t    downloadAsync(url, function(text){\n\t      result[i] = text;\n\t      count++;\n\t      //计数器等于url个数，那么退出\n\t      if(count === len){\n\t        console.log('download all files.');\n\t      }\n\t    });\n\t  });\n\t}\n\n##No.67、绝不要同步地调用异步的回调函数\n**Tips：**：\n\n1. 即使可以立即得到数据，也绝不要同步地调用异步回调函数\n2. 同步地调用异步的回调函数扰乱了预期的操作序列，并可能导致意想不到的交错代码\n3. 同步地调用异步的回调函数可能导致栈溢出或错误的处理异常\n4. 使用异步的API，比如setTimeout函数来调用异步回调函数，使其运行于另外一个回合\n\n如果异步下载代码，优先从缓存拿数据，那么代码很可能是：\n\n\tvar cache = new Dict();\n\t\n\tfunction downFileWithCache(url, onsuccess){\n\t  if (cache.has(url)){\n\t    onsuccess(cache.get(url));\n\t    return;\n\t  }\n\t  return downloadAsync(url, function(text){\n\t    cache.set(url, text);\n\t    onsuccess(text);\n\t  });\n\t}\n\n以上代码，同步的调用了回调函数，可能会导致一些微妙的问题，**异步的回调函数本质上是以空的调用栈来调用，因此将异步的循环实现为递归函数是安全的，完全没有累计赵越调用栈控件的危险。**同步的调用不能保证这一点，所以，更好的代码如下：\n\n\tvar cache = new Dict();\n\t\n\tfunction downFileWithCache(url, onsuccess){\n\t  if (cache.has(url)){\n\t    setTimeout(onsuccess.bind(null, cache.get(url)), 0)\n\t    return;\n\t  }\n\t  return downloadAsync(url, function(text){\n\t    cache.set(url, text);\n\t    onsuccess(text);\n\t  });\n\t}\n\n##No.68、使用promise模式清洁异步逻辑\n**Tips：**：\n\n1. promise代表最终值，即并行操作完成时最终产生的结果\n2. 使用promise组合不同的并行操作\n3. 使用promise模式的API避免数据竞争\n4. 在要求有意的竞争条件时使用select（也被称为choose）\n\n一直以来，JavaScript处理异步的方式都是callback，当异步任务很多的时候，维护大量的callback将是一场灾难。所以Promise规范也应运而生，[http://www.ituring.com.cn/article/66566](http://www.ituring.com.cn/article/66566) 。\n\nPromise已经纳入了ES6，而且高版本的Chrome、Firefox都已经实现了Promise，只不过和现如今流行的类Promise类库相比少些API。\n\n看下最简单的Promise代码（猜猜最后输出啥？）：\n\n\tvar p1 = new Promise(function(resolve, reject){\n\t  setTimeout(function(){\n\t    console.log('1');\n\t    resolve('2');\n\t  }, 3000);\n\t});\n\t\n\tp1.then(function(val){\n\t  console.log(val);\n\t});\n\n如果代码是这样呢？\n\n\tvar p1 = new Promise(function(resolve, reject){\n\t  setTimeout(function(){\n\t    console.log('1');\n\t    //resolve('2');\n\t    reject('3');\n\t  }, 3000);\n\t});\n\t\n\tp1.then(function(val){\n\t  console.log(val);\n\t}, function(val){\n\t  console.log(val);\n\t});\n\n再来看一个Promise.all的示例：\n\n\tPromise.all([new Promise(function(resolve, reject){\n\t  setTimeout(function(){\n\t    console.log(1);\n\t    resolve(1);\n\t  }, 2000);\n\t}), new Promise(function(resolve, reject){\n\t  setTimeout(function(){\n\t    console.log(2);\n\t    resolve(2);\n\t  }, 1000);\n\t}), Promise.reject(3)])\n\t.then(function(values){\n\t  console.log(values);\n\t});\n\n``Promise.all([]).then(fn)``**只有当所有的异步任务执行完成之后，才会执行then。**\n\n接着看一个Promise.race的示例：\n\n\tPromise.race([new Promise(function(resolve, reject){\n\t  setTimeout(function(){\n\t    console.log('p1');\n\t    resolve(1);\n\t  }, 2000);\n\t}), new Promise(function(resolve, reject){\n\t  setTimeout(function(){\n\t    console.log('p2');\n\t    resolve(2);\n\t  }, 1000);\n\t})])\n\t.then(function(value){\n\t  console.log('value = ' + value);\n\t});\n\n结果是：\n \n\tp2\n\tvalue = 2\n\tp1\n\n``Promise.race([]).then(fn)``**会同时执行所有的异步任务，但是只要完成一个异步任务，那么就调用then。**\n\n**promise.catch(onRejected)是promise.then(undefined, onRejected) 的语法糖。**\n\n\n更多关于Promise的资料请参考：[https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise)\n\n第三方Promise库有许多，如：Q, when.js 等"
  },
  {
    "path": "运维&部署/Docker容器管理平台Humpback进阶-私有仓库.md",
    "content": "---\ntitle: Docker容器管理平台Humpback进阶-私有仓库\ndate: 2017-6-8 19:20:54\n---\n\n# Docker私有仓库\n\n在 `Docker` 中，当我们执行 `docker pull xxx` 的时候，可能会比较好奇，`docker` 会去哪儿查找并下载镜像呢？\n\n它实际上是从 `registry.hub.docker.com` 这个地址去查找，这就是Docker公司为我们提供的公共仓库，上面的镜像，大家都可以看到，也可以使用。\n\n**所以，我们也可以带上仓库地址去拉取镜像，如：`docker pull registry.hub.docker.com/library/alpine`，不过要注意，这种方式下载的镜像的默认名称就会长一些。**\n\n如果要在公司中使用 `Docker`，我们基本不可能把商业项目上传到公共仓库中，那如果要多个机器共享，又能怎么办呢？\n\n正因为这种需要，所以私有仓库也就有用武之地了。\n\n所谓私有仓库，也就是在本地（局域网）搭建的一个类似公共仓库的东西，搭建好之后，我们可以将镜像提交到私有仓库中。这样我们既能使用 `Docker` 来运行我们的项目镜像，也避免了商业项目暴露出去的风险。\n\n想想如下场景：\n\n有一个商业项目，需要部署到N台机器上（也就是分布式部署）。\n\n**1、常规做法**：生成部署文件，手动拷贝到各个服务器，调整各项配置，挨个运行。（大致耗时半小时）\n\n**2、常规做法高级版**：在每台服务器上安装FTP Server（实际上除非静态，否则不够用），或者是SVN Server（相对FTP Server，可以还原版本），相对常规做法，优化了手动拷贝这个部署。（大致耗时20分钟）\n\n**3、使用Docker的做法（前提是要部署的服务器要安装好docker环境）**：在某台服务器上构建好镜像，拷贝镜像到其他机器，启动镜像（大致耗时10分钟）。\n\n其中拷贝镜像的方式如下：\n```bash\n# 将docker镜像保存为tar文件。\ndocker save <image name> > <tar file address>\n# 如\ndocker save node-test > /tmp/node-test.tar\n\n# 拷贝这个tar文件到需要使用该镜像的服务器上（FTP，SCP等等）\n\n# 将tar文件文件加载为镜像\ndocker load < /tmp/node-test.tar\n\n# 接下来就可以通过镜像运行容器了。\n```\n\n**这种方式中，用到了Docker的优势，但是拷贝文件这个，实在是山寨。**\n\n**4、使用Docker+私有仓库的做法**：在某台服务器上构建好镜像，推送到私有仓库，在其他要部署的服务器上，拉取镜像，然后运行。\n\n**对比以上的几种方式，我们可以知道前三种都无法逃避拷贝文件，并登录到服务器这个操作，这也是操作慢的根源，当我们有了私有仓库之后，所有的步骤都差不多可以自动化了，可以说是大大提交的效率。**\n\n看到私有仓库有这么大的优势，肯定要一探究竟了吧。别急，接下来，我们就来看看如何部署和使用私有仓库，并利用 `Humpback` 来再次提高部署效率。\n\n# 搭建私有仓库\n\n既然是使用 `Docker` ，那毫无意外，私有仓库也是个容器化的东西。Docker官方早就为我们考虑了私有化部署的场景，所以，它提供了官方的私有仓库镜像：`registry`。接下来，我们就使用这个镜像来搭建私有仓库。\n\n首先，按照常规思路，我们先拉取镜像：`docker pull registry:2.6.1`。（建议带上Tag拉取）\n\n一般来说，有了镜像，我们就可以直接运行它就行了。\n\n为了定制一些配置，和在 `Humpback` 中使用，我们还需要提供一个定制化的配置文件（使用yml来编写配置文件），如下：\n\n```yml\n# config.yml 内容\nversion: 0.1\nlog:\n  fields:\n    service: registry\nstorage:\n  cache:\n    blobdescriptor: inmemory\n  filesystem:\n    rootdirectory: /var/lib/registry\nhttp:\n  addr: :7000\n  secret: docker-registry\n  headers:\n    X-Content-Type-Options: [nosniff]\n    Access-Control-Allow-Headers: ['*']\n    Access-Control-Allow-Origin: ['*']\n    Access-Control-Allow-Methods: ['GET,POST,PUT,DELETE']\nhealth:\n  storagedriver:\n    enabled: true\n    interval: 10s\n    threshold: 3\n```\n\n其中 `storage` 设置提交到仓库的镜像，应该存储在什么地方；`http` 节点中需要配置端口和安全码，其中关键的地方在于 `http.headers` 的配置。如上的配置，是为了能够跨域访问仓库API，这是要让仓库搭配 `Humpback` 必须的设置，Humpback会在浏览器端对仓库发起请求。\n\n**如果不设置 `http.secret`，会遇到如下错误：**\n```\nNo HTTP secret provided - generated random secret. \n```\n\n**还需要注意，`http.addr` 的写法，`:7000` 并不是错误的写法，不要省略了 `:` ，这代表使用所有地址的 `7000` 端口。**\n\n接着我们把这个配置文件放在 `/etc/docker/registry/` 目录下，然后就可以创建容器并运行了，命令如下: \n\n```bash\n# -p映射端口，格式为：主机端口:容器内部端口\n# -v映射volumn(目录或者文件)，格式为：主机目录:容器内目录\n# --name 设置容器名称\n# 最后的 `registry:2.6.1` 则是镜像名称\ndocker run -d -p 7000:7000 --restart=always \\\n -v /var/lib/registry/:/var/lib/registry/ \\\n -v /etc/docker/registry/config.yml:/etc/docker/registry/config.yml \\\n --name humpback-registry \\\n registry:2.6.1\n```\n\n运行好容器后，我们通过直接访问地址 `http://192.168.1.200:7000/v2/` 来检查仓库是否正常运行，当返回 `{}` 时，表示部署成功。\n\n## 推送镜像到私有仓库\n\n要推送镜像到私有仓库，需要先根据私有仓库地址来设定新标签。根据我的环境，我进行的操作如下：\n\n```bash\n# pull image from docker hub（从官方仓库拉取一个镜像）\ndocker pull alpine:3.6\n\n# 根据私有仓库，设定标签（必须） \n# 为镜像 `alpine:3.6` 创建一个新标签 `192.168.1.200:7000/alpine:3.6`\ndocker tag alpine:3.6 192.168.1.200:7000/alpine:3.6\n\n# 推送到私有仓库中\ndocker push 192.168.1.200:7000/alpine:3.6\n```\n\n**在推送到的时候，可能会遇到问题：`http: server gave HTTP response to HTTPS client`，因为默认是提交到 `https`，但我们的仓库是使用的http，此时要么创建一个https映射，要么将仓库地址加入到不安全的仓库列表中。**\n\n*如何将仓库地址配置到不安全仓库列表中？*\n\n使用如下步骤：\n\n```bash\n# 编辑 /etc/docker/daemon.json\nvi /etc/docker/daemon.json\n# 增加配置项\n{\n  ... # 其他配置项\n  \"insecure-registries\":[ # 关键配置项，将仓库将入到不安全的仓库列表中\n    \"192.168.1.200:7000\"\n  ]\n}\n# 重启Docker服务（CentOS 7.2）\nsystemctl restart docker\n```\n\n之后，再次执行 `docker push 192.168.1.200:7000/alpine:3.6` 就没问题了。\n\n通过访问 `http://192.168.1.200:7000/v2/alpine/tags/list` 就能看到刚才提交的镜像了。\n\n也可以通过 `http://192.168.1.200:7000/v2/_catalog` 来列出仓库中的镜像列表。\n\n# Humpback中使用私有仓库\n\n至此，我们已经安装好了私有仓库，接着，我们就需要在 `Humpback` 中来使用私有仓库。\n\n首先，需要在系统配置中启用私有仓库，并设置好我们的仓库地址，如下：\n\n![启用仓库](http://img.hstar.org/humpback-md-21.jpg)\n\n之后，我们就可以查看 `Hub` 功能了，截图如下：\n\n![查看私有仓库](http://img.hstar.org/humpback-md-22.jpg)\n\n接着，重点来了，我们来创建容器的时候，可以使用私有仓库的镜像了：\n\n![从私有仓库拉取镜像](http://img.hstar.org/humpback-md-23.jpg)\n\n\n# 结语\n\n`Humbpack` 已经在我公司稳定迭代1年多，是一套比较简单易用，又不失强大的Docker管理平台。\n\n有 `Docker` 运维需求，而又因为命令行的 `Swarm` 不够易用，强大的 `K8S`(Kubernetes) 难以部署和运维，那就赶快来尝试下 `Humpback`，**够用，易用，易部署**。\n\n同时，用来作为本地开发部署环境也是极好的。比如我就喜欢把各种数据库，各种尝鲜的程序让 `Humpback` 来管理，用之即来挥之即去。\n\n## 最后：**Humpback开源免费，Github地址是：[https://github.com/humpback/humpback](https://github.com/humpback/humpback)，要是喜欢，还望不吝给个 `Star`；如果觉得不好用，或者不够用，也欢迎给我们提 `Issue`，当然，能够有 `PR` 那就更好了。**\n\n"
  },
  {
    "path": "运维&部署/Docker部署：Mysql Master-Slave.md",
    "content": "---\ntitle: Docker部署：Mysql主从\ndate: 2019-2-18 20:21:32\n---\n\n# 0、前言\n\n博主有个应用，为了提高可用性，打算使用 Mysql 的主从复制。同时，由于 DB 也是部署在容器中的，所以在操作之后，也已此文记录一下。\n\n# 1、环境准备\n\n## 1.1、基础环境\n\n博主主力机为 Windows10，所以是直接 Hyper-V 创建的虚拟机安装 CentOS 7.6 操作系统。\n\n下文出现的宿主机命令，仅针对 Redhat/CentOS ，其他 Linux 系统，需要自行使用同类的命令。\n\n## 1.2、Docker 环境\n\n可直接根据：[https://docs.docker.com/install/linux/docker-ce/centos/](https://docs.docker.com/install/linux/docker-ce/centos/) 完成 Docker 安装。\n\n通过命令行：docker info 进行验证。\n\n## 1.3、目录规划\n\n**推荐将 mysql 的数据和配置放在宿主机目录上，所以需要提前规划目录。**\n\n为了演示，我们准备在一台机器上直接部署三个 mysql 实例，实现一主两从。那么目录准备如下：\n\n1. 首先 `mkdir /opt/db_storage` 创建数据库相关目录（我个人喜欢把/opt 作为存储目录）\n2. 按需创建三个目录，用于映射到容器。\n\n```bash\n/opt/db_storage/\n  mysql01/\n    data/ # 用于存放DB数据\n    conf/\n      my.cnf # 配置文件\n  mysql02/ # 子目录同 mysql01\n  mysql03/ # 子目录同 mysql01\n```\n\n# 2、配置、部署容器\n\n首先，提前准备好 mysql 镜像（也可以直接 docker run 的时候自动下载），如：mysql:8.0.15 mysql:5.7.22\n\n## 2.1、配置 my.cnf\n\n对于 Mysql Master-Salve 来说，第一个要点就是配置 my.cnf。\n\n假设我们把 mysql01 目录作为 Master，那么配置文件简化内容如下：\n\n```bash\n# mysql01/conf/my.cnf Master实例的配置\n[mysqld]\nlog-bin=mysql-bin # 开启二进制日志\nserver_id=1 # 配置server_id，经测试 server-id=1 也可以\n\n# mysql02/conf/my.cnf Slave1 实例的配置\n[mysqld]\nserver-id=12 # 根据个人喜好设置，但必须要唯一\n\n# mysql03/conf/my.cnf Slave2 实例的配置\n[mysqld]\nserver-id=13 # 根据个人喜好设置，但必须要唯一\n```\n\n**注意：主要就是 master 实例开启日志，然后 slave 实例配置唯一的 ID。**\n\n## 2.2、创建容器\n\n由于是一台机器上，运行三个实例，所以用 host 网络模式就不是太合适，所以我们选择使用桥接网络模式。\n\n因此，我们执行的命令如下：\n\n```bash\n# 创建 Master 容器\ndocker run --name mysql-master \\\n-v /opt/db_storage/mysql01/conf:/etc/mysql/conf.d \\\n-v /opt/db_storage/mysql01/data:/var/lib/mysql \\\n-p 8001:3306\n-e MYSQL_ROOT_PASSWORD=mima \\\n-d mysql:8.0.15\n\n# 创建 Slave1 容器\ndocker run --name mysql-master \\\n-v /opt/db_storage/mysql02/conf:/etc/mysql/conf.d \\\n-v /opt/db_storage/mysql02/data:/var/lib/mysql \\\n-p 8002:3306\n-e MYSQL_ROOT_PASSWORD=mima \\\n-d mysql:8.0.15\n\n# 创建 Slave2 容器\ndocker run --name mysql-master \\\n-v /opt/db_storage/mysql03/conf:/etc/mysql/conf.d \\\n-v /opt/db_storage/mysql03/data:/var/lib/mysql \\\n-p 8003:3306\n-e MYSQL_ROOT_PASSWORD=mima \\\n-d mysql:8.0.15\n```\n\n**这里面主要的区别，就是映射到容器的目录以及端口映射有差异。**\n\n至此，我们的容器也准备好了。\n\n# 3、启用主从复制\n\n经过前两个步骤的准备，现在我们只需要连接到 Mysql 实例，然后开启主从复制即可。\n\n**对于 Master 实例**，我们只需要确认下 log-bin 是否正常启用即可。所以连接到 Server 之后，执行如下命令即可：\n\n```sql\nshow master status;\n```\n\n结果如下图：\n\n![master result](https://blog-store.oss-cn-beijing.aliyuncs.com/201902/20190218233701.png)\n\n**对于 Slave 实例**，略麻烦一些，简单思考一下，如果我们要去复制（备份）一个 DB 的内容，很明显需要知道要到什么地方去复制。那么这个动作对应的语句就是：\n\n```sql\nCHANGE MASTER TO\nMASTER_HOST='192.168.1.201', -- Master 实例的IP\nMASTER_PORT=8001, -- Master 实例的端口\nMASTER_USER='root', -- Master 实例的用户名\nMASTER_PASSWORD='mima', -- Master 实例的用户名\nMASTER_LOG_FILE='mysql-bin.000003', -- 注意，此处很关键，需要配置 bin log 文件的名称，如上图 Master 状态。\nMASTER_LOG_POS=0; -- 这个更重要，标记从日志的哪个位置开始复制。\n```\n\n**注意，此处为了简单直接以 root 用户连接到 master，实际使用中，建议单独创建一个账号用于同步。**\n\n> 如何创建一个账号用于同步呢？\n\n```sql\n-- 创建用户，其中 mima 是登录密码，其中 % 表示不限制客户端IP。\nCREATE USER 'for-slave'@'%' IDENTIFIED WITH mysql_native_password BY 'mima';\n--也可以使用IP，限制客户端访问\nCREATE USER 'for-slave'@'192.168.1.201' IDENTIFIED WITH mysql_native_password BY 'mima';\n\n-- 对账户授权\nGRANT REPLICATION SLAVE ON *.* TO 'for-slave'@'%'\n\n-- 刷新授权表\nflush privileges;\n```\n\n**注意：以上操作需要在 Master 实例上执行。**\n\n以上仅仅是配置了从哪儿同步数据，那么要开始同步，则需要执行：\n\n```sql\nstart slave; -- 开始同步\n\n--当然，猜测一下， 停止同步就应该是\n-- stop slave;\n```\n\n接着，我们还是要确认下 Slave 的状态：\n\n```sql\nshow slave status;\n```\n\n结果如图：\n\n![slave status](https://blog-store.oss-cn-beijing.aliyuncs.com/201902/20190218234809.jpg)\n\n对于多个 Slave 实例，用同样的方式处理即可。\n\n**测试就比较直接，操作 Master 实例，查看 Slave 实例，是否也有同样的变化即可。**\n\n# 4、常见场景\n\n## 4.1、我想只复制指定的 DB，应该如何实现？\n\n这是一个非常常见的需求，一个 Server 下一般都会有很多的 DB，我们需要选择我们关注的 DB 来进行复制即可。可以有如下两种方式。\n\n### 方案一：让 master 服务器只记录指定 DB 的 binlog\n\n这个方案比较容易理解，直接从源头上只记录需要复制的表的日志。这样从库也只能复制这些变化。实现如下：\n\n```bash\n# 编辑 master 实例的 my.cnf 配置文件。加入如下节点\n[mysqld]\nlog-bin=mysql-bin\nserver_id=1\nbinlog-do-db=要复制的DB # 如果是有多个，多复制几行即可。\n\n# 那如果我只是想要排除几个DB呢？\nbinlog-ignore-db=要排除的DB # 如果是有多个，同样多复制几行。\n```\n\n**很明显，这个方案是从源头切掉了 binlog，如果后续要多复制几个 DB，那就不好处理，因为压根没有记录 binlog。**\n\n**遗留问题一：都配置上会有什么效果呢？**\n\n### 方案二：在 slave 服务器上设置需要复制的 DB（个人推荐）\n\n做法也和方案一比较类似，不过是在 slave 的配置中增加类似配置：\n\n```bash\n# 编辑 slave 实例的 my.cnf 配置\nreplicate-do-db=要复制的DB # 如果是有多个，多复制几行即可。\nreplicate-ignore-db=要排除的DB # 如果是有多个，多复制几行即可。\n```\n\n> 为了避免操作从库，建议在从库的配置中，增加 read_only=true，此配置对 root 账户无效。参考[https://dev.mysql.com/doc/refman/8.0/en/server-system-variables.html#sysvar_read_only](https://dev.mysql.com/doc/refman/8.0/en/server-system-variables.html#sysvar_read_only)。\n\n## 4.2、如果我一个 DB 已经有数据了（未开 binlog），现在想设置为 master-slave，怎么实现呢？\n\n这个也是博主实际的场景。我的做法是，现再现有的实例上，启用 binlog。然后再使用 mysqldump 将数据同步到从库（注意，此时从库还没有 start slave），同步完成后，再执行 start slave 来启用。\n\n# 5、一些过程中的问题\n\n1. Docker 报错： `WARNING: IPv4 forwarding is disabled. Networking will not work.`\n\n这是由于宿主机没有配置 IPV4 转发导致的，解决方案如下：\n\n```bash\nsudo vi /usr/lib/sysctl.d/00-system.conf # 修改配置\n\n# 添加如下行\nnet.ipv4.ip_forward=1\n\n# 保存好之后，重启网络服务\nsystemctl restart network\n```\n\n2. 使用 mysqldump 备份 DB 时，出现 `Table 'xxx' doesn't exist when using LOCK TABLES`\n\n这是由于 DB 中已经删除中该表，在该表的物理文件还在，进入 DB 表存储目录，对指定的 table 进行删除即可（慎重，不要误删）。\n\n3. 使用 mysqldump 备份时，出现 `Authentication plugin 'caching_sha2_password' cannot be loaded: /usr/lib64/mysql/plugin/caching_sha2_password.so: cannot open shared object file: No such file or directory\" when trying to connect`\n\n这是由于 Mysql 8.x 的安全策略，导致无法直接使用密码进行备份。解决办法就是直接连接到该 Mysql Server，执行如下修改：\n\n```sql\n-- 允许使用native password\nALTER USER 'root'@'%' IDENTIFIED WITH mysql_native_password BY '<root账户密码>';\n-- 刷新授权表\nflush privileges;\n```\n"
  },
  {
    "path": "运维&部署/一个简单易用的容器管理平台-Humpback.md",
    "content": "---\ntitle: 一个简单易用的容器管理平台-Humpback\ndate: 2017-05-20 09:09:58\n---\n\n\n# 什么是Humpback？\n\n在回答这个问题前，我们得先了解下什么的 `Docker`（哦，现在叫 `Moby`，文中还是继续称 ` Docker`）。\n\n在 [Docker-百度百科](http://baike.baidu.com/item/Docker) 中，对 `Docker` 已经解释得很清楚了。\n\n简单来说，Docker可以帮助我们以容器的方式快速运行APP。\n\n由于一个镜像就是一个完整的 `APP`，只要我们构建好镜像，我们就可以快速，一致的在多个地方运行同样的 `APP`。这虽然解决了 `APP` 一致性的这个问题，但是，我们在部署的时候，依然要远程到服务器上，拖镜像，通过一长串命令（端口映射，磁盘映射，环境变量等的配置）来启动Docker容器。\n\n这是一个重复而容易出错的过程，`Humpback` 就是为了解决该问题而生（当然，我们已经扩展了更多的功能：如集群管理，镜像构建，私有仓库管理等）。\n\n那现在来回答什么是 `Humpback`，`Humpback` 是一个简单易用的轻量级容器管理平台，一个私有仓库管理平台，一个容器调度平台。\n\n那它能做什么呢？\n\n1. 以 `Web界面` 的方式，来进行容器的创建/运行以及管理（启动，停止，重启，监控，应用版本升级/回退）。 ★★★★★\n2. 镜像构建，将镜像所需文件和Dockfile打包为 `.tar.gz` 文件，就可以打包为一个镜像 ★★\n3. 管理私有仓库，将自己部署好的私有仓库，纳入到humpback的管理中。 ★★★★\n4. 集群容器调度，当我们部署一个app时，只需要告诉humpback，我要部署多少个实例，humpback会自动根据配置进行集群部署，并通过WebHook通知部署结果。 ★★★\n5. 对容器管理进行权限设定。 ★★\n\n**注：星级表示功能的常用程度，五星为最常用的功能。**\n\n差点忘了贴官方文档和Github地址，罪过！\n\n[Humpback官方文档](https://humpback.github.io/humpback/)\n\n[Github 开源汇总地址](https://github.com/humpback/humpback)\n\n[Github Humpback 组织](https://github.com/humpback)\n\n# 部署Humbpack\n\n说了这个多，有没有想尝试下使用 `Humbpack` 进行容器管理？易用，不仅体现在真正的使用上，还需要能够简单部署。\n\n接着，我们就看一下应该如何部署 `Humpback`：\n\n1. 安装 `Docker` （如果是Windows机器，那通过虚拟机（Hyper-V or Vmware）安装Linux就算是第0步吧）\n\n这是前置条件（需要1.8.3以上），关于Docker的安装，我就不详细说明了，我相信有兴趣体验Humbpack的，Docker安装肯定不在话下。\n\n2. 部署 `Humpback` 管理站点\n\n既然是用的Docker，那毫无疑问，我们已经把Humpback-web打包成了一个镜像，只需要pull下来即可使用。在确认docker已经安装成功的前提下，执行如下命令，即可安装好 Humbpack 的管理站点。\n\n```bash\n# 创建一个目录，用来存储humpback-web的数据库文件\nmkdir -p /opt/app/humpback-web\n\n# 完整粘贴即可，利用docker启动容器。其中如果要修改监听端口，就把8000改掉。\ndocker run -d --net=host --restart=always \\\n-e HUMPBACK_LISTEN_PORT=8000 \\\n-v /opt/app/humpback-web/dbFiles:/humpback-web/dbFiles \\\n--name humpback-web \\\nhumpbacks/humpback-web:1.0.0 \n```\n\n启动成功之后，访问 `http://localhost:8000` 来确定是否部署成功。\n如果要在宿主机访问，请使用虚拟机绑定的IP地址，另外，需要注意防火墙。\n如果能够在浏览器中看到登录界面，那么就可以输入默认超级管理员账户：`admin`，密码：`123456` 进行登录。\n\n3. 部署 `Humpback-agent`\n\n从项目名称就很容易看出，这货就是一个代理，为Humpback管理站点提供数据的。\n\n需要先在要被管理的机器上安装Docker环境（如果就在humpback这台虚拟机中试验，可以跳过，因为已经安装Docker），之后输入 `docker version` 查看一下版本号，我们主要关注其中的 `API Version`（待会要用）。\n\n老规矩，我们的 `humpback-agent` 也必然是一个镜像，那么执行如下方式安装下：\n\n```bash\n# 为了简单使用，先不考虑集群功能\n# 注意，之前我们在 docker version 中记录的 API Version 要排上用场了，\n# 以下命令中有个环境变量 DOCKER_API_VERSION ，需要被设定为我们记录的API VERSION的值。\ndocker run -d -ti --net=host --restart=always \\\n-e DOCKER_API_VERSION=v1.21 \\\n-v /var/run/:/var/run/:rw \\\n--name=humpback-agent \\\nhumpbacks/humpback-agent:1.0.0\n```\n\n当启动成功之后，我们的 `humpback-agent` 也部署成功了。\n\n4. 将机器（虚拟机）纳入Humpback管理\n\n打开我们第2步运行起来的Web管理平台，登录之后，创建一个Group:\n![Humpback Group List](http://img.hstar.org/humpback-demo-img-1.png)\n并将部署了 `humpback-agent` 的机器IP，添加到Servers属性中，如图：\n![Humpback Add Group](http://img.hstar.org/humpback-demo-img-2.jpg)\n，然后进入我们的Group就能看到我们的机器和容器了。\n![Humpback Add Group](http://img.hstar.org/humpback-demo-img-3.jpg)\n**至此，我们的Humback已经可用了，当然，这仅仅部署了一部分功能。如果需要私有仓库和集群，参考官方文档进行部署即可。**\n\n\n# 举个栗子\n\n光说不练，等于白干。现在，我们已经部署好 `Humpback` 了，那我们就来简单使用下。\n\n## 场景一\n\n假设有如下场景，我们想要部署一个Redis来做缓存。\n\n官方提供的操作过程：\n\n```bash\n# 远程登录到服务器之后\n$ wget http://download.redis.io/releases/redis-3.2.8.tar.gz\n$ tar xzf redis-3.2.8.tar.gz\n$ cd redis-3.2.8\n$ make\n```\n\n其中可能会遇到的问题：\n1. wget没装，手动安装wget。\n2. make redis 出错，需要安装依赖项\n3. 需要先登录到服务器\n4. 等等\n\n但是如果用Humpback呢？\n\n1. 打开Web管理界面的添加容器页面\n2. 填写容器相关信息如下：\n3. 点击确定，OK，妥了。\n\n>在这样的场景下，两者的差距并不大。\n\n接着，我想临时停一下Redis：\n\n常规做法：\n1. 登录到服务器（又要登录，烦不烦）\n2. 找到这个服务（通过名称找，哎呀，命令记不住了）\n3. 停止\n\n如果用Humpback呢？\n\n1. 在容器管理界面，点击Stop，OK，妥了\n\n>此时有 `Humpback` 就简单多了。\n\n再接着，发现一台redis不够了，又一台服务器，需要部署redis。\n\n常规做法：只能把之前的部署步骤再做一次。\n\n如果这台服务器也被Humpback管理中，那么用Humpback仅仅是创建一个容器的事情。\n\n\n## 场景二\n\n在场景一中，我们是使用三方库，那这个场景呢，我们用来部署一个Web程序。\n\n常规部署过程：\n\n1. 打包程序\n2. 登录服务器，把程序拷贝到服务器上\n3. 修改配置文件\n4. 运行程序\n\n> 如果有多台，那么就要重复N次这样的步骤\n\n那如果是Humpback呢？\n\n1. 构建镜像\n2. 创建容器并运行\n\n> 如果有多台要部署，只需要批量创建容器即可，一次搞定\n\n第二天，发布了一个新版本\n\n常规部署过程：\n\n1. 重新打包程序，拷贝到服务器上（还要担心覆盖出错）\n2. 修改配置文件\n3. 重新启动程序\n\nHumpback中：\n1. 构建新版本镜像\n2. 在容器管理中，点击升级，选择新版本即可。\n\n> 在这个过程，差异还不是太明显，不过明显humpback更快\n\n第三天，发现新版本程序，有个严重bug（没有bug的程序不是好程序）\n\n这下常规部署过程就折腾了，相当于重新发布一个历史版本，而且此时还不能保证和历史版本一致。\n\n但，如果是humpback，在容器管理界面，点击升级（实际可以升级/降级），然后选中历史版本，点击确定，就完完整整的还原到历史版本上去了。\n\n\n# 总结\n\n总之，Humpback好用，有需要就赶紧体验下吧。暂时不需要，也可以体验下，吹牛也能多一些套路，而且，万一以后用得到呢。\n\n当然，此文提到的仅仅的基础用法，但我觉得，这足够了。要想体验更复杂的玩法，强烈建议参考官方文档。\n\n另附上几张操作图：\n\n创建容器界面：\n![创建容器界面](http://img.hstar.org/humpback-demo-img-4.jpg) \n\n容器详细信息界面：\n![容器详细信息界面：](http://img.hstar.org/humpback-demo-img-5.jpg) \n\n容器版本升级界面\n![容器版本升级界面](http://img.hstar.org/humpback-demo-img-6.jpg)  \n\n容器监控界面\n![容器监控界面](http://img.hstar.org/humpback-demo-img-7.jpg)  \n"
  },
  {
    "path": "运维&部署/前端监控系统实现.md",
    "content": "# 0、前言\n\n一个强大，好用的统计分析系统，能够非常方便的将用户访问数据，站点性能数据，各种统计信息进行汇总展示。这对于有点体量的公司都是非常重要的。\n\n# 1、客户端信息收集\n\n## 1.1、基本信息\n\n要收集浏览器信息，一般是通过 `navigator` 对象，从这里面，可以获取到：\n\n```js\nnavigator.language // 浏览器当前语言\nnavigator.userAgent // 浏览器信息\nnavigator.platform // 操作系统平台\nnavigator.javaEnabled() // 是否支持Java\nnavigator.maxTouchPoints // 触摸点个数\n```\n\n其次，还会通过 `screen` 收集屏幕信息：\n\n```js\nscreen.width // 屏幕宽度\nscreen.height // 屏幕高度\nscreen.colorDepth // 屏幕颜色bit\n```\n\n通过 `document` 获取文档相关信息\n\n```js\ndocument.referrer // 来源站点\ndocument.charset || document.characterSet // 字符编码\n```\n\n## 1.2、性能信息\n\n要收集性能信息，我们需要通过 `performance` 对象，其中大致分为：`Navigation Timing`, `User Timing` 以及 ` Resource Timing`。\n\n首先，通过 ``var perfData = window.performance.timing;`` 获取到时间点数据，这些时间数据都是以long类型的UTC时间，数据大致结构如下：\n\n```js\nvar perfData = {\n  connectEnd:                 1511431220347, // 结束连接\n  connectStart:               1511431220347, // 开始连接\n  domComplete:                1511431221532, // DOM渲染完毕\n  domContentLoadedEventEnd:   1511431220886,\n  domContentLoadedEventStart: 1511431220884,\n  domInteractive:             1511431220884,\n  domLoading:                 1511431220680, // 开始DOM渲染\n  domainLookupEnd:            1511431220347, // 结束DNS解析\n  domainLookupStart:          1511431220347, // 开始DNS解析\n  fetchStart:                 1511431220347, // 开始Fetch\n  loadEventEnd:               1511431221565,\n  loadEventStart:             1511431221532,\n  navigationStart:            1511431220272, // 开始导航\n  redirectEnd:                0,             // 结束跳转\n  redirectStart:              0,             // 开始跳转\n  requestStart:               1511431220353, // 开始请求\n  responseEnd:                1511431220680, // 结束响应\n  responseStart:              1511431220678, // 开始响应\n  secureConnectionStart:      0,             // 开始安全连接\n  unloadEventEnd:             0,\n  unloadEventStart:           0\n}\n```\n\n以此，我们可以得出如下时间数据：\n\n```js\nvar dnsLookupTime = perfData.domainLookupEnd - perfData.domainLookupStart; // DNS解析耗时\nvar tcpConnectTime = perfData.connectEnd - perfData.connectEnd; // TCP连接耗时\nvar pageLoadTime = perfData.loadEventEnd - perfData.navigationStart; // 页面加载时间，对应 Network 的 Load\nvar sendRequestTime = pertData.responseStart - perfData.requestStart; // 发送请求耗时\nvar serverResponseTime = pertData.responseEnd - perfData.responseStart; // 服务端响应耗时\nvar renderTime = perfData.domComplete - perfData.domLoading; // 页面渲染所用的时间\nvar domContentLoadedTime = perfData.domContentLoadedEventEnd - perfData.navigationStart;// 页面内容加载时间，对应 Network 的 DOMContentLoaded\nvar resolveDomTreeTime = perfData.domComplete - perfData.domInteractive; // 解析DOM树耗时\nvar whiteScreenTime = perfData.domLoading - perfData.navigationStart; // 白屏时间\n```\n\n# 2、异常信息收集\n\n对于异常来说，我们一般会提供自动异常收集，也需要允许主动上报异常。\n\n## 如何自动上报异常呢？\n\n我们可以监控 `window.error`，然后获取到错误信息，进行上报。\n\n```js\nwindow.addEventListener('error', function (evt) {\n  console.log(evt);\n}, false);\n```\n\n## 主动上报\n\n对于主动上报的话,我们仅仅需要提供一个方法，来触发一次上报即可。\n\n```js\ntrackException(evt) {\n  const self = this;\n  const data = getExceptionData(evt);\n  data.tid = self._tid;\n  data.t = 'exception';\n  report(self._url, data);\n}\n```\n\n# 3、上报细节处理\n\n## 如何给服务端发送数据\n\n在我们的上报客户端JS中，我们应该尽量少的去使用Ajax请求这些相对复杂的东西，而且也要考虑兼容性问题以及上报成功率。这个时候，我们可以考虑如下做法：\n\n通过 `navigator.sendBeacon` 来进行上报，这是现在浏览器的新特新，能保持一定上报。所以，我们会优先使用它来进行上报。\n\n那如果该特性并不可用的时候呢？发ajax请求么？不。我们可以采用构造图片请求的做法来实现：\n\n```js\nvar img = new Image();\nimg.src = reportUrl;\n```\n\n就这么简单么？也不是这么简单，我们还需要考虑这个请求是否可能发不出去（在遇到内存回收时，该变量由于没有被引用，会被回收掉，所以可能发不出去），为了提高成功率，我们应该按照如下做法实现：\n\n```js\nconst rndKey = `report_img_${makeRndString()}`;\n  const img = window.WebMoniter[rndKey] = new Image();\n  img.onload = img.onerror = function () {\n    window.WebMoniter[rndKey] = null; // 手动清理\n  }\n  img.src = reportUrl;\n```\n\n通过构造一个随机数key，把引用放到window上，在发送完成（包括成功和失败）后，进行清理动作。\n\n**注意：该做法会有url长度限制，要尽量保证URL不至于太长。**\n\n## Other\n\n待续..."
  },
  {
    "path": "运维&部署/记一次Zookeeper数据找回.md",
    "content": "---\ntitle: 记一次Zookeeper数据找回\ndate: 2017-11-3 09:25:22\n---\n\n# 1、关于Zookeeper\n\n`ZooKeeper` 是可用于维护配置信息，命名，提供分布式同步分组服务的集中式服务。了解更多，请查看 [官网](https://zookeeper.apache.org/)\n\n`Zookeeper` 在我们这的用途，主要是提供分布式配置管理服务。并且我们有一套UI可以管理这些配置。\n\n# 2、PRD事故\n\n某一天，某个误操作，导致把一个System（配置是树形结构的，system下面包含有多个具体的key，每个key对应的value才是具体给某个app的配置）全部删除了。由于该System很庞大，我们很难采取手工方式恢复。\n\n考虑到数据存储在 `Zookeeper` 中，那么是否 `Zookeeper` 有相关的日志机制，可以恢复呢？\n\n> 由于配置读取后，会缓存在应用中，只要应用不重启，所以短时间内还不会受到配置丢失的影响，这也是为什么在短时间内，并没有影响具体业务。\n\n**由于配置不会经常变，所以就算日志备份不够及时，基本上也是可接受的**\n\n# 3、探索恢复方案\n\n## Round 1\n\n首先，我们先确认了 `Zookeeper` 是否有日志备份机制。很幸运，`Zookeeper` 在默认配置下运行是有进行定期快照和日志备份的。这也就为恢复提供了极大的可能性。\n\n接着，我们从 `${dataDir}/version-2/` 下找到了很多 `snapshot.{hash}` 的快照文件。\n\n拷贝其中最新的快照（如果判断最新？看创建时间）出来，等待测试。\n\n通过查阅资料，我们在Zookeeper源代码中打开 `cmd`， 执行如下命令:\n\n> 我下载的Zookeeper是3.4.9，所以以此版本为例\n\n```bash\njava -cp dist-maven/zookeeper-3.4.9.jar;lib/log4j-1.2.16.jar;lib/slf4j-log4j12-1.6.1.jar;lib/slf4j-api-1.6.1.jar org.apache.zookeeper.server.SnapshotFormatter snapshot.4115cad8bf\n```\n**注意：其中需要的jar文件，都可以在 zookeeper source 下找到。最后一个是具体的快照文件路径**\n\n> 执行之后，控制台刷出一堆数据，欣喜，似乎有戏。\n\nBut，仅仅是看起来很好，发现只有Path信息，并没有具体的数据。\n\n## Round 2\n\n别慌，除了快照文件，我们还发现有日志文件，而且单个体积都比快照大（极大可能性有数据）。\n\n接着，我们从 `${dataLogDir}/version-2/` 下找到最新的日志文件，通过执行以下命令查看：\n\n```bash\njava -cp dist-maven/zookeeper-3.4.9.jar;lib/log4j-1.2.16.jar;lib/slf4j-log4j12-1.6.1.jar;lib/slf4j-api-1.6.1.jar org.apache.zookeeper.server.LogFormatter log.4115cad8c1\n```\n\n通过观察控制台，确实发现有相关数据。\n\nBut，数据长这样：\n\n```\nsession 0x15f5889137c260d cxid 0x3a3 zxid 0x4115caed1f setData '/cloudtask_v2/WORKER-0c6aaa48-8938-4249-8e12-99b71c06b616,#7b2274797065223a322c2\n2686f73746e616d65223a224531315745423138222c226c6f636174696f6e223a224531315f45435f584d4c4d616b65725f575757222c226f73223a2277696e646f7773222c22706c6174666f726d223a22616d643634222c22697061646472223a223137322e31362e3134302e323138222c22706964223a313536342c2273696e67696e223a747275652c2274696d657374616d70223a313530393434303732312c22617474616368223a2265794a4b62324a4e5958684462335675644349364d48304b227da,921\n```\n\n看起来很难从这里解析出数据。\n\n## Round 3\n\n继续翻资料，发现Zookeeper启动时可以从日志和快照中自动加载数据。\n\n有路就要尝试，把快照和日志拷贝到对应的目录下，然后重启Zookeeper。\n\n> 如何重启？\n\n进入Zookeeper目录，执行 \"bin/zkServr.cmd\"\n\n看起来有戏，应该加载没有报错。\n\n> 该如何验证呢？\n\n此时，我们尝试使用 `Node` 编写代码，来读取这个Zookeeper的数据。\n\n```js\nconst zookeeper = require('node-zookeeper-client');\n\nconst client = zookeeper.createClient('localhost:2181');\n\nconst listChildren = (client, path) => {\n  client.getChildren(path, event => {\n    // listChildren(client, path);\n  }, (error, children, stat) => {\n    if (error) {\n      console.log('Failed to list children of %s due to: %s.', path, error);\n      return;\n    }\n    // 打印出所有的子节点\n    console.log('Children of %s are: %j.', path, children);\n  });\n};\n\nclient.once('connected', function () {\n  console.log('Connected to the server.');\n  listChildren(client, '/');\n});\n\nclient.connect();\n```\n\n发现确实能查询到正确的数据，接着通过操作该实例，读取到丢失节点的数据，恢复到了产线环境。\n\n# 4、总结\n\n1. 我们在用 `Zookeeper` 的时候，因为差不多是业界标准，所以直接就上了。并没有进行深入研究，导致出了问题再去查，有一定的时间（万一解决得慢）风险\n2. 没有预演过程，有较高的不可预测风险\n3. 对于这种危险操作，在PRD环境，仅仅是有二次提示，误操作的可能性较高\n4. 没有相关的重要数据备份机制，完全依靠了Zookeeper自身的日志（要是Zookeeper没有日志备份，那就麻烦了）"
  }
]