leetcode ---Word Break2

Word Break的升级版,求出所有的组合。

Time Limit Exceeded

var wordBreak = function (s, wordDict) {
    let result={};
    let obj = {};
    wordDict.forEach((x) => {
        obj[x] = x;
    })
    let vals=[];
    let arr = [];
    var run =function (s,obj) {
        // if(s in result){return result[s];}
        let temp = '';

        for (var i = 0; i < s.length; i++) {
            temp += s[i];
            if (obj[temp]) {
                // console.log(1)
                arr.push(temp)
                if (i === s.length - 1) {
                    vals.push(arr.join(' '));
                }

                run(s.slice(i + 1), obj);
                arr.pop();

            }

        }
        // result[s]=res;
    }
    run(s,obj);
    return vals;
};

不超时

var wordBreak = function (s, wordDict) {
    let result = {};
    let obj = {};
    wordDict.forEach((x) => {
        obj[x] = x;
    })
    var out = function (s, obj) {
        if (s in result) { return result[s]; }
        let vals = [];
        let arr = [];
        var run = function (s, obj) {

            let temp = '';

            for (var i = 0; i < s.length; i++) {
                temp += s[i];
                if (obj[temp]) {
                    // console.log(1)
                    arr.push(temp)
                    if (i === s.length - 1) {
                        vals.push(arr);
                    } else {
                        let res = out(s.slice(i + 1), obj);
                        res.forEach((x) => {
                            var tempArr = arr.concat(x);
                            vals.push(tempArr);
                            // arr.pop();
                        });
                        arr.pop();
                    }
                }

            }

        }
        run(s, obj);
        result[s] = vals;
        return vals;
    }
    return out(s, obj).map(x => x.join(' '));
    // run(s, obj);
    // return vals;
};

leetcode ---Word Break

给定一个非空字符串,以及一个非空字典的列表,是否可以确定字符串可以分割成一个或多个字典里的单词,你可以确定字典中不包含重复单词。

var wordBreak = function (s, wordDict) {
    let result={};
    let obj = {};
    wordDict.forEach((x) => {
        obj[x] = x;
    })
    var run =function (s,obj) {
        if(s in result){return result[s];}
        let temp = '';
        let b = false;
        for (var i = 0; i < s.length; i++) {
            temp += s[i];
            if (obj[temp]) {
                // console.log(1)
                if (i === s.length - 1) {
                    return true;
                }
                b = b || run(s.slice(i + 1), obj)
                if(b===true){break;}
            }

        }
        result[s]=b;
        return b;
    }
    return run(s,obj);
};

nginx配置判断UA跳转pc或mobile

# mobile的conf
server {
        client_max_body_size 4G;
        listen  10080;  ## listen for ipv4; this line is default and implied
        server_name localhost;
        root /home/ldh/test/mobile;
        location / {
                autoindex on; #显示索引
                autoindex_exact_size on; #显示大小
                autoindex_localtime on;   #显示时间
        }
}


# pc的conf
server {
        client_max_body_size 4G;
        listen  10081;  # listen for ipv4; this line is default and implied
        server_name localhost;
        location / {
        root /home/ldh/test/pc;
                autoindex on; #显示索引
                autoindex_exact_size on; #显示大小
                autoindex_localtime on;   #显示时间
                if ( $http_user_agent ~* "(Android|iPhone|Windows|Phone|UC|iPad|iPod|Kindle)" ){
                    # root /home/ldh/test/mobile;
                    rewrite ^(.*) http://localhost:10080$uri;
                }
        }

}

Angularx服务器渲染

一、vendor,polyfill打包

使用vendor抽出公共部分,如angular/*rxjs。这些基本上都不变。加上hash[8]

二、去掉不常用的库

如momentjs我们只用format()的功能。但是它是很大500+kb。还有jquery只使用了$.param() 它也是200+kb。

三、懒加载

在经过一,二部后已经node_modules中出来jpush-ui外,基本没有了。没啥优化空间了。官网的页面是很多的,20多个页面。可以看到下图。这2.8M基本都是页面。

image
大小备注15M+第一次23.6M(client.js)dll打包,分成3个文件,client、vendor、polyfill33.1M(client.js)去掉momentjs库42.8M(client.js)去掉jquery库

工具

这里使用BundleAnalyzerPlugin分析工具。

new BundleAnalyzerPlugin({
    analyzerMode: 'server',
    analyzerHost: '127.0.0.1',
    analyzerPort: 8888,
    reportFilename: 'report.html',
    defaultSizes: 'parsed',
    openAnalyzer: true,
    generateStatsFile: false,
    statsFilename: 'stats.json',
    statsOptions: null,
    logLevel: 'info'
})

js笔记4

focus, blur不支持事件冒泡,支持事件捕获。使用focusin,focusout代替。

const events = {
    focus: 'focusin',
    blur: 'focusout'
};

console.log打印的对象是不会被垃圾回收器回收的。因此最好不要在页面中console.log任何大对象,这样可能会影响页面的整体性能,特别在生产环境中。除了console.log外,另外还有console.dir、console.error、console.warn等都存在类似的问题,这些细节需要特别的关注。

搭建私有npm仓库

这里使用 Sinopia 搭建npm仓库。只需两句代码即可搭建成功。

sinopia有以下几个优势:

  1. 不同步拉取npm库,占据大量硬盘,没有硬盘被撑爆的问题;
  2. 安装配置极其简单,不需要数据库;
  3. 支持配置上游registry配置,一次拉取即缓存;
  4. 直接可以拉取docker镜像;

安装

$ npm install -g sinopia
$ sinopia

curl http://localhost:4873/,没有timeout。这时已经搭好了。不过只能本地访问,可以直接修改config.yaml配置文件,最后一行listen: 0.0.0.0:4873。不过推荐nginx反向代理到80。当然还可以通过docker搭建,也很简单。

使用pm2守护进程

pm2 start `which sinopia`

config.yaml配置文件

~/.config/sinopia/目录。

# path to a directory with all packages
storage: ./storage  //npm包存放的路径

auth:
  htpasswd:
    file: ./htpasswd   //保存用户的账号密码等信息
    # Maximum amount of users allowed to register, defaults to "+inf".
    # You can set this to -1 to disable registration.
    max_users: -1  //默认为1000,改为-1,禁止注册

# a list of other known repositories we can talk to
uplinks:
  npmjs:
    url: http://registry.npm.taobao.org/  //默认为npm的官网,由于国情,修改 url 让sinopia使用 淘宝的npm镜像地址

packages:  //配置权限管理
  '@*/*':
    # scoped packages
    access: $all
    publish: $authenticated

  '*':
    # allow all users (including non-authenticated users) to read and
    # publish all packages
    #
    # you can specify usernames/groupnames (depending on your auth plugin)
    # and three keywords: "$all", "$anonymous", "$authenticated"
    access: $all

    # allow all known users to publish packages
    # (anyone can register by default, remember?)
    publish: $authenticated

    # if package is not available locally, proxy requests to 'npmjs' registry
    proxy: npmjs

# log settings
logs:
  - {type: stdout, format: pretty, level: http}
  #- {type: file, path: sinopia.log, level: info}

# you can specify listen address (or simply a port) 
listen: 0.0.0.0:4873  ////默认没有,只能在本机访问,添加后可以通过外网访问。

客户端使用

安装nrm

$ npm install -g nrm # 安装nrm
$ nrm add XXXXX http://XXXXXX:4873 # 添加本地的npm镜像地址
$ nrm use XXXX # 使用本址的镜像地址


ldh@ldh-PC:~/GitHubs/aa$ nrm ls

  npm ---- https://registry.npmjs.org/
  cnpm --- http://r.cnpmjs.org/
  taobao - https://registry.npm.taobao.org/
  nj ----- https://registry.nodejitsu.com/
  rednpm - http://registry.mirror.cqupt.edu.cn/
  npmMirror  https://skimdb.npmjs.com/registry/
  edunpm - http://registry.enpmjs.org/
* lnpm --- http://npm.dadigua.win/

已经直接使用私有仓库就行了。如果找不到库,会自动到上游仓库去拉的。然后npm addusernpm loginnpm publish 等命令sinopia搭建的npm库都是支持的。

如下是Sinopia搭建npm库的首页。publish了一个库测试了下。

angular/cdk/portal实现

Portal和PortalHost

这些是angular封装好的动态渲染的工具。PortalHost可以看成一个容器,Portal是需要动态渲染的(如模板或组件)。PortalHost和Portal是一对一关系,每个PortalHost只能插入一个Portal。

PortalHost

  • attach(portal: Portal<any>) PortalHost绑定Portal
  • detach(): void 取消绑定
  • dispose(): void 销毁
  • hasAttached(): boolean 是否已经绑定

Portal有个两个实现TemplatePortal和ComponentPortal

  • attach(PortalHost): Promise<T> 绑定
  • detach(): Promise<void> 取消绑定
  • isAttached: boolean 是否已经绑定

实现

portal最终都是通过viewContainerRef的createComponent和createEmbeddedView动态创建的。

  • createEmbeddedView类型如下,必须的参数只有一个TemplateRef,返回值是ViewRef类型,要使它立即显示执行ViewRef.detectChanges(),使用后要记得执行viewContainer.remove(viewContainer.indexOf(viewRef));销毁。

    createEmbeddedView(templateRef: TemplateRef, context?: C, index?: number): EmbeddedViewRef;

  • createComponent类型如下,必须的参数只有一个ComponentFactory。返回值是componentRef,不使用了要记得执行componentRef.destroy()销毁。

index是插入的位置,injector是注入器,projectableNodes要是插入到组件中viewRef的rootNodes节点。

createComponent<C>(componentFactory: ComponentFactory<C>, index?: number, injector?: Injector, projectableNodes?: any[][], ngModule?: NgModuleRef<any>): ComponentRef<C>;

Tree Shaking

Tree Shaking

使用

修改 tsconfig.json文件,compilerOptions.module,从commonjs,改成es2015。我们的代码编译成js的时候就使用es2015模块规范。然后使用webpack2以上,自带UglifyJsPlugin插件压缩就会,自动Tree Shaking。这是我们的代码在编译到es5的时候会保持es2015模块规范。

{
  "compilerOptions": {
    "target": "es5",
    "module": "es2015",
   }
}

其他

但是这只是Tree Shaking自己的代码,一般减少不了很多体积,一个工程主要是npm包占了大量体积。而npm包,一般为了兼容,都会转成es5在发布。es5自然不是es2015模块规范了。虽然typescript可以输出es5代码,模块规范保持es2015的怪异代码。但是现阶段大量的npm库都不是es2015规范。

ng-zorro-antd学习1

ng-zorro-antd项目使用Angular CLI构建,测试项目。所它的工程结构是

tslint

Angular是使用codelyzer扩展了tslint。可以对Angular项目进行更好的统一风格。如下,规定组件selector前缀。更多规则

"directive-selector": [
  true,
  "attribute",
  "nz",
  ["camelCase","kebab-case"]

],
"component-selector": [
  true,
  [
    "element",
    "attribute"
  ],
  [
    "nz",
    "app"
  ],
  "kebab-case"
],

还有在指定文件头,一般注明所有权,以及作者。如下:

"file-header": [true, "Copyright Google Inc\\."],
/**
 * @license
 * Copyright Google Inc. All Rights Reserved.
 */

test

作为一个开源项目,测试是必须。Angular项目默认有两个测试,单元测试,端到端测试。

  • 单元测试(unit tests)

使用jasmine+Karma ,命令ng test

  • 端到端测试(end-to-end tests)

使用Protractor ,命令ng e2e

exportAs

exportAs在模板中方便访问该指令或组件的实例。

import {Component,Directive} from 'angular2/core';

@Directive({
    selector:'[kittencup]',
    exportAs:'kp'
})
class Kittencup{
    url:string = 'http://kittencup.com'
}

@Component({
    selector: 'App',
    directives: [Kittencup],
    template: `
        <h1 kittencup #kpInstance="kp">
            <span (click)="clickHandle(kpInstance)">{{kpInstance.url}}</span>
        </h1>
    `
})
export class App {

    clickHandle(kpInstance:Kittencup){
        console.log(kpInstance);
    }
}

@Angular/cdk

ng-zorro-antd项目的覆盖,弹出之类组件是用的Angular Materialoverlay组件,这是Angular团队官方写的组件库,他们是使用@Angular/cdkportal解决弹出的问题。

现在body下建立一个弹出层的容器,使用DomPortalHost添加template或者Component。

全局CSS引入

ng-zorro-antd使用一个root组件。它是是一个空组件,只有css。然后设置组件css全局渲染,只需引入这个组件,就引入了全局CSS。这样解决了发布时还要发布CSS。

vscode-extension-ngx

写了一个vscode插件。添加html语法,对angular模板语法的支持。平时写angular模板就是html。实在太渣了,跟写jsx没法比。打算写一个插件,正在完善中,高亮和提示angular模板语法。

如下:
vscode-extension-ngx

github

vscode-extension-ngx

Angular服务器渲染

一、前言

​ 为什么需要服务端渲染?当我们要求渲染时间尽量快、页面响应速度快时(优点),才会采用服务器渲染,并且应该“按需”对页面进行渲染 ——“首次加载/首屏”。即服务端渲染的优势在于:由中间层( nodejs端 )为客户端请求初始数据、渲染好页面 ,有利于SEO优化。

二、实现

Angular2.x有个服务器渲染 Angular2-Universal ,但是在Angular4 中部分是合并到@angular/platform-server。使用也大大不同了,这里主要讲Angular4,如何使用服务器渲染。

1.新建一个Angular新项目

ng new Server-Render,这是localhost:4200返回的。没有经过服务器渲染,<body>只有<app-root></app-root>标签。

<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <title>ServerRender</title>
  <base href="/">

  <meta name="viewport" content="width=device-width, initial-scale=1">
  <link rel="icon" type="image/x-icon" href="favicon.ico">
</head>
<body>
  <app-root></app-root>
<script type="text/javascript" src="inline.bundle.js"></script><script type="text/javascript" src="polyfills.bundle.js"></script><script type="text/javascript" src="styles.bundle.js"></script><script type="text/javascript" src="vendor.bundle.js"></script><script type="text/javascript" src="main.bundle.js"></script></body>
</html>

2.增加browser-app.module.tsserver-app.module.ts

增加两个文件browser-app.module.tsserver-app.module.ts一个是node渲染的Module,还有一个浏览器渲染的Module。

//browser-app.module.ts
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { AppComponent } from './app.component';
import { AppModule } from './app.module';

@NgModule({
  bootstrap: [ AppComponent ],
  imports: [
    BrowserModule.withServerTransition({
      appId: 'my-app-id' 
    }),
    AppModule
  ]
})
export class BrowserAppModule {}



//server-app.module.ts
import { NgModule, APP_BOOTSTRAP_LISTENER, ApplicationRef } from '@angular/core';
import { ServerModule } from '@angular/platform-server';
import { AppComponent } from './app.component';
import { AppModule } from './app.module';
import { BrowserModule } from '@angular/platform-browser';

@NgModule({
  bootstrap: [AppComponent],
  imports: [
    BrowserModule.withServerTransition({
      appId: 'my-app-id' //必须和browser-app.module.ts中的一样
    }),
    ServerModule,
    AppModule
  ]
})
export class ServerAppModule {

}

3.入口修改

Angular默认是用@angular/platform-browser-dynamic加载,只需要一个入口。而服务器渲染有两个入口,main.server.tsmain.browser.ts,一个是node运行的入口,一个是浏览器运行的入口。

  • main.browser.ts

用于加载BrowserAppModule,生成浏览器运行的js代码。

export function main() {
  return platformBrowserDynamic().bootstrapModule(BrowserAppModule);
}
//需要因为服务器渲染已经渲染好html,需要等待dom加载完成,绑定事件。
document.addEventListener('DOMContentLoaded', main, false);
  • main.server.ts

在服务器上面渲染js代码,加载ServerAppModule。这里使用express作为web服务器。使用html作为模板,更改@nguniversal/express-engine为模板引擎。这里把Angular路由注册成express路由,第一次加载都会经过服务器渲染,其他时候使用history API模拟跳转。

import 'zone.js/dist/zone-node';//使用zone的node版
import 'reflect-metadata';
import 'rxjs/Rx';
import * as express from 'express';
import { Request, Response } from 'express';
import { platformServer, renderModuleFactory } from '@angular/platform-server';
import { ServerAppModule } from './app/server-app.module';
import { ngExpressEngine } from '@nguniversal/express-engine';
import { routers } from './app/router'
import { enableProdMode } from '@angular/core';
enableProdMode();
const app = express();
const port = 8000;
const baseUrl = `http://localhost:${port}`;

app.engine('html', ngExpressEngine({
  bootstrap: ServerAppModule
}));

app.set('view engine', 'html');
app.set('views', 'src');

app.use('/', express.static('dist', { index: false }));

routers.forEach((route: any) => {
  app.get('/'+route.path, (req: Request, res: Response) => {
    console.time(`GET: ${req.originalUrl}`);
    res.render('../dist/index', {
      req: req,
      res: res
    });
    console.timeEnd(`GET: ${req.originalUrl}`);
  });
});

app.listen(port, () => {
  console.log(`Listening at ${baseUrl}`);
});

@nguniversal/express-engine是一个nodejs渲染Angular应用程序渲染引擎,它加载初始的html文件渲染出DOM结构,返回给浏览器。高级用法。比如,在渲染的时候接收到request参数,直接渲染到html中。如下:

import { Request } from 'express';
import { REQUEST } from '@nguniversal/express-engine/tokens';

@Injectable()
export class RequestService {
  constructor(@Inject(REQUEST) private request: Request) {}
}

三、服务器渲染陷阱

  • window, document, navigator, 等浏览器属性是不存在nodejs中的。因此涉及到dom结构的库如(jQuery)都不能工作。

因此你在项目中使用的时候,需要注意到当前运行在什么环境,如下:

import { PLATFORM_ID } from '@angular/core';
import { isPlatformBrowser, isPlatformServer } from '@angular/common';

constructor(@Inject(PLATFORM_ID) private platformId: Object) { ... }

ngOnInit() {
  if (isPlatformBrowser(this.platformId)) {
     // Client only code.
     ...
  }
  if (isPlatformServer(this.platformId)) {
    // Server only code.
    ...
  }
}
  • 尽量少使用或避免避免setTimeout,它将等所有的setTimeout结束并渲染完,才会response

  • 记得关闭Rxjs的流。

  • 服务器渲染时不能直接操作nativeElement

    constructor(element: ElementRef, renderer: Renderer2) {

    renderer.setStyle(element.nativeElement, 'font-size', 'x-large');

    }

  • 在服务器渲染的时候发送了XHR请求,渲染出带数据的HTML,在浏览器中又会发送XHR请求,这次是多余的。可以使用缓存,把服务器端的数据通过生成<scrirpt>标签传递给浏览器,浏览器使用缓存即可。官方示例

linux下,使用vscode开发c/c++

先安装编译工具。g++,build-essential。

错误实例 g++: error trying to exec ‘cc1plus’: execvp: 没有那个文件或目录,这时因为缺少g++,或者gcc和g++版本不一致。

sudo apt-get install build-essential
sudo apt-get install g++

vscode安装vscode-cpptools插件

它带了很多的功能,如clang格式化代码,利用gdb断点调试。如果提示miDebuggerPath没找到,则安装gdbsudo apt-get install gdb

makefile

makefile带来的好处就是——“自动化编译”,一旦写好,只需要一个make命令,整个工程完全自动编译,极大的提高了软件开发的效率。make是一个命令工具,是一个解释makefile中指令的命令工具。

实例如下:

build : 1.o
    g++ -o main 1.o
1.o : 1.cpp 
    g++ -g -c 1.cpp
clean :
    rm 1.o main



///launch.json
{
    "version": "0.2.0",
    "configurations": [
        {
            "name": "(gdb) Launch",
            "type": "cppdbg",
            "request": "launch",
            "program": "${workspaceRoot}/main", //make编译出的可执行文件
            "args": [],
            "stopAtEntry": true,
            "cwd": "${workspaceRoot}",
            "environment": [],
            "externalConsole": true,
            "preLaunchTask": "build",
            "linux": {
                "MIMode": "gdb",
                "miDebuggerPath": "/usr/bin/gdb"
            },
            "osx": {
                "MIMode": "lldb"
            },
            "windows": {
                "MIMode": "gdb"
            }
        }
    ]
}
//tasks.json
{
    "version": "2.0.0",
    "command": "make",
    "showOutput": "always",
    "tasks": [
        {
            "taskName": "clean"
        },
        {
            "taskName": "build",
            "problemMatcher": {
                "owner": "cpp",
                "fileLocation":  ["relative", "${workspaceRoot}"],
                "pattern": {
                    "regexp": "^(.*):(\\d+):(\\d+):\\s+(warning|error):\\s+(.*)$",
                    "file": 1,
                    "line": 2,
                    "column": 3,
                    "severity": 4,
                    "message": 5
                }
            }
        }
    ]
}

有时还会提示这个错误No terminal is available to launch the debugger. Please install Gnome Terminal or XTerm没必要安装XTerm命令行,只需把自带的命令行软连接就行,sudo ln -s ./deepin-terminal xterm

为啥parseInt(0.0000008) === 8?

IEEE 754

javascript 的数字系统是采用 IEEE 754,一开始看到这个问题,以为是 IEEE 754 导致的问题。

常见的问题有浮点数比较:

console.log((0.1 + 0.2) == 0.3);  // false
console.log((0.1 + 0.2) === 0.3); // false
console.log(0.1 + 0.2); // 0.30000000000000004

parseInt(string, radix)

parseInt(0.0000008)等于parseInt(String(0.0000008),10)

String(0.000008);  // '0.000008'
String(0.0000008); // '8e-7'

因此

parseInt(String(0.0000008),10)等于parseInt(‘8e-7’,10)===8

结论

不要将 parseInt 当做转换 Number 和 Integer 的工具。

parseInt(1/0, 19);      // 18
parseInt(false, 16);    // 250
parseInt(parseInt, 16); // 15
parseInt("0x10");       // 16
parseInt("10", 2);      // 2

http://blog.csdn.net/Vanhukseter/article/details/43459927

再谈Angular变化检测

什么是变化检测?

angular,react等前端mvvm框架,都是读取组件内部状态渲染页面。但是,当运行时组件状态变化时,页面已经被渲染了,我们必须弄清每个组件的状态发生了什么变化。访问DOM是很昂贵的,因此,必须找出需要更新的位置,尽可能小的访问。

什么是angular变化检测

@Component({
  template: `
    <h1>{{firstname}} {{lastname}}</h1>
    <button (click)="changeName()">Change name</button>
  `
})
class MyApp {

  firstname:string = 'Pascal';
  lastname:string = 'Precht';

  changeName() {
    this.firstname = 'Brad';
    this.lastname = 'Green';
  }
}

这个组件模板就绑定了两个属性firstname``lastname,当这个两个属性没改变时,这里就不需要更新。

什么时候进行变化检测

angular中包含了一个zonejs,zone.js采用猴子补丁(Monkey-patched)的暴力方式将JavaScript中的异步任务都包裹了一层,使得这些异步任务都将运行在zone的上下文中。

  • EVENT - clicksubmit,…
  • XHR - AJAX``Fetch
  • 计时器 - setTimeout()setInterval()
  • Promise- async

基本上每当执行一些异步操作时,我们的应用程序状态可能已经改变。这时需要有人告诉Angular来更新视图。

以下是缩略版本的源码,ApplicationRef就是angular程序的根,它是监听了NgZoneonMicrotaskEmpty,无论什么异步操作都会通知angular执行变化检测。

class ApplicationRef {

  changeDetectorRefs:ChangeDetectorRef[] = [];

  constructor(private zone: NgZone) {
      this._zone.onMicrotaskEmpty.subscribe(
          {next: () => { this._zone.run(() => { this.tick(); }); }});//订阅zone的通知,然后执行tick变化检测的函数。
  }

  tick() {
    this.changeDetectorRefs
      .forEach((ref) => ref.detectChanges());
  }
}

Angular变更检测方式

Angular变更检测是一个单向数据流,总是从根部开始到每个组件。如下面两种图。

j

优化变化检测

Angular变化检测是非常快的。它可以在几毫秒内执行数十万次检查。这主要是由于Angular生成VM友好的代码。但是,它还是可能造成性能问题。比如大量列表,或者在IE浏览器上面,IE的js runtime比不上V8的速度的。

一.手动变化检测

上面都是Angular自动的。Angular每个组件都可以注入ChangeDetectorRef,就能手动变化检测了。首先改变changeDetection设置为OnPush

OnPush

当组件changeDetection设置为OnPush时,Angular变化检测除了在第一次,其他时候将跳过这个组件和子组件(在ng2中有些不同)。除非@Input()属性变化了。

@Component({
  template: `
 <h2>{{vData.name}}</h2>
 <span>{{vData.email}}</span>
  `,
  changeDetection: ChangeDetectionStrategy.OnPush
})
class VCardCmp {
  @Input() vData;
}

markForCheck

在父组件或者自己设置OnPush后,这时使用setTimeout改变绑定数据,发现没有变化。我们需要使用ChangeDetectorRefmarkForCheck,告诉Angular进行一次检测。运行一次markForCheck只能检测一次。

 constructor(private cdRef: ChangeDetectorRef) {
 }
click(){
  this.xxxx=123;
  //this.cdRef.markForCheck();这里可以不需要,事件会自动执行markForCheck方法。
      setTimeout(()=>{
     this.xxxx=456;
     this.cdRef.markForCheck(); //必须在回调函数中再次执行markForCheck。
   })
}

ChangeDetectorRef其他

  • detectChanges - 从该组件到各个子组件执行一次变化检测。就是Angular程序根部(ApplicationRef)绑定的东西。
  • detach - 从变化检测树中分离,该组件将不再执行变化检测,除非手动调用 reattach() 方法。
  • reattach - 重新添加已分离的变化检测器。

二.可观察的数据

使用Angular自带的Async管道。AsyncPipe不仅仅支持Observable,还支持Promise。

constructor(private _ref: ChangeDetectorRef) {}
private _updateLatestValue(async: any, value: Object): void {
   if (async === this._obj) {
     this._latestValue = value;
     this._ref.markForCheck();//当改变数据的时候,call markForCheck方法;
   }
 }
 ngOnDestroy(): void {
   if (this._subscription) {
     this._dispose();//当组件销毁的时候,Observable会自动注销监听
   }
 }

三.不可变的数据

Immutable 主要Angular已经自带Rxjs。Immutable和Rxjs一样都是很大的。一般在Angular中很少使用Immutable。

Angular With WebWorkers

Angular With WebWorkers

使用

  • npm install --save @angular/platform-webworker @angular/platform-webworker-dynamic

  • 在app.module.ts文件中,把BrowserModule 替换WorkerAppModule,从@angular/platform-webworker中导出。

  • 修改main.ts

    import { bootstrapWorkerUi } from ‘@angular/platform-webworker’;

    bootstrapWorkerUi(‘webworker.bundle.js’);

  • 添加workerLoader.ts 文件,这是webworker的入口。主要把platformBrowser改成platformWorkerAppDynamic,从@angular/platform-webworker-dynamic引入。

    import ‘polyfills.ts’;
    import ‘@angular/core’;
    import ‘@angular/common’;

    import { platformWorkerAppDynamic } from ‘@angular/platform-webworker-dynamic’;
    import { AppModule } from ‘./app/app.module’;

    platformWorkerAppDynamic().bootstrapModule(AppModule);

  • 修改打包方式

    "entry": {
      "main": [
        "./src/main.ts"
      ],
      "polyfills": [
        "./src/polyfills.ts"
      ],
      "styles": [
        "./src/styles.css"
      ],
      "webworker": [
        "./src/workerLoader.ts" //增加一个入口。
      ]
    },
     plugins: [
          new HtmlWebpackPlugin({
              template: 'src/index.html',
              excludeChunks: ['webworker'] //这不能在html引入,要通过platformWorkerAppDynamic加载webworker
          })
      ]

在webworker上面运行差异

1.ElementRef类型的nativeElement属性,本来是获取dom。在webworker中是拿不到的。如果要对dom操作,使用ng自带的Renderer2、Renderer。

2.angular的RouterModule好像不能用,webworker.bundle.js:73400 Unhandled Promise rejection: No provider for PlatformLocation! ; Zone: <root> ; Task: Promise.then ; Value: Error: No provider for PlatformLocation!

3.window,document不能使用。意味着document.cookie拿不到,且window.localStorage一样。

4.location对象(只读):不能设置,可能导致angular router不能使用的原因。而且

  • location.href===http://localhost:8080/webworker.bundle.js。结果应该是http://localhost:8080/

Angular模块编译和发布

gulp-inline-ng2-template

使用这个模块把,templateUrlstyleUrls的相对路径转templatestyles。安装gulp4.0 npm install gulpjs/gulp.git#4.0 --save-dev

var inlineTemplatesTask = lazypipe()
  .pipe(inlineTemplates, {
    base: '/src',
    useRelativePaths: true,
    templateProcessor: function (filepath, ext, file, cb) {
      var minifiedFile = htmlMinifier.minify(file, {
        collapseWhitespace: true,
        caseSensitive: true,
        removeComments: true,
        removeRedundantAttributes: true
      });
      cb(null, minifiedFile);
    },
  });

gulp.task('ngc:templates', function () {
  return gulp.src(PATHS.src, { base: 'src' })
    .pipe(inlineTemplatesTask())
    .pipe(gulp.dest(PATHS.tsInline));
});

gulp.task('ngc', gulp.series( 'ngc:templates', function __ngc(cb) {
  exec(`${executable} -p ./tsconfig-aot.json`, (e) => {
    if (e) console.log(e);
    del('./temp/aot');
    cb();
  }).stdout.on('data', function (data) { console.log(data); });
}));
//gulpfile.js

tsconfig-aot.json

{
  "compilerOptions": {
    "target": "es5",
    "lib": [
      "es2015",
      "dom"
    ],
    "module": "commonjs",
    "moduleResolution": "node",
    "declaration": true,
    "experimentalDecorators": true,
    "emitDecoratorMetadata": true,
    "outDir": "lib",
    "rootDir": "./temp/inline",
    "sourceMap": true,
    "inlineSources": true,
    "noImplicitAny": false
  },
  "typeRoots": [
    // add path to @types
    "node_modules/@types"
  ],
  "files": [
    "temp/inline/index.ts"
  ],
  "exclude": [
    "node_modules"
  ],
  "angularCompilerOptions": {
    "strictMetadataEmit": true,
    "genDir": "temp/aot"
  }
}

AoT 编译

首先安装@angular/compiler-cli``@angular/compiler

运行ngc -p tsconfig-aot.json是AoT 编译,要求严格的,不要使用tsc编译。遇到的一些坑。除了第二条不提示以外,其他按照提示改就行。

  1. template标签必须用ng-template代替。可能我用的@angular/compiler版本较高。
  2. AoT 不支持”export default class”,这个有点坑。不管是component还是module,不要用default导出。
  3. interface和type等必须export出来。

npm publish

按照上次npm包发布就行。

npm package发布(TypeScript)

typings

用typescript编写的项目,有一个优点。在使用的有时候有语法提示。在package.json加上"types": "./lib/index.d.ts"入口的*.d.ts文件。别人在使用你的库的,就会有语法提示了。
选项类型默认值描述–declarationbooleanfalse生成相应的.d.ts文件。–declarationDirstring生成声明文件的输出路径。默认为outDir路径。–outDirstring重定向输出目录。–outFilestring将输出文件合并为一个文件。合并的顺序是根据传入编译器的文件顺序和///<reference``>和import的文件顺序决定的。

.npmignore

如果这是npm publish忽略文件。如果没有就使用.gitignore。一般.npmignore和.gitignore还是不同,我们最好使自己的包在别人安装的时候,所有的都是必须的。

package.json

  • "main": "./lib/index.d.ts" main字段是表示这个包的入口js文件。使用tsc编译就行。千万不要使用webpack编译,webpack会把这个项目需要的包的代码直接生成在js中。如果这个包别的库也会用到。这样就打包了两次。
  • devDependencies和dependencies区别,devDependencies是开发依赖,例如webpack,typescript,jest等。dependencies这时运行时的依赖库,如jquery。npm在安装你的包的时候,只会安装dependencies下的。

npm adduser

在npm.org上注册一个账号就行,然后运行上面的命令。按提示输入就行。

license

一般的开源软件的许可证有 ISC, BSD, MIT,其实都是属于 Copyleft 的许可证,对软件的使用限制很少,发布时选择时候的许可证即可。npm 默认的是 ISC。

npm publish .

运行npm publish .命令。

  1. 如果提示no_perms Private mode enable, only admin can publish this module,检查源是不是npm的。
  2. 如提示you do not have permission to publish xxxx,一般是包名冲突了,换个名字publish 。
  3. npm包package.json中registory属性一定要填写,每次publish npm时package.json中version版本一定要大于上一次。
  4. npm unpublish半小时内你可以删除自己发布的库,之后你就再也不能删除了。

.travis.yml

使用travis持续集成你的项目,用户一般会对经过测试的项目是更加信赖。

language: node_js
node_js:
  - "7"


  "scripts": {
    "build": "tsc",
    "test": "tsc&&jest"
  },

其他sourceMap

  1. 使用tsc编译,配置tsconfig中sourceMap为true就会生成sourceMap文件。

  2. 要使webpack生成的sourceMap,还要在webpack.config文件中,添加devtool : “source-map”。

angular变化检测实现原理

angular变化检测实现原理

ViewFlags

ViewFlags视图更新方式与ChangeDetectionStrategy对应

export const enum ViewFlags {
  None = 0,
  OnPush = 1 << 1,
}

ViewState

ViewState视图状态与ChangeDetectorStatus对应。使用位码做状态,好处是可以同一时间处于多个状态。

export const enum ViewState {
  BeforeFirstCheck = 1 << 0,
  FirstCheck = 1 << 1,
  Attached = 1 << 2,
  ChecksEnabled = 1 << 3,
  IsProjectedView = 1 << 4,
  CheckProjectedView = 1 << 5,
  CheckProjectedViews = 1 << 6,
  Destroyed = 1 << 7,

  CatDetectChanges = Attached | ChecksEnabled,  //处于Attached(已添加)和ChecksEnabled(开启更新)都是true
  CatInit = BeforeFirstCheck | CatDetectChanges  //这个状态还加上了在第一检测之前的状态。
}

checkAndUpdateView

视图的变化检测与更新,这是检查模板上面绑定的新数据和以前的对比。若变化则更新。当视图是OnPush时,去掉ChecksEnabled状态。

function callViewAction(view: ViewData, action: ViewAction) {
  const viewState = view.state;
  switch (action) {
    ...
    case ViewAction.CheckAndUpdate:
      if ((viewState & ViewState.Destroyed) === 0) {//组件不能处于Destroyed状态
        if ((viewState & ViewState.CatDetectChanges) === ViewState.CatDetectChanges) {//组件必须处于CatDetectChanges状态
          checkAndUpdateView(view);  //执行变化检测
        } else if (viewState & ViewState.CheckProjectedViews) {
          execProjectedViewsAction(view, ViewAction.CheckAndUpdateProjectedViews);
        }
      }
      break;
    ...

  }
}

export function checkAndUpdateView(view: ViewData) {
  if (view.state & ViewState.BeforeFirstCheck) {
    view.state &= ~ViewState.BeforeFirstCheck;
    view.state |= ViewState.FirstCheck;
  } else {
    view.state &= ~ViewState.FirstCheck;
  }
  markProjectedViewsForCheck(view);
  Services.updateDirectives(view, CheckType.CheckAndUpdate);
  execEmbeddedViewsAction(view, ViewAction.CheckAndUpdate);
  execQueriesAction(
      view, NodeFlags.TypeContentQuery, NodeFlags.DynamicQuery, CheckType.CheckAndUpdate);

  callLifecycleHooksChildrenFirst(
      view, NodeFlags.AfterContentChecked |
          (view.state & ViewState.FirstCheck ? NodeFlags.AfterContentInit : 0));

  Services.updateRenderer(view, CheckType.CheckAndUpdate);

  execComponentViewsAction(view, ViewAction.CheckAndUpdate);
  execQueriesAction(
      view, NodeFlags.TypeViewQuery, NodeFlags.DynamicQuery, CheckType.CheckAndUpdate);
  callLifecycleHooksChildrenFirst(
      view, NodeFlags.AfterViewChecked |
          (view.state & ViewState.FirstCheck ? NodeFlags.AfterViewInit : 0));

  if (view.def.flags & ViewFlags.OnPush) {
    view.state &= ~ViewState.ChecksEnabled;  //当视图是OnPush时,去掉ChecksEnabled状态。
  }
  view.state &= ~(ViewState.CheckProjectedViews | ViewState.CheckProjectedView);
}

ViewRef

  1. markForCheck(): void//从当前组件开始向上,当视图是OnPush时,添加ChecksEnabled状态,在进行一次变化检测后,会去掉ChecksEnabled状态。

    markForCheck(): void { markParentViewsForCheck(this._view); }
    export function markParentViewsForCheck(view: ViewData) {
    let currView: ViewData|null = view;
    while (currView) {

    if (currView.def.flags & ViewFlags.OnPush) {
      currView.state |= ViewState.ChecksEnabled;  //当视图是OnPush时,添加ChecksEnabled状态。
    }
    currView = currView.viewContainerParent || currView.parent;  //从当前组件开始向上。

    }
    }

  1. ​ detach(): void

    detach(): void { this._view.state &= ~ViewState.Attached; } //去掉Attached状态

  1. detectChanges(): void

    detectChanges(): void {

    const fs = this._view.root.rendererFactory;
    if (fs.begin) {
      fs.begin();
    }
    Services.checkAndUpdateView(this._view); //执行checkAndUpdateView函数
    if (fs.end) {
      fs.end();
    }

    }

  1. ​ detach(): void

    reattach(): void { this._view.state |= ViewState.Attached; } //添加Attached状态

ApplicationRef

ApplicationRef是根组件。订阅了zone通知,对自己和子节点进行变化检测,调用view.detectChanges()。

export class ApplicationRef_ extends ApplicationRef{
    constructor(private _zone: NgZone, private _console: Console, private _injector: Injector,
      private _exceptionHandler: ErrorHandler,
      private _componentFactoryResolver: ComponentFactoryResolver,
      private _initStatus: ApplicationInitStatus) {
      ...
      this._zone.onMicrotaskEmpty.subscribe(
          {next: () => { this._zone.run(() => { this.tick(); }); }});//订阅zone的通知,然后执行tick变化检测的函数。
      ...
    }
    tick(): void {
      if (this._runningTick) {
        throw new Error('ApplicationRef.tick is called recursively');
      }

      const scope = ApplicationRef_._tickScope();
      try {
        this._runningTick = true;
        this._views.forEach((view) => view.detectChanges());//对自己和子节点进行变化检测。
        if (this._enforceNoNewChanges) {
          this._views.forEach((view) => view.checkNoChanges());
        }
      } catch (e) {
        // Attention: Don't rethrow as it could cancel subscriptions to Observables!
        this._zone.runOutsideAngular(() => this._exceptionHandler.handleError(e));
      } finally {
        this._runningTick = false;
        wtfLeave(scope);
      }
  }
}

触发事件

组件即使在ChangeDetectionStrategy.OnPush的模式下,在自己范围内触发事件也可以触发一次变化检测。

export function dispatchEvent(
    view: ViewData, nodeIndex: number, eventName: string, event: any): boolean|undefined {
  try {
    const nodeDef = view.def.nodes[nodeIndex];
    const startView = nodeDef.flags & NodeFlags.ComponentView ?
        asElementData(view, nodeIndex).componentView :
        view;
    markParentViewsForCheck(startView);   //就是执行了markForCheck()。这样可以执行一次变化检测了。
    return Services.handleEvent(view, nodeIndex, eventName, event);
  } catch (e) {
    // Attention: Don't rethrow, as it would cancel Observable subscriptions!
    view.root.errorHandler.handleError(e);
  }
}

js异常捕获

try/catch
try{
    try{
        throw('error')
    }catch(e){
       console.error(e)//输出
    }
}catch(e){
  console.error(e,2)//不输出
}


try{
    try{
        throw('error')
    }catch(e){
       console.error(e)//输出
       throw(e)
    }
}catch(e){
  console.error(e,2)//输出
}
Promise或async函数中
new Promise((resolve,reject)=>{
   new Promise((resolve,reject)=>{
      reject('error')//可以换throw(e)
  }).then().catch((e)=>{console.error(e); reject(e)//不能换throw(e)})
}).then().catch((e)=>{console.error(e,2)})


new Promise((resolve,reject)=>{
      resolve(1)
}).then((data)=>{return data})
  .then(()=>{throw(1)})
  .catch((x)=>{console.log(x)})


new Promise((resolve,reject)=>{
      resolve(1)
}).then((data)=>{return data})
  .then(()=>{ 
      return new Promise((resolve,reject)=>{
            throw('error')
      }) 
   })
  .catch((x)=>{console.log(x)})

AsyncPipe源码分析

AsyncPipe

在严格要求性能的环境下,使用ngZone更新视图,是不行的。这时我们可以使用 Observables 机制提升性能。先把组件设置{changeDetection:ChangeDetectionStrategy.OnPush} 这时不会触发ngZone来进行脏检查,这时我们可以通过AsyncPipe管道订阅 Observables 对象,在变化发生之后,进行视图更新。

源码链接angular/AsyncPipe

用法

  • AsyncPipe不仅仅支持Observable,还支持Promise。对于Observable,只要subscribe属性是一个function类型,和参数是一个对象有next属性就行。不一定要rx的Observable。可以自己实现一个迷你的Observable。

原理

constructor(private _ref: ChangeDetectorRef) {}
private _updateLatestValue(async: any, value: Object): void {
   if (async === this._obj) {
     this._latestValue = value;
     this._ref.markForCheck();
   }
 }//当接收数据的时候,利用this._ref.markForCheck();更新组件
 ngOnDestroy(): void {
   if (this._subscription) {
     this._dispose();//当组件销毁的时候,Observable会自动注销监听
   }
 }