类型

var a = 1; 隐式; var a:number = 1; 显示声明

var arr = []

类型:

基本类型:

联合类型:—– 选择

函数签名:

success:(a, b)=>void;

复合类型(Object Type):

var a:{x:xx, y:xx};

number, string, boolean, null, undefined, enum, any;

函数类型:

  1. 参数

    • 函数签名
  2. 返回值

function show(a:number, b:number)


外部变量

declare var $;


接口-interface

  • 和 java 不同
  • 接口:约定、限制;

真面向对象

class 写法, extend, 多继承

访问修饰符:

  1. public 共有 任何人可以访问
  2. private 私有 只有类内部可以访问
  3. protected 受保护-友元 只有子类可以访问

泛型– 别跟 “any” 搞混了 泛– 宽泛

class Calc<T> {
    a:T;
    b:T;
    show (c  :T) {
        alert(a + b + c)
    }
} var obj = new Calc<number>();

obj.a = 12;
obj.b = 'abc'; // 报错 }

Array — 泛型

interface Array<T> { 

    reverse() : T[]; 

    sort(compare?: a(a:T, b:T) =>number):T[];

    …

}

// 泛型:类型当参数传入

数组完成写法:var arr:Array<number> = new Array<number>();

layout 响应式布局工具

  • Breakpoints 屏幕尺寸
  • BreakpointObserver 观察器
  • MediaMatcher 媒体查询匹配

import { Component, OnInit } from "@angular/core";
import {
  BreakpointObserver,
  BreakpointState,
  MediaMatcher,
  Breakpoints
} from "@angular/cdk/layout";
import { Observable } from "rxjs/Observable";
@Component({
  selector: "layout",
  templateUrl: "./layout.component.html",
  styleUrls: ["./layout.component.scss"]
})
export class LayoutComponent implements OnInit {
  isHandset: Observable<BreakpointState>;
  constructor(public breakpointObserver: BreakpointObserver, public media: MediaMatcher) {}

  ngOnInit() {
    // 手持设备
    let Handset = Breakpoints.Handset;
    // 手持landscape屏
    let HandsetLandscape = Breakpoints.HandsetLandscape;
    //手持portrait屏
    let HandsetPortrait = Breakpoints.HandsetPortrait;
    // 多媒体
    let Medium = Breakpoints.Medium;
    // 平板电脑
    let Tablet = Breakpoints.Tablet;
    // 平板电脑 Landscape
    let TabletLandscape = Breakpoints.TabletLandscape;
    // 平板电脑 Portrait
    let TabletPortrait = Breakpoints.TabletPortrait;
    // web
    let Web = Breakpoints.Web;
    // web landscape
    let WebLandscape = Breakpoints.WebLandscape;
    // web portrait
    let WebPortrait = Breakpoints.WebPortrait;
    // 大屏幕
    let Large = Breakpoints.Large;
    // 更大屏幕
    let XLarge = Breakpoints.XLarge;
    // 更小屏幕
    let XSmall = Breakpoints.XSmall;
    // 小屏幕
    let Small = Breakpoints.Small;

    // 是否满足多个条件
    this.breakpointObserver
      .observe([
        Handset,
        HandsetLandscape,
        Handset,
        Medium,
        Tablet,
        TabletLandscape,
        TabletPortrait,
        Web,
        WebLandscape,
        WebPortrait,
        Large,
        XLarge,
        Small,
        XSmall
      ])
      .subscribe(res => {
        console.log(res);
      });
      this.breakpointObserver
      .observe(Breakpoints.Handset)
        .subscribe((state: BreakpointState) => {
        if (state.matches) {
          console.log('手机设备尺寸内');
        } else {
          console.log('非手机设备尺寸内');
        }
      });

  }
}

angular6

1. 简单介绍下项目开始,官方文档上都有详细介绍

1. 然后全局安装 angularCLI

npm install -g @angular/cli

2. 用脚手架创建项目

ng new my-app 

这样就生成一个项目

3. 进入项目目录运行服务并打开

cd my-app
ng serve --open

注:项目目录官方文档很详细我就不介绍了

2. 创建组件

ng generate component heroes

会在 app 文件下创建 一个 hreoes 组件并将其引入到 app.moduls.ts 根文件

hreoes 组件文件夹:文件夹目录下会有四个文件,css 样式文件,html 文件,ts 文件,剩下一个文件没有用.

// 用 ng generate component 创建组件过多时,app.mosuls.ts 会出现乱码或引入错误代码跑偏,需要自己整理!!!

@Component 是个装饰器函数,用于为该组件指定 Angular 所需的元数据。

CLI 自动生成了三个元数据属性:

  1. selector— 组件的选择器(CSS 元素选择器)

  2. templateUrl— 组件模板文件的位置。

  3. styleUrls— 组件私有 CSS 样式表文件的位置。

还有很多扩展属性:
    1. encapsulation: 

      2. changeDetection: 实例化组件时,Angular会创建一个更改检测器

      3.  viewProviders定义其视图DOM子项可见的可注入对象集。

      4. moduleId ,

      5. templateUrl 与 template :不用多说指向 html ,templateUrl 指向 html 路径, template 直接来写 html

      6. styleUrls 与 styles :同上

      7. animations :动画,很厉害的东西,官方介绍通过类似动画的DSL在组件上定义动画。这种描述动画的DSL方法允许灵活性,既有利于开发人员,也有利于框架。动画通过侦听模板中元素上发生的状态更改来工作。当发生状态变化时,Angular可以利用并在其间设置动画。这与CSS转换的工作方式类似,但是,通过编程DSL,动画不仅限于特定于DOM的环境。(Angular也可以在幕后执行优化,使动画效果更高。)要使动画可供使用,动画状态更改将放置在动画触发器中,动画触发器位于动画注释元数据内。在触发器内,可以放置状态和转换条目。

      8. interpolation:覆盖默认的封装开始和结束分隔符 (respectively {{ and }})

      9. entryComponents:定义在定义此组件时应编译的组件。对于此处列出的每个组件,Angular将创建一个ComponentFactory并将其存储在ComponentFactoryResolver中。

       10. preserveWhitespaces: boolean 

CSS 元素选择器 app-heroes 用来在父组件的模板中匹配 HTML 元素的名称,以识别出该组件。

ngOnInit 是一个生命周期钩子,Angular 在创建完组件后很快就会调用 ngOnInit。这里是放置初始化逻辑的好地方。

始终要 export 这个组件类,以便在其它地方(比如 AppModule)导入它。

3. 创建服务

ng generate service hero

使用 Angular CLI 创建一个名叫 hero 的服务。

@Injectable() 服务


注意,这个新的服务导入了 Angular 的 Injectable 符号,并且给这个服务类添加了 @Injectable() 装饰器。 它把这个类标记为依赖注入系统的参与者之一。HeroService 类将会提供一个可注入的服务,并且它还可以拥有自己的待注入的依赖。 目前它还没有依赖,但是很快就会有了

@Injectable() 装饰器会接受该服务的元数据对象,就像 @Component() 对组件类的作用一样。

mysql 常用语法

1. SQL 语句分类

  • DDL (Data DefinitonLanguage) 数据定义语言,用来数据库对象:库、表、列等。功能:创建、删除、修改库和表结构。
  • DML (Data Manipulation Language) 数据操作语言,用来蒂尼数据库记录,增、删、改表记录。
  • DCL  (Data Control Language) 数据控制语言,用来定义访问权限和安全级别。
  • DQL (Data Query Language) 数据查询语言,用来查询记录。

2. SQL 数据中的属性类型

  • tintiny:1 字节,小整数值。
  • smallint:2 字节,大整数值。
  • mediumint:3 字节,大整数值。
  • int或 integer:4 字节,整型,大整数值。
  • float单精度浮点数值。
  • double(5.2) 双精度浮点型数值,参数表示该浮点型数值最多有5位,其中必须有2位小数。
  • decimal(M,D) 小数值,参数表示该数值最多有M位,其中必须有D位小数。
  • char: 字符型,固定长度字符串类型: char(255)。 你存入一个 a 字符, 虽然 a 只占一个字符, 但是它会自动给你加 254 个空格凑成 255 个长度。即数据的长度不足指定长度,它会补足到指定长度。
  • varchar 可变长度字符串类型:-varchar(65535), 你存入的数据多长它就是多长。它会抽出几个字节来记录数据的长度。
  • text (CLOB):mysql 独有的数据类型,字符串类型。
  • blob:字节类型
  • year: 年份值,格式为:YYYY
  • date: 日期类型,格式为 yyy-mm-dd.
  • time 时间类型,格式为: hh:mm:ss。
  • timestamp: 时间戳类型,格式为上面二者的综合。
  • datetime: 混合日期和时间值,格式为:YYYYMMDD HHMMSS

3. SQL 语句详解

首先需要在命令行中输入 mysql -u root -p 来进入 mysql 。注意:1、mysql 语法不区分大小写,但是建议在写关键字时用大写。2. 每一条语句后面以分号结尾。

3.1 DDL (数据定义语言)语法

该语言用来对数据库和表结构进行操作。

对数据的操作:

查看所有的数据库 show databases:

使用书库 use 数据名,

创建数据库并制定编码: create database [if not exists] 数据库名 [defaultcharacter set utf8];

删除数据库:drop database 数据库名;

修改数据库的编码:alter database 数据库名 character set utf-8

对表结构的操作

创建表:

create table (if not exists) 表名 (
列名,列类型, 
...,
列名,列类型,
)
  • 查看当前数据库中所有表:show tables;
  • 查看表结构:desc 表名;
  • 删除表:drop 表名;
  • 修改表:修改表有5个操作,但前缀都是一样的: alter table 表名…
  • 修改表之添加列:alter table 表名 add (列名 列类型, …, 列名 列类型);
  • 修改表之列名称:alter table 表名 modify 列名 列的新类型;
  • 修改表之列名称列类型一起修改:alter table 表名 change 原列名 新列名 列名类型;
  • 修改表之删除列:alter table 表名 drop 列名;
  • 修改表之修改表名:alter table 表名 rename to 新表名

3.2 DML (数据操作语法)语法

该语言用来记录操作(增,删,改)。

3.2.1 插入数据(一次插入就是一行)

insert into 表名 (列名1,列名2,列名3) value (列值1,列值2,列值3)

说明:

1. 在数据库中所有的字符串类型,必须使用单引号。

2. (列名1,列名2,列名3)可省略,表示按照表中的顺序插入。但不建议采取这种写法,因为降低了程序的可读性。

3. 在命令行插入记录不要写中文,否则会出现乱码(解决控制台的乱码问题后便可插入中文)

3.2.2 修改记录(不会修改一行)

修改某列的全部值: update 表名 set 列名1 = 列值1

修改(某行或者多行的记录的)列的指定值:update 表名 set 列名1=列值1 where 列名2=列值2 or 列名3=列值3;

运算符:=,!=,<>,<,>,>=, <=, between…and, in(…), is null, not, or, and,

 其中 in(…)的用法表示集合。 例如: update 表名 set 列名1=列值1 where 列名2=列值2 or 列名2=列值2 用 in(…) 写成 update 表名 set 列名1=列值1 where 列名2 in (列值2,列值3)

3.2.3 删除数据(删除整行)

delete from 表名 (where 条件);不加 where 条件时会删除表中所有的记录,所以为了防止这种失误操作,很多数据库往往都会有备份。

3.3 DCL(数据控制语言)语法

该语言用来定义访问权限,理解即可,以后不会多用。需要记住的是,一个项目创建一个用户,一个项目对应的数据库只有一个。这个用户只能对这个数据库有权限,其它数据库该用户就操作不了。

3.3.1创建用户

用户只能在指定ip地址上登录mysql:create user 用户名@IP地址 identified by ‘密码’;

用户可以在任意ip地址上登录:create user 用户名@‘%’ identified by ‘密码’;

3.3.2给用户授权

语法:grant 权限1,…,权限n on 数据库.* to 用户名@IP地址;其中权限1、2、n可以直接用all关键字代替。权限例如:create,alter,drop,insert,update,delete,select。

3.3.3撤销授权

语法:revoke 权限1,…,权限n on 数据库.* from 用户名@ ip地址;撤销指定用户在指定数据库上的指定权限。撤销例如:revoke create,delete on mydb1.* form user@localhost;表示的意思是撤消user用户在数据库mydb1伤的create、alter权限。

3.3.4查看权限

查看指定用户的权限:show grants for 用户名@ip地址;

3.4DQL(数据查询语言)语法

重点,该语言用来查询记录,不会修改数据库和表结构。

3.4.1基本查询(后缀都是统一为from 表名)
  • 1.字段(列)控制1.查询所有列:select * from 表名;
    其中*表示查询所有列,而不是所有行的意思。
  • 2.查询指定列:select 列1,列2,列n from 表名;
  • 3.完全重复的记录只显示一次:在查询的列之前添加distinct:select distinct $ from 表名;
    缺省值为all。
  • 4.列运算a.数量类型的列可以做加、减、乘、除:SELECT sal*5 from 表名;
说明:

1.遇到null加任何值都等于null的情况,需要用到ifnull()函数。
2.将字符串做加减乘除运算,会把字符串当作0。b.字符串累类型可以做连续运算(需要用到concat()函数):select concat(列名1,列名2) from 表名;其中列名的类型要为字符串。c. 给列名起别名:select 列名1 (as) 别名1,列名2 (as) 别名2 from 表名;

2.条件控制
1.条件查询。在后面添加where指定条件:select * from 表名 where 列名=指定值;
2.模糊查询:当你想查询所有姓张的记录。用到关键字like。eg:select * from 表名 where 列名 like ‘张_’;
(代表匹配任意一个字符,%代表匹配0~n个任意字符)。

3.4.2排序(所谓升序和降序都是从上往下排列)

1.升序:select * form 表名 order by 列名 (ASC );
()里面的内容为缺省值;
2.降序:select * from 表名 order by 列名 DESC;

3.使用多列作为排序条件: 当第一列排序条件相同时,根据第二列排序条件排序(当第二列依旧相同时可视情况根据第三例条件排序)。eg:select * from 表名 order by 列名1 ASC, 列名2 DESC;
意思是当列名1的值相同时按照列名2的值降序排。

3.4.3聚合函数

1.count:select count(列名) from 表名;
,纪录有效行数。
2.max:select count(列名) from 表名;
,列中最大值。
3.min:select sum(列名) from 表名;
,列中最小值。
4.sum:select sum(列名) from 表名;
,求列的总值,null 和字符串默认为0。
5.avg:select avg(列名) from 表名;
,一列的平均值。

3.4.4分组查询

分组查询的信息都是组的信息,不能查到个人的信息,其中查询组的信息是通过聚合函数得到的。
语法:select 分组列名,聚合函数1,聚合函数2 from 表名 group by 该分组列名;
其中分组列名需要的条件是该列名中有重复的信息。
查询的结果只能为:作为分组条件的列和聚合函数;查处的信息都是组的信息。
分组查询前,还可以通过关键字where先把满足条件的人分出来,再分组。语法为:select 分组列,聚合函数 from 表名 where 条件 group by 分组列;

分组查询后,也可以通过关键字having把组信息中满足条件的组再细分出来。语法为:select 分组列,聚合函数 from 表名 where 条件 group by 分组列 having 聚合函数或列名(条件);

3.4.5LIMIT子句(mysql中独有的语法)

LIMIT用来限定查询结果的起始行,以及总行数。
例如:select * from 表名 limit 4,3;
表示起始行为第5行,一共查询3行记录。

4.总结

学过的关键字:select,from,where,group by,having ,order by。
当一条查询语句中都包含所有这些关键字时它们的优先级是select>from>where>group by>having>order by

今天做锚点连接的时候,发现偶尔超链接出现偏移,在文档中找打了一个 :target  CSS选择器;

定义和用法

URL 带有后面跟有锚名称 #,指向文档内某个具体的元素。这个被链接的元素就是目标元素(target element)。

:target 选择器可用于选取当前活动的目标元素。

<!DOCTYPE html>
<html>
<head>
<style>
#news1:target
{
border: 2px solid #D4D4D4;
background-color: #e5eecc;
}
</style>
</head>
<body>

<h1>这是标题</h1>

<p><a href="#news1">跳转至内容 1</a></p>
<p><a href="#news2">跳转至内容 2</a></p>

<p>请点击上面的链接,:target 选择器会突出显示当前活动的 HTML 锚。</p>

<p id="news1"><b>内容 1...</b></p>
<p id="news2"><b>内容 2...</b></p>

<p><b>注释:</b> Internet Explorer 8 以及更早的版本不支持 :target 选择器。</p>

</body>
</html>

今天看到 一个 PHP 疯狂的吐槽,JavaScript 面试人员 JS 写的不如 PHP 连 AMD 与 CMD 都不会,我心里默默的想了一下,自己脑海里只是有个浅薄的概念,吓的我赶紧查查资料。

参考资料

     AMD 规范

     CMD 规范

    SeaJS 与 RequestJS 差异可以参考 https://github.com/seajs/seajs/issues/277

AMD 是 RequireJS 在推广过程中对模块定义的规范化产出。
CMD 是 SeaJS 在推广过程中对模块定义的规范化产出。
类似的还有 CommonJS Modules/2.0 规范,是 BravoJS 在推广过程中对模块定义的规范化产出。

这些规范的目的都是为了 JavaScript 的模块化开发,特别是在浏览器端的。
目前这些规范的实现都能达成浏览器端模块化开发的目的

区别:

1. 对于依赖的模块,AMD 是提前执行,CMD 是延迟执行。不过 RequireJS 从 2.0 开始,也改成可以延迟执行(根据写法不同,处理方式不同)。CMD 推崇 as lazy as possible.

2. CMD 推崇依赖就近,AMD 推崇依赖前置。看代码:

// CMD
define(function(require, exports, module) {
var a = require(‘./a’)
a.doSomething()
// 此处略去 100 行
var b = require(‘./b’) // 依赖可以就近书写
b.doSomething()
// … 
})

// AMD 默认推荐的是
define([‘./a’, ‘./b’], function(a, b) { // 依赖必须一开始就写好
a.doSomething()
// 此处略去 100 行
b.doSomething()

})

虽然 AMD 也支持 CMD 的写法,同时还支持将 require 作为依赖项传递,但 RequireJS 的作者默认是最喜欢上面的写法,也是官方文档里默认的模块定义写法。

3. AMD 的 API 默认是一个当多个用,CMD 的 API 严格区分,推崇职责单一。比如 AMD 里,require 分全局 require 和局部 require,都叫 require。CMD 里,没有全局 require,而是根据模块系统的完备性,提供 seajs.use 来实现模块系统的加载启动。CMD 里,每个 API 都简单纯粹

最近发现一个 好用的 node 调试方法

$ node – -inspect app.js  在平常启动项目的时候加上 –inspect 

 – –inspect参数是启动调试模式必需的。这时,打开浏览器访问

接下来,就要开始调试了。一共有两种打开调试工具的方法,第一种是在 Chrome 浏览器的地址栏,键入 chrome://inspect或者about:inspect,回车后就可以看到下面的界面。

在 Target 部分,点击 inspect 链接,就能进入调试工具了。

第二种进入调试工具的方法,是在 http://127.0.0.1:3000 的窗口打开”开发者工具”,顶部左上角有一个 Node 的绿色标志,点击就可以进入。

三、调试工具窗口
调试工具其实就是”开发者工具”的定制版,省去了那些对服务器脚本没用的部分。
它主要有四个面板。
Console:控制台
Memory:内存
Profiler:性能
Sources:源码
这些面板的用法,基本上跟浏览器环境差不多,这里只介绍 Sources (源码)面板。


四、设置断点
进入 Sources 面板,找到正在运行的脚本app.js。
在第11行(也就是下面这一行)的行号上点一下,就设置了一个断点。
这时,浏览器访问 http://127.0.0.1:3000/alice ,页面会显示正在等待服务器返回。切换到调试工具,可以看到 Node 主线程处于暂停(paused)阶段。
进入 Console 面板,输入 name,会返回 alice。这表明我们正处在断点处的上下文(context)。
再切回 Sources 面板,右侧可以看到 Watch、Call Stack、Scope、Breakpoints 等折叠项。打开 Scope 折叠项,可以看到 Local 作用域和 Global 作用域里面的所有变量。
Local 作用域里面,变量name的值是alice,双击进入编辑状态,把它改成bob。
然后,点击顶部工具栏的继续运行按钮。
页面上就可以看到 Hello bob 了。
命令行下,按下 ctrl + c,终止运行app.js。


五、调试非服务脚本
Web 服务脚本会一直在后台运行,但是大部分脚本只是处理某个任务,运行完就会终止。这时,你可能根本没有时间打开调试工具。等你打开了,脚本早就结束运行了。这时怎么调试呢?
$ node –inspect=9229 -e “setTimeout(function() { console.log(‘yes’); }, 30000)”
上面代码中,–inspect=9229指定调试端口为 9229,这是调试工具默认的通信端口。-e参数指定一个字符串,作为代码运行。
访问chrome://inspect,就可以进入调试工具,调试这段代码了。
代码放在setTimeout里面,总是不太方便。那些运行时间较短的脚本,可能根本来不及打开调试工具。这时就要使用下面的方法。
$ node – –inspect-brk=9229 app.js
上面代码中,–inspect-brk指定在第一行就设置断点。也就是说,一开始运行,就是暂停的状态。


六、忘了写 –inspect 怎么办?
打开调试工具的前提是,启动 Node 脚本时就加上–inspect参数。如果忘了这个参数,还能不能调试呢?
回答是可以的。首先,正常启动脚本。
$ node app.js
然后,在另一个命令行窗口,查找上面脚本的进程号。
$psax|grepapp.js
30464pts/11Sl+:00nodeapp.js
30541pts/12S+:00grepapp.js
上面命令中,app.js的进程号是30464。
接着,运行下面的命令。
$ node -e ‘process._debugProcess(30464)’
上面命令会建立进程 30464 与调试工具的连接,然后就可以打开调试工具了。
还有一种方法,就是向脚本进程发送 SIGUSR1 信号,也可以建立调试连接。
$ kill -SIGUSR1 30464


七、参考链接
Debugging Node.js with Google Chrome, by Jacopo Daeli
Debugging Node.js with Chrome DevTools, by Paul Irish
Last minute node debugging, by Remy Sharp

问题汇总:

    1. 侧滑返回如何禁止?

    2. 侧滑出现上一页面 (页面 js 未执行)

    3. 侧滑出现侧拉,页面空白或页面未刷新,(js 未执行)

个人感觉是框架问题,也是机制问题,ios 很容易出现这样的问题,页面禁用侧滑事件无用,出现上一页面,上一页面也检测不到,显示为空白页面

侧拉拉出侧拉的页面,页面内没有检测到任何事件,内部 js 也没有执行!

可以试试 ios 边缘拖拽关闭禁止


ios的边缘拖拽关闭可以通过下列代码来禁止:

plus.webview.currentWebview().setStyle({
                        'popGesture': 'none'
                    });

在软件中,性能一直扮演着重要的角色。在Web应用中,性能变得更加重要,因为如果页面速度很慢的话,用户就会很容易转去访问我们的竞争对手的网站。作为专业的web开发人员,我们必须要考虑这个问题。有很多“古老”的关于性能优化的最佳实践在今天依然可行,例如最小化请求数目,使用CDN以及不编写阻塞页面渲染的代码。然而,随着越来越多的web应用都在使用JavaScript,确保我们的代码运行的很快就变得很重要。

假设你有一个正在工作的函数,但是你怀疑它运行得没有期望的那样快,并且你有一个改善它性能的计划。那怎么去证明这个假设呢?在今天,有什么最佳实践可以用来测试JavaScript函数的性能呢?一般来说,完成这个任务的最佳方式是使用内置的performance.now()函数,来衡量函数运行前和运行后的时间。

在这篇文章中,我们会讨论如何衡量代码运行时间,以及有哪些技术可以避免一些常见的“陷阱”。

Performance.now()

高分辨率时间API提供了一个名为now()的函数,它返回一个DOMHighResTimeStamp对象,这是一个浮点数值,以毫秒级别(精确到千分之一毫秒)显示当前时间。单独这个数值并不会为你的分析带来多少价值,但是两个这样的数值的差值,就可以精确描述过去了多少时间。

这个函数除了比内置的Date对象更加精确以外,它还是“单调”的,简单说,这意味着它不会受操作系统(例如,你笔记本上的操作系统)周期性修改系统时间影响。更简单的说,定义两个Date实例,计算它们的差值,并不代表过去了多少时间。

“单调性”的数学定义是“(一个函数或者数值)以从不减少或者从不增加的方式改变”。

我们可以从另外一种途径来解释它,即想象使用它来在一年中让时钟向前或者向后改变。例如,当你所在国家的时钟都同意略过一个小时,以便最大化利用白天的时间。如果你在时钟修改之前创建了一个Date实例,然后在修改之后创建了另外一个,那么查看这两个实例的差值,看上去可能像“1小时零3秒又123毫秒”。而使用两个performance.now()实例,差值会是“3秒又123毫秒456789之一毫秒”。

在这一节中,我不会涉及这个API的过多细节。如果你想学习更多相关知识或查看更多如何使用它的示例,我建议你阅读这篇文章:Discovering the High Resolution Time API。

既然你知道高分辨率时间API是什么以及如何使用它,那么让我们继续深入看一下它有哪些潜在的缺点。但是在此之前,我们定义一个名为makeHash()的函数,在这篇文章剩余的部分,我们会使用它。

functionmakeHash(source){

varhash=;

if(source.length===)returnhash;

for(vari=;i

varchar=source.charCodeAt(i);

hash=((hash

hash=hash&hash;// Convert to 32bit integer

}

returnhash;

}

我们可以通过下面的代码来衡量这个函数的执行效率:

vart0=performance.now();

varresult=makeHash(‘Peter’);

vart1=performance.now();

console.log(‘Took’,(t1-t0).toFixed(4),’milliseconds to generate:’,result);

如果你在浏览器中运行这些代码,你应该看到类似下面的输出:

Took 0.2730 milliseconds to generate: 77005292

这段代码的在线演示如下所示:

记住这个示例后,让我们开始下面的讨论。

缺陷1 – 意外衡量不重要的事情

在上面的示例中,你可以注意到,我们在两次调用performance.now()中间只调用了makeHash()函数,然后将它的值赋给result变量。这给我们提供了函数的执行时间,而没有其他的干扰。我们也可以按照下面的方式来衡量代码的效率:

vart0=performance.now();

console.log(makeHash(‘Peter’));// bad idea!

vart1=performance.now();

console.log(‘Took’,(t1-t0).toFixed(4),’milliseconds’);

这个代码片段的在线演示如下所示:

但是在这种情况下,我们将会测量调用makeHash(‘Peter’)函数花费的时间,以及将结果发送并打印到控制台上花费的时间。我们不知道这两个操作中每个操作具体花费多少时间, 只知道总的时间。而且,发送和打印输出的操作所花费的时间会依赖于所用的浏览器,甚至依赖于当时的上下文。

或许你已经完美的意识到console.log方式是不可以预测的。但是执行多个函数同样是错误的,即使每个函数都不会触发I/O操作。例如:

vart0=performance.now();

varname=’Peter’;

varresult=makeHash(name.toLowerCase()).toString();

vart1=performance.now();

console.log(‘Took’,(t1-t0).toFixed(4),’milliseconds to generate:’,result);

同样,我们不会知道执行时间是怎么分布的。它会是赋值操作、调用toLowerCase()函数或者toString()函数吗?

缺陷 #2 – 只衡量一次

另外一个常见的错误是只衡量一次,然后汇总花费的时间,并以此得出结论。很可能执行不同的次数会得出完全不同的结果。执行时间依赖于很多因素:

编辑器热身的时间(例如,将代码编译成字节码的时间)

主线程可能正忙于其它一些我们没有意识到的事情

你的电脑的CPU可能正忙于一些会拖慢浏览器速度的事情

持续改进的方法是重复执行函数,就像这样:

vart0=performance.now();

for(vari=;i

makeHash(‘Peter’);

}

vart1=performance.now();

console.log(‘Took’,((t1-t0)/10).toFixed(4),’milliseconds to generate’);

这个示例的在线演示如下所示:

这种方法的风险在于我们的浏览器的JavaScript引擎可能会使用一些优化措施,这意味着当我们第二次调用函数时,如果输入时相同的,那么JavaScript引擎可能会记住了第一次调用的输出,然后简单的返回这个输出。为了解决这个问题,你可以使用很多不同的输入字符串,而不用重复的使用相同的输入(例如‘Peter’)。显然,使用不同的输入进行测试带来的问题就是我们衡量的函数会花费不同的时间。或许其中一些输入会花费比其它输入更长的执行时间。

缺陷 #3 – 太依赖平均值

在上一节中,我们学习到的一个很好的实践是重复执行一些操作,理想情况下使用不同的输入。然而,我们要记住使用不同的输入带来的问题,即某些输入的执行时间可能会花费所有其它输入的执行时间都长。这样让我们退一步来使用相同的输入。假设我们发送同样的输入十次,每次都打印花费了多长时间。我们会得到像这样的输出:

Took0.2730millisecondstogenerate:77005292

Took0.0234millisecondstogenerate:77005292

Took0.0200millisecondstogenerate:77005292

Took0.0281millisecondstogenerate:77005292

Took0.0162millisecondstogenerate:77005292

Took0.0245millisecondstogenerate:77005292

Took0.0677millisecondstogenerate:77005292

Took0.0289millisecondstogenerate:77005292

Took0.0240millisecondstogenerate:77005292

Took0.0311millisecondstogenerate:77005292

请注意第一次时间和其它九次的时间完全不一样。这很可能是因为浏览器中的JavaScript引擎使用了优化措施,需要一些热身时间。我们基本上没有办法避免这种情况,但是会有一些好的补救措施来阻止我们得出一些错误的结论。

一种方式是去计算后面9次的平均时间。另外一种更加使用的方式是收集所有的结果,然后计算“中位数”。基本上,它会将所有的结果排列起来,对结果进行排序,然后取中间的一个值。这是performance.now()函数如此有用的地方,因为无论你做什么,你都可以得到一个数值。

让我们再试一次,这次我们使用中位数函数:

varnumbers=[];

for(vari=;i

vart0=performance.now();

makeHash(‘Peter’);

vart1=performance.now();

numbers.push(t1-t0);

}

functionmedian(sequence){

sequence.sort();// note that direction doesn’t matter

returnsequence[Math.ceil(sequence.length/2)];

}

console.log(‘Median time’,median(numbers).toFixed(4),’milliseconds’);

缺陷 #4 – 以可预测的方式比较函数

我们已经理解衡量一些函数很多次并取平均值总会是一个好主意。而且,上面的示例告诉我们使用中位数要比平均值更好。

在实际中,衡量函数执行时间的一个很好的用处是来了解在几个函数中,哪个更快。假设我们有两个函数,它们的输入参数类型一致,输出结果相同,但是它们的内部实现机制不一样。

functionisIn(haystack,needle){

varfound=false;

haystack.forEach(function(element){

if(element.toLowerCase()===needle.toLowerCase()){

found=true;

}

});

returnfound;

}

console.log(isIn([‘a’,’b’,’c’],’B’));// true

console.log(isIn([‘a’,’b’,’c’],’d’));// false

我们可以立刻发现这个方法有改进的地方,因为haystack.forEach循环总会遍历所有的元素,即使我们可以很快找到一个匹配的元素。现在让我们使用for循环来编写一个更好的版本。

functionisIn(haystack,needle){

for(vari=,len=haystack.length;i

if(haystack[i].toLowerCase()===needle.toLowerCase()){

returntrue;

}

}

returnfalse;

}

console.log(isIn([‘a’,’b’,’c’],’B’));// true

console.log(isIn([‘a’,’b’,’c’],’d’));// false

现在我们来看哪个函数更快一些。我们可以分别运行每个函数10次,然后收集所有的测量结果:

functionisIn1(haystack,needle){

varfound=false;

haystack.forEach(function(element){

if(element.toLowerCase()===needle.toLowerCase()){

found=true;

}

});

returnfound;

}

functionisIn2(haystack,needle){

for(vari=,len=haystack.length;i

if(haystack[i].toLowerCase()===needle.toLowerCase()){

returntrue;

}

}

returnfalse;

}

console.log(isIn1([‘a’,’b’,’c’],’B’));// true

console.log(isIn1([‘a’,’b’,’c’],’d’));// false

console.log(isIn2([‘a’,’b’,’c’],’B’));// true

console.log(isIn2([‘a’,’b’,’c’],’d’));// false

functionmedian(sequence){

sequence.sort();// note that direction doesn’t matter

returnsequence[Math.ceil(sequence.length/2)];

}

functionmeasureFunction(func){

varletters=’a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u,v,w,x,y,z’.split(‘,’);

varnumbers=[];

for(vari=;i

vart0=performance.now();

func(letters,letters[i]);

vart1=performance.now();

numbers.push(t1-t0);

}

console.log(func.name,’took’,median(numbers).toFixed(4));

}

measureFunction(isIn1);

measureFunction(isIn2);

我们运行上面的代码, 可以得出如下的输出:

true

false

true

false

isIn1took0.0050

isIn2took0.0150

这个示例的在线演示如下所示:

到底发生了什么?第一个函数的速度要快3倍!那不是我们假设的情况。

其实假设很简单,但是有些微妙。第一个函数使用了haystack.forEach方法,浏览器的JavaScript引擎会为它提供一些底层的优化,但是当我们使用数据索引技术时,JavaScript引擎没有提供对应的优化。这告诉我们:在真正测试之前,你永远不会知道。

结论

在我们试图解释如何使用performance.now()方法得到JavaScript精确执行时间的过程中,我们偶然发现了一个基准场景,它的运行结果和我们的直觉相反。问题在于,如果你想要编写更快的web应用,我们需要优化JavaScript代码。因为计算机(几乎)是一个活生生的东西,它很难预测,有时会带来“惊喜”,所以如果了解我们代码是否运行更快,最可靠的方式就是编写测试代码并进行比较。

当我们有多种方式来做一件事情时,我们不知道哪种方式运行更快的另一个原因是要考虑上下文。在上一节中,我们执行一个大小写不敏感的字符串查询来寻找1个字符串是否在其它26个字符串中。当我们换一个角度来比较1个字符串是否在其他100,000个字符串中时,结论可能是完全不同的。

上面的列表不是很完整的,因为还有更多的缺陷需要我们去发现。例如,测试不现实的场景或者只在JavaScript引擎上测试。但是确定的是对于JavaScript开发者来说,如果你想编写更好更快的Web应用,performance.now()是一个很棒的方法。最后但并非最不重要,请谨记衡量执行时间只是“更好的代码”的一反面。我们还要考虑内存消耗以及代码复杂度。

three.js 学习

创建场景

    三个要素: 场景,相机,渲染器

var scene = new THREE.Scene();
var camera = new THREE.PerspectiveCamera( 75, window.innerWidth / window.innerHeight, 0.1, 1000 );

var renderer = new THREE.WebGLRenderer();
renderer.setSize( window.innerWidth, window.innerHeight ); // setSize 
document.body.appendChild( renderer.domElement );

PerspectiveCamera 场景

现在已经设置好了场景,相机,渲染器。 three  有几个不同的渲染器, 我们使用 PerspectiveCamera.

1. 第一个属性是视野, FOV 是任何给定时刻显示器上看到的场景的范围。该值以度为单位。

2. 第二个是设置宽高,

3. 第三个属性与第四个属性是近和远裁剪平面。这意味着,远离相机和物体远远超过或接近近处的物体不会被渲染,

WebGLRenderer 渲染器

 除了 webGLRenderer 之外 还有其他一些功能,通常用作旧版浏览器的用户的回退功能,或者由于某些原因而无法支持 WebGL 的用户。

添加到页面

我们将 renderer 元素添加到 HTML 文档中。这是渲染器用来向我们显示场景的 <canvas> 元素。

setSize 

除了创建渲染器实例外,设置 程序显示的实际大小,如果想保持应用程序的大小,但以较低的分辨率渲染它,可以通过 false 作为 updateStyle (第三个参数)调用 setSize 来实现,例如 setSize(window.innerWidth / 2, window.innerHeight / 2, false)

git 用了好久,但是都比较粗浅,记忆并不深刻,准备好好整理一下

Git 第一步

1. 每次初始化一个厂库,很简单 

git init 

2.  新建一个文件,把文件添加到厂库, 命令 git add

git add 文件名

3. 用命令 git commit 提交到厂库

git commit -m "第一次提交" // -m "这里是提交的描述"

-m后面输入的是本次提交的说明,可以输入任意内容,当然最好是有意义的,这样你就能从历史记录里方便地找到改动记录。

嫌麻烦不想输入-m “xxx”行不行?确实有办法可以这么干,但是强烈不建议你这么干,因为输入说明对自己对别人阅读都很重要。

git commit 命令执行成功后会告诉你,1个文件被改动(我们新添加的 文件),插入了两行内容( 有两行内容)。

为什么Git添加文件需要add,commit一共两步呢?因为commit可以一次提交很多文件,所以你可以多次add不同的文件

Git 时光机回退

回到过去

1. 在Git中,用HEAD表示当前版本,也就是最新的提交3628164…882e1e0(注意我的提交ID和你的肯定不一样),上一个版本就是HEAD^,上上一个版本就是HEAD^^,当然往上100个版本写100个^比较容易数不过来,所以写成HEAD~100

git reset --hard HEAD^

2. 最新的那个版本 已经看不到了!好比你从21世纪坐时光穿梭机来到了19世纪,想再回去已经回不去了,肿么办?

 办法其实还是有的,只要上面的命令行窗口还没有被关掉,你就可以顺着往上找啊找啊 就可以指定回到未来的某个版本。

git reset --hard 版本号  

版本号没有必要写全,前几位就可以了,Git会自动去找。当然也不能只写前一两位,因为 Git 可能会找多个版本号,就无法确定是哪一个了

Git的版本回退速度非常快,因为Git在内部有个指向当前版本的HEAD指针。 现在,你回退到了某个版本,关掉了电脑,第二天早上就后悔了,想恢复到新版本怎么办?找不到新版本的commit id怎么办?

在Git中,总是有后悔药可以吃的。当你用git reset –hard HEAD^ 回退到 某个 版本时,再想恢复到 以前版本 ,就必须找到 commit id。Git提供了一个命令git reflog用来记录你的每一次命令。


git reflog

  • HEAD 指向的版本就是当前版本,因此,Git允许我们在版本的历史之间穿梭,使用命令 git reset –hard commit_id。

  • 穿梭前,用 git log 可以查看提交历史,以便确定要回退到哪个版本。

  • 要重返未来,用 git reflog 查看命令历史,以便确定要回到未来的哪个版本。

微信网页样式经常跑偏一大部分是网页字体问题
;(function () {  
 if (typeof WeixinJSBridge == "object" && typeof WeixinJSBridge.invoke == "function") {
  handleFontSize(); 
 } else { 
 if (document.addEventListener) { 
 document.addEventListener("WeixinJSBridgeReady", handleFontSize, false); 
 } else if (document.attachEvent) {  
document.attachEvent("WeixinJSBridgeReady", handleFontSize); 
 document.attachEvent("onWeixinJSBridgeReady", handleFontSize);  
}  }   function handleFontSize() { 
 // 设置网页字体为默认大小 
 WeixinJSBridge.invoke('setFontSizeCallback', {'fontSize': 0}); 
 // 重写设置网页字体大小的事件  
WeixinJSBridge.on('menu:setfont', function () {  WeixinJSBridge.invoke('setFontSizeCallback', {'fontSize': 0});  });  }
 })()

Promise 对象用于一个异步操作的最终完成(或失败)及其结果值的表示。(简单点说就是处理异步请求。我们经常会做些承诺,如果我赢了你就嫁给我,如果输了我就嫁给你之类的诺言。这就是promise的中文含义:诺言,一个成功,一个失败。)

语法:

new Promise( function(resolve, reject) {...} /* executor */  );

参数:

executor 

    executor 是一个带有 resolve 和 reject 两个参数的函数,executor 函数在 Promise 构造函数执行时同步执行,被传递 resolve  和 reject 函数 (executor 函数在 Promise 构造函数返回新建对象前被调用)。resolve 和 reject 函数被调用时,分别将 promise 的状态改为 fulfilled(完成)或 rejected(失败)。executor 内部通常会执行一个异步操作,一旦完成,可以调用 resolve 函数将 promise 状态改成 fulfilled,或者在发生错误是将它的状态改为 rejected

如果在 executor 函数中抛出一个错误,那么该 promise 状态为 rejected 。executor 函数的返回值被忽略。


描述:

    Promise 对象是一个代理对象(代理一个值),被代理的值在 Promise 对象创建时可能是未知的。他允许你为异步操作的成功和失败费别绑定相应的处理方法(handlers)。这让异步方法可以像同步那样返回值,但并不是立即返回最终执行结果,而是一个能代表未来出现的结果的 Promise 对象

一个 Promise 有以下几种状态:

  • pending : 初始状态,成功或者失败状态
  • fulfilled:意味着操作成功完成。
  • rejected:意味着操作失败。

pending 状态的 Promise 对象可能触发 fulfilled 状态并传递一个值给相应的状态处理方法,也可能触发失败状态(rejected)并传递失败信息。当其中任意一种情况出现是, Promise 对象的 then 方法绑定的处理方法(handlers)就会被调用(then方法包含两个参数:onfulfilled 和 onrejected,它们都是 Function 类型。当 Promise 状态为 fulfilled 时, 调用 then 的 onfulfilled 方法, 当 Promise 状态为 rejected 时, 调用 then 的 onrejected 方法,所以在异步操作完成和绑定处理方法之间不存在竞争)。

因为 Promise.prototype.then 和 Promise.prototype.catch 方法返回 promise 对象, 所以它们可以被链试调用


不要被迷惑了: 有一些语言中有惰性求值和延时计算的特性,它们也被称为“promises”,例如Scheme. Javascript中的promise代表一种已经发生的状态, 而且可以通过回调方法链在一起。 如果你想要的是表达式的延时计算,考虑无参数的”箭头方法“:  f = () =>表达式 创建惰性求值的表达式使用 f() 求值。

注意: 如果一个promise对象处在fulfilled或rejected状态而不是pending状态,那么它也可以被称为settled状态。你可能也会听到一个术语resolved ,它表示promise对象处于fulfilled状态。(此处有待商榷。按本文的原英文翻译resolved表示fulfilled,但是后面的术语链接又表明resolved和fulfilled并不是一样。)关于promise的术语, Domenic Denicola 的 States and fates 有更多详情可供参考。

属性:

Promise.length 

    length 属性,其值总是为1(构造器参数的数目)

Promise.prototype

    表示 Promise 构造器的原型.


方法:

Promise.all(iterable) 

    这个方法返回一个新的promise对象,该promise对象在iterable参数对象里所有的promise对象都成功的时候才会触发成功,一旦有任何一个iterable里面的promise对象失败则立即触发该promise对象的失败。这个新的promise对象在触发成功状态以后,会把一个包含iterable里所有promise返回值的数组作为成功回调的返回值,顺序跟iterable的顺序保持一致;如果这个新的promise对象触发了失败状态,它会把iterable里第一个触发失败的promise对象的错误信息作为它的失败错误信息。Promise.all方法常被用于处理多个promise对象的状态集合。(可以参考jQuery.when方法—译者注)

Promise.race(iterable)

    当iterable参数里的任意一个子promise被成功或失败后,父promise马上也会用子promise的成功返回值或失败详情作为参数调用父promise绑定的相应句柄,并返回该promise对象。

Promise.reject(reason)

    返回一个状态为失败的Promise对象,并将给定的失败信息传递给对应的处理方法

Promise.resolve(value)

    返回一个状态由给定value决定的Promise对象。如果该值是一个Promise对象,则直接返回该对象;如果该值是thenable(即,带有then方法的对象),返回的Promise对象的最终状态由then方法执行决定;否则的话(该value为空,基本类型或者不带then方法的对象),返回的Promise对象状态为fulfilled,并且将该value传递给对应的then方法。通常而言,如果你不知道一个值是否是Promise对象,使用Promise.resolve(value) 来返回一个Promise对象,这样就能将该value以Promise对象形式使用。


创建 Promise

Promise 对象是由关键字 new 及其构造函数来创建的。该构造函数会?把一个叫做“处理器函数”(executor function)的函数作为它的参数。这个“处理器函数”接受两个函数——resolve 和 reject ——作为其参数。当异步任务顺利完成且返回结果值时,会调用 resolve 函数;而当异步任务失败且返回失败原因(通常是一个错误对象)时,会调用reject 函数。

const myFirstPromise = new Promise((resolve, reject) => {
  // ?异步操作,最终调用:
  //
  //   resolve(someValue); // fulfilled
  // ?或
  //   reject("failure reason"); // rejected
});

想要某个函数?拥有promise功能,只需让其返回一个promise即可。

function myAsyncFunction(url) {
  return new Promise((resolve, reject) => {
    const xhr = new XMLHttpRequest();
    xhr.open("GET", url);
    xhr.onload = () => resolve(xhr.responseText);
    xhr.onerror = () => reject(xhr.statusText);
    xhr.send();
  });
};



Array.prototype 属性表示 Array 构造函数的原型,并允许您向所有的 Array 对象添加新的属性和方法。

描述

Array 实例继承自 Array.prototype 。 与所有构造函数一样,可以更改构造函数的原型对象,以对所有的 Array 实例进行更改。 例如,可以添加新的方法和属性以扩展所有 Array 对象。 

Array.prototype 本身也是一个 Array

Array.isArray(Array.prototype)

属性

Array.prototype.constructor

     所有的数组实例都继承了这个属性,它的值就是 Array, 表明了所有的数组都是由 Array 构造函数出来的。

Array.prototype.length

    因为 Array.prototype 也是个数组,所以它也有 length 属性, 这个值为0, 因为它是个空数组。

方法

会改变自身的方法

下面这些方法会改变调用它们的对象自身的值:

Array.prototype.copyWithin()

    在数组内部,将一段元素序列拷贝到另一段元素序列上,覆盖原有的值。

Array.prototype.fill()

    将数组中指定区间的所有元素的值,都替换成某个固定的值。

Array.prototype.pop()

    删除数组的最后一个元素,并返回这个元素。

Array.prototype.push()

    在数组的末尾增加一个或多个元素,并返回数组的新长度。

Array.prototype.reverse()

    颠倒数组中元素的排列顺序,即原先的第一个变为最后一个,原先的最后一个变为第一个。

Array.prototype.shift()

    删除数组的第一个元素,并返回这个元素。

Array.prototype.sort()

    对数组元素进行排序,并返回当前数组。

Array.prototype.splice()

    在任意的位置给数组添加或删除任意个元素。

Array.prototype.unshift()

    在数组的开头增加一个或多个元素,并返回数组的新长度。

不会改变自身的方法

下面的这些方法绝对不会改变调用它们的对象的值,只会返回一个新的数组或者返回一个其他的期望值

Array.prototype.concat()

    返回一个由当前数组和其它若干个数组或者若干个非数组值组合而成的新数组。

Array.prototype.includes()

    判断单签数字是否包含某指定值,如果是返回 true ,否则返回 false。

Array.prototype.join()

    连接所有数组元素组成一个字符串。

Array.prototype.slice()

    抽取当前数组中的一段元素组合成一个新的数组。

Array.prototype.toSource()  // 这个 API 并没有标准化

    返回一个表示当前数组字面量的字符串。遮蔽了原型链上的 Object.prototype.toSource() 方法。

Array.prototype.toString()

    返回一个由所有数组元素组合而成的字符串。遮蔽了原型链上的 Object.prototype.toString() 方法

Array.prototype.toLocaleString()

   返回一个由所有数组元素组合而成的本地化后的字符串。遮蔽了原型链上的 Object.prototype.toLocaleString() 方法。

Array.prototype.indexOf()

    返回数组中第一个与指定值相等的元素的索引,如果找不到这样的元素,则返回 -1。

Array.prototype.lastIndexOf()

    返回数组中最后一个(从右边数第一个)与指定值相等的元素的索引,如果找不到这样的元素,则返回 -1。


遍历方法

    在下面的众多遍历方法中,有很多方法都需要指定一个回调函数作为参数。在回调函数执行之前,数组的长度会被缓存在某个地方,所以,如果你在回调函数中为当前数组添加了新的元素,那么那些新添加的元素是不会被遍历到的。此外,如果在回调函数中对当前数组进行了其它修改,比如改变某个元素的值或者删掉某个元素,那么随后的遍历操作可能会受到未预期的影响。总之,不要尝试在遍历过程中对原数组进行任何修改,虽然规范对这样的操作进行了详细的定义,但为了可读性和可维护性,请不要这样做

Array.prototype.forEach()

    为数组中的每个元素执行一次回调函数。

Array.prototype.entries() 

    返回一个数组迭代器对象,该迭代器会包含所有数组元素的键值对。

Array.prototype.every()

    如果数组中的每个元素都满足测试函数,则返回 true,否则返回 false。

Array.prototype.some()

    如果数组中至少有一个元素满足测试函数,则返回 true,否则返回 false。

Array.prototype.filter()

    将所有在过滤函数中返回 true 的数组元素放进一个新数组中并返回。

Array.prototype.find() 

    找到第一个满足测试函数的元素并返回那个元素的值,如果找不到,则返回 undefined。

Array.prototype.findIndex() 

    找到第一个满足测试函数的元素并返回那个元素的索引,如果找不到,则返回 -1。

 Array.prototype.keys() 

    返回一个数组迭代器对象,该迭代器会包含所有数组元素的键。Array.prototype.map() 返回一个由回调函数的返回值组成的新数组。

Array.prototype.reduce()

    从左到右为每个数组元素执行一次回调函数,并把上次回调函数的返回值放在一个暂存器中传给下次回调函数,并返回最后一次回调函数的返回值。

Array.prototype.reduceRight()

    从右到左为每个数组元素执行一次回调函数,并把上次回调函数的返回值放在一个暂存器中传给下次回调函数,并返回最后一次回调函数的返回值。

 Array.prototype.values() 

    返回一个数组迭代器对象,该迭代器会包含所有数组元素的值。

Array.prototype[@@iterator]()

    和上面的 values() 方法是同一个函数。


开始渲染时间

该时间点表示浏览器开始绘制页面,在此之前页面都是白屏,所以也称为白屏时间。

该时间点可用公式Time To Start Render = TTFB(Time To First Byte) + TTDD(Time To Document Download) + TTHE(Time To Head End)表示。其中TTFB表示浏览器发起请求到服务器返回第一个字节的时间,TTDD表示从服务器加载HTML文档的时间,TTHE表示文档头部解析完成所需要的时间。在高级浏览器中有对应的属性可以获取该时间点。Chrome可通过chrome.loadTimes().firstPaintTime获取,IE9+可以通过performance.timing.msFirstPaint获取,在不支持的浏览器中可以根据上面公式通过获取头部资源加载完的时刻模拟获取近似值。开始渲染时间越快,用户就能更快的看见页面。

对于该时间点的优化有:

1)优化服务器响应时间,服务器端尽早输出

2)减少html文件大小

3)减少头部资源,脚本尽量放在body中


DOM Ready

该时间点表示dom解析已经完成,资源还没有加载完成, 这个时候用户与页面的交互已经可用了。用公式TimeTo Dom Ready = TTSR(Time To Start Render) + TTDC(Time To Dom Created) + TTST(Time To Script)可以表示。TTSR上面已经介绍过了,TTDC表示DOM树创建所耗时间。TTST表示BODY中所有静态脚本加载和执行的时间。在高级浏览器中有DOMContentLoaded事件对应,MDN上有关DOMContentLoaded事件描述的文档如下,

The DOMContentLoaded event is fired when the document has been completely loaded and parsed, without waiting for stylesheets, images, and subframes to finish loading (the load event can be used to detect a fully-loaded page).

详细规范可以查看W3C的HTML5规范。从MDN文档上可以看出该事件主要是指dom文档加载解析完成,看上去很简单,但是DOMContentLoaded事件的触发与css,js息息相关,现在有专门的名词Critical Rendering Path(关键呈现路径)来描述,在文章【关键呈现路径】中详细介绍了关键呈现路径对DOMContentLoaded的影响。

在不支持DOMContentLoaded事件的浏览器中可以通过模拟获取近似值,主要的模拟方法有:

1)低版本webkit内核浏览器可以通过轮询document.readyState来实现

2)ie中可通过setTimeout不断调用documentElement的doScroll方法,直到其可用来实现

具体实现方法可以参考主流框架(jquery等)的实现。 DOM Ready时间点意味着用户与页面可以进行交互了,因此越早越好,对于该时间点的优化有:

1)减少dom结构的复杂度,节点尽可能少,嵌套不要太深

2)优化关键呈现路径


首屏时间

该时间点表示用户看到第一屏页面的时间,这个时间点很重要但是很难获取,一般都只能通过模拟获取一个近似时间。一般模拟方法有:

1)不断获取屏幕截图,当截图不再变化时,可以视为首屏时间。可参考webPagetest的Speed Index算法;

2)一般影响首屏的主要因素是图片的加载,通过页面加载完后判断图片是否在首屏内,找出加载最慢的一张即可视为首屏时间。当然还需考虑其他细节,具体可参考【7天打造前端性能监控系统】

针对该时间点的优化有:

1)页面首屏的显示尽量不要依赖于js代码,js尽量放到domReady后执行或加载

2)首屏外的图片延迟加载

3)首屏结构尽量简单,首屏外的css可延迟加载


onload

该时间点是window.onload事件触发的时间,表示原始文档和所有引用的内容已经加载完成,用户最明显的感觉就是浏览器tab上loading状态结束。 

该时间点的优化方式有:

1)减少资源的请求数和文件大小

2)将非初始化脚本放到onLoad之后执行

3)无需同步的脚本异步加载

为了优化整站性能,页面onload的时候可以考虑做一些预加载,把其它页面需要用到的资源预先加载进来。

在Web开发的时候经常会遇到浏览器不响应事件进入假死状态,甚至弹出“脚本运行时间过长“的提示框,如果出现这种情况说明你的脚本已经失控了,必须进行优化。
浏览器内核是多线程的,它们在内核控制下相互配合以保持同步,一个浏览器至少实现三个常驻线程, GUI 渲染线程, 浏览器事件触发线程。

  1. JavaScript 引擎是基于事件驱动的单线程执行的, JS 线程一直等待着任务队列中任务的到来然后加以处理, 浏览器无论再什么时候都只是一个 JS 线程在运行 JS 程序。
  2. GUI 渲染线程负责渲染浏览器界面,当界面需要重绘(Repaint)或由于某种操作引发回流(reflow)时,该线程就会执行。但需要注意 GUI渲染线程与JS引擎是互斥的,当JS引擎执行时GUI线程会被挂起,GUI更新会被保存在一个队列中等到JS引擎空闲时立即被执行.
  3. 事件触发线程,当一个事件被触发时该线程会把事件添加到待处理队列的队尾,等待JS引擎的处理。这些事件可来自JavaScript引擎当前执行的代码块如setTimeOut、也可来自浏览器内核的其他线程如鼠标点击、AJAX异步请求等,但由于JS的单线程关系所有这些事件都得排队等待JS引擎处理。

    了解了浏览器的内核处理方式就不难理解浏览器为什么会进入假死状态了,当一段JS脚本长时间占用着处理机就会挂起浏览器的GUI更新,而后面的事件响应也被排在队列中得不到处理,从而造成了浏览器被锁定进入假死状态。另外JS脚本中进行了DOM操作,一旦JS调用结束就会马上进行一次GUI渲染,然后才开始执行下一个任务,所以JS中大量的DOM操作也会导致事件响应缓慢甚至真正卡死浏览器,如在IE6下一次插入大量的HTML。而如果真的弹出了“脚本运行时间过长“的提示框则说明你的JS脚本肯定有死循环或者进行过深的递归操作了。

       Nicholas C. Zakas认为不论什么脚本,在任何时间、任何浏览器上执行都不应该超过100毫秒,否则一定要将脚本分解成若干更小的代码段。那么我们该如何来做呢: 

        第一步,优化你的循环,循环体中包含太多的操作和循环的次数过多都会导致循环执行时间过长,并直接导致锁死浏览器。如果循环之后没有其他操作,每次循环只处理一个数值,而且不依赖于上一次循环的结果则可以对循环进行拆解,看下面的chunk的函数:

function chunk (array, process, context) { 
    setTimeout(function() {
         var item = array.shift()
         process.call(context, item) 
        if(array.length > 0) {
             setTimeout(arguments.callee, 100) 
        }
     }, 100) 
}

        chunk() 函数的用途就是将一个数组分成小块处理,它接受三个参数:要处理的数组,处理函数以及可选的上下文环境。每次函数都会将数组中第一个对象取出交给 process 函数处理,如果数组中还有对象没有被处理则启动下一个 timer,直到数组处理完。这样可保证脚本不会长时间占用处理机,使浏览器出一个高响应的流畅状态。

        借助JS强大的闭包机制任何循环都是可拆分的,下面的版本增加了 callback 机制,使可再循环处理完毕之后进行其他的操作。

function chunk ( array,  process,  cbfun ) { 
      var i  = 0, len = array.length  // 这里是要注意在执行过程中数组最好是不变的
      setTimeout(function () {  // 循环体要做的操作 
      process(array[i], i++)
         if( i < len ) { 
            setTimeout(farguments.callee, 100)
         }else { 
        cbfun()     // 循环结束之后要做的操作
         }
     }, 100} 
} 

    第二步, 优化你的函数,如果函数体内有太多不相干但又要在一起执行的操作则可以进行拆分,考虑下面的函数:

function dosomething () { 
    dosomething1() 
    dosomething2()
 }

    第三步,优化递归操作,函数递归虽然简单直接但是过深的递归操作不但影响性能而且稍不注意就会导致浏览器弹出脚本失控对话框,必须小心处理。

    看以下斐波那契数列的递归算法:

 function fibonacci (n) {
    return n < 2 ? n : fibonacci(n - 1) + fibonacci(n - 2)
}

   fibonacci(40) 这条语句将重复调用自身331160280次,在浏览器中执行必然导致脚本失控,而采用下面的算法则只需要调用40次

fibonacci = function (n) {
    var memo = {0 : 0, 1 : 0}                            // 计算结果缓存
        var shell = function (n) {
            if ( typeof result != 'number' )         //  如果只没有被计算则进行计算
                memo[n] = shell (n - 1) + shell(n - 2) 
                return memo[n]
        }
    return shell(n)
}

这项技术被称为memoization,他的原理很简单就是同样的结果你没必要计算两次。另一种消除递归的办法就是利用迭代,递归和迭代经常会被作为互相弥补的方法。

第四步,减少DOM操作,DOM操作的代价是相当昂贵的,大多数DOM操作都会触发浏览器的回流(reflow)操作。例如添加删除节点,修改元素样式,获取需要经过计算的元素样式等。我们要做的就是尽量少的触发回流操作。

 

el.style.width = '300px' el.style.height = '300px'
el.style.backgroundColor = 'red'

上面的操作会触发浏览器的三次回流操作,再看下面的方式:

el.className = 'newStyle'

通过设置改元素的className一次设置多个样式属性,将样式写再CSS文件中,只触发一次回流,达到了同样是效果而且效率更高。因为浏览器最擅长的就是根据class设置样式。

还有很多可以减少DOM操作的方法,在此就不多说了,但是一个基本的原则就是让浏览器去做它自己擅长的事情,例如通过class来改变元素的属性。


vue 中显示隐藏问题

vue  使用当用 遇到一个 temple 模板里面的坑,就是 v-if  和 v-show 不显示的问题,需要在标签中加入 style=”display:black”

vue 中组建之间的传参问题

1.父向子组件传参

App.vue为父,引入componetA组件之后,则可以在App.vue中使用标签(注意驼峰写法要改成componet-a写法,因为html对大小写不敏感,componenta与componentA对于它来说是一样的,不好区分,所以使用小写-小写这种写法)。

而子组件componetA中,声明props参数’msgfromfa’之后,就可以收到父向子组件传的参数了。例子中将msgfromfa显示在<p>标签中。

App.vue中
<component-a msgfromfa="( 不积跬步,无以至千里;不积小流,无以成江海)"></component-a>
import componentA from './components/componentA'
export default {
    new Vue({
        components: {
          componentA
        }
    })
}

componentA.vue中
<p>{{ msgfromfa }}</p>
export default {
    props: ['msgfromfa']
}

2.子组件向父传参

2.1  .$emit

用法:vm.$emit( event, […args] ),触发当前实例上的事件。附加参数都会传给监听器回调。
例子:

App.vue中component-a绑定了自定义事件”child-say”。

子组件componentA中,单击按钮后触发”child-say”事件,并传参msg给父组件。父组件中listenToMyBoy方法把msg赋值给childWords,显示在<p>标签中。

App.vue中
<p>Do you like me? {{childWords}}</p>
<component-a msgfromfa="(Just Say U Love Me)" v-on:child-say="listenToMyBoy"></component-a>
import componentA from './components/componentA'
export default {
    new Vue({
        data: function () {
            return {
              childWords: ""
            }
        },
        components: {
          componentA
        },
        methods: {
            listenToMyBoy: function (msg){
              this.childWords = msg
            }
        }
    })
}
componentA.vue中
<button v-on:click="onClickMe">like!</button>
import componentA from './components/componentA'
export default {
    data: function () {
        return {
          msg: 'I like you!'
        }
    },
    methods: {
      onClickMe: function(){
        this.$emit('child-say',this.msg);
      }
    }
}

2.2  .$dispatch

用法:vm.$dispatch( event, […args] ),派发事件,首先在实例上触发它,然后沿着父链向上冒泡在触发一个监听器后停止。
例子:App.vue中events中注册”child-say”事件。子组件componentA中,单击按钮后触发”child-say”事件,并传参msg给父组件。父组件中”child-say”方法把msg赋值给childWords,显示在<p>标签中。

App.vue中
<p>Do you like me? {{childWords}}</p>
<component-a msgfromfa="(Just Say U Love Me)"></component-a>
import componentA from './components/componentA'
export default {
    new Vue({
        events: {
            'child-say' : function(msg){
              this.childWords = msg
            }
        }
    })
}
componentA.vue中
<button v-on:click="onClickMe">like!</button>
import componentA from './components/componentA'
export default {
    data: function () {
        return {
          msg: 'I like you!'
        }
    },
    methods: {
      onClickMe: function(){
        this.$dispatch('child-say',this.msg);
      }
    }
}

vue 中 axios 请求

axios 请求的坑:请求当中的 axios 在浏览器中需要 URLSearchParams 的支持

var params = new URLSearchParams();
params.append('param1','value1');
params.append('param2','value2');
axios.post('/foo',params);

// 注意: URLSearchParams  有兼容性问题 ios 10.11 以下不支持 需要兼容,请看 dome

一、DOM操作影响页面性能的核心问题

通过js操作DOM的代价很高,影响页面性能的主要问题有如下几点:

  • 访问和修改DOM元素
  • 修改DOM元素的样式,导致重绘重排
  • 通过对DOM元素的事件处理,完成与用户的交互功能

DOM的修改会导致重绘重排

  • 重绘是指一些样式的修改,元素的位置和大小都没有改变;
  • 重排是指元素的位置或尺寸发生了变化,浏览器需要重新计算渲染树,而新的渲染树建立后,浏览器会重新绘制受影响的元素。

页面重绘的速度要比页面重排的速度快,在页面交互中要尽量避免页面的重排操作。浏览器不会在js执行的时候更新DOM,而是会把这些DOM操作存放在一个队列中,在js执行完之后按顺序一次性执行完毕,因此在js执行过程中用户一直在被阻塞。

1.页面渲染过程

一个页面更新时,渲染过程大致如下:

  • JavaScript: 通过js来制作动画效果或操作DOM实现交互效果
  • Style: 计算样式,如果元素的样式有改变,在这一步重新计算样式,并匹配到对应的DOM上
  • Layout: 根据上一步的DOM样式规则,重新进行布局(重排
  • Paint: 在多个渲染层上,对新的布局重新绘制(重绘
  • Composite: 将绘制好的多个渲染层合并,显示到屏幕上

在网页生成的时候,至少会进行一次布局和渲染,在后面用户的操作时,不断的进行重绘或重排,因此如果在js中存在很多DOM操作,就会不断地出发重绘或重排,影响页面性能。

2.DOM操作对页面性能的影响

如前面所说,DOM操作影响页面性能的核心问题主要在于DOM操作导致了页面的重绘重排,为了减少由于重绘和重排对网页性能的影响,我们要知道都有哪些操作会导致页面的重绘或者重排。

2.1 导致页面重排的一些操作:

  • 内容改变
    • 文本改变或图片尺寸改变
  • DOM元素的几何属性的变化
    • 例如改变DOM元素的宽高值时,原渲染树中的相关节点会失效,浏览器会根据变化后的DOM重新排建渲染树中的相关节点。如果父节点的几何属性变化时,还会使其子节点及后续兄弟节点重新计算位置等,造成一系列的重排。
  • DOM树的结构变化
    • 添加DOM节点、修改DOM节点位置及删除某个节点都是对DOM树的更改,会造成页面的重排。浏览器布局是从上到下的过程,修改当前元素不会对其前边已经遍历过的元素造成影响,但是如果在所有的节点前添加一个新的元素,则后续的所有元素都要进行重排。
  • 获取某些属性
    • 除了渲染树的直接变化,当获取一些属性值时,浏览器为取得正确的值也会发生重排,这些属性包括:offsetTopoffsetLeft、 offsetWidthoffsetHeightscrollTopscrollLeftscrollWidthscrollHeight、 clientTopclientLeftclientWidthclientHeightgetComputedStyle()
  • 浏览器窗口尺寸改变
    • 窗口尺寸的改变会影响整个网页内元素的尺寸的改变,即DOM元素的集合属性变化,因此会造成重排。

2.2 导致页面重绘的操作

  • 应用新的样式或者修改任何影响元素外观的属性
    • 只改变了元素的样式,并未改变元素大小、位置,此时只涉及到重绘操作。
  • 重排一定会导致重绘
    • 一个元素的重排一定会影响到渲染树的变化,因此也一定会涉及到页面的重绘。

二、高频操作DOM会导致的问题

接下来会分享一下在平时项目中由于高频操作DOM影响网页性能的问题。

1. 抽奖项目的高频操作DOM问题

1.1 存在的问题

在最近做的抽奖项目中,就遇到了这样的由于高频操作DOM,导致页面性能变差的问题。在经历几轮抽奖后,文字滚动速度越来越慢,肉眼能感受到与第一次抽奖时文字滚动速度的明显差别,如持续时间过长或轮次过多,还会造成浏览器假死现象。

实现 demo

1.2 问题分析

下图为抽奖时文字滚动过程中的timeline记录。

timeline分析:

  1. FPS:最上面一栏为绿色柱形为帧率(FPS),顶点值为60fps,上方红色方块表示长帧,这些长帧被Chrome称为jank(卡顿)。
  2. CPU:第二栏为CPU,蓝色表示loading(网络通信和HTML解析),黄色表示scripting(js执行时间),紫色表示rendering(样式计算和布局,即重排), 绿色为painting(即重绘)。

更多timeline使用方法可参考:如何使用Chrome Timeline 工具(译)

由上图可以看出,在文字滚动过程中红色方块出现频繁,页面中存在的卡顿过多。帧率的值越低,人眼感受到的效果越差。

接下来选择一段长帧区域放大来看

在这段区域内最大一帧达到了49.7ms,帧率只有20fps,接下来看看这一帧里是什么因素耗时过长


由上图可以看出,耗时最大的在scripting,js的执行时间达到了44.9ms,占总时间的93.2%,因为主要靠js计算控制DOM的显示内容,所以js运行时间过长。

选取一段FPS值很低的部分查看造成这段值低的原因

由下图可看出主要为dom.html中的js执行占用时间。

点进dom.html文件,即可定位到该函数

由此可知,主要是rolling这个函数执行时间过长,对该部分失帧影响较大。而这个函数的主要作用就是实现文字的滚动效果,也可以从代码中看出,这个函数利用的setTimeout来反复执行,并且在这个函数中存在着循环以及大量的DOM操作,造成了页面的失帧等问题。

1.3 优化方案

针对该项目中的问题,采取的解决方法是:

  • 一次性生成全部<li>,并且隐藏这些<li>,随机生成一组随机数数组,只有index与数组里面的随机数相等时,才显示该位置的<li>,虽然也会触发重排和重绘,但是性能要远远高于直接操作DOM的添加和删除。
  • 用requestAnimationFrame取代setTimeout不断生成随机数。

requestAnimationFrame与setTimeout和setInterval类似,都是通过递归调用同一个方法不断更新页面。

  • setTimeout():在特定的时间后执行函数,而且只执行一次,如果在特定时间前想取消执行函数,可以用clearTimeout立即取消执行。但是并不是每次执行setTimeout都会在特定的时间后执行,页面加载后js会按照主线程中的顺序按序执行那个,如果在延迟时间内主线程不空闲,setTimeout里面的函数是不会执行的,它会延迟到主线程空闲时才执行。
  • setInterval():在特定的时间间隔内重复执行函数,除非主动清除它,不然会一直执行下去,清除函数可以使用clearInterval。setInterval也会等到主线程空闲了再执行,但是setInterval去排队时,如果发现自己还在队列中未执行,就会被drop掉,所以可能会造成某段时间的函数未被执行。
  • requestAnimationFrame():它不需要设置时间间隔,它会在浏览器每次刷新之前执行回调函数的任务。这样我们动画的更新就能和浏览器的刷新频率保持一致。requestAnimationFrame在运行时,浏览器会自动优化方法的调用,并且如果页面不是激活状态下的话,动画会自动暂停,有效节省了CPU开销。

在采用上面的方法进行优化后,在经历多轮抽奖后,文字滚动速度依旧正常,网页性能良好,不会出现文字滚动速度越来越慢,最后导致浏览器假死的现象。

实现demo: https://gxt19940130.github.io/demo/demo_gxt/dom_by_vue.html

1.4 优化前后FPS对比

优化前文字滚动时的timeline


优化后文字滚动时的timeline

优化前的代码对DOM操作很频繁,因此FPS值普遍偏低,而优化后可以看出红色方块明显减少,FPS值一直处于高值。

1.5 优化前后CPU占用对比

优化前文字滚动时的timeline


优化后文字滚动时的timeline


优化前js的CPU占用率较高,而优化后占用CPU的主要为渲染时间,因为优化后的代码只是控制了节点的显示和隐藏,所以在js上消耗较少,在渲染上消耗较大。

2.吸顶导航条相关及scroll滚动优化

2.1 存在的问题

吸顶导航条要求当页面滚动到某个区域时,对应该区域的导航条在设置的显示范围内保持吸顶显示。涉及到的操作:

  • 监听页面的scroll事件
  • 在页面滚动时进行计算和DOM操作
    • 计算:计算当前所在位置是否为对应导航条的显示范围
    • DOM操作:显示在范围内的导航条并且隐藏其他导航条

由于scroll事件被触发的频率高、间隔近,如果此时进行DOM操作或计算并且这些DOM操作和计算无法在下一次scroll事件发生前完成,就会造成掉帧、页面卡顿,影响用户体验。

2.2 优化方案

针对该项目中的问题,采取的解决方法是:

  • 尽量控制DOM的显示或隐藏,而不是删除或添加:

    页面加载时根据当前页面中吸顶导航的数量复制对应的DOM,并且隐藏这些导航。当页面滚动到指定区域后,显示对应的导航。

  • 一次性操作DOM:

    将复制的DOM存储到数组中,将该数组append到对应的父节点下,而不是根据复制得到DOM的数量依次循环插入到父节点下。

  • 多做缓存:

    如果某个节点将在后续进行多次操作,可以将该节点利用变量存储起来,而不是每次进行操作时都去查找一遍该节点。

  • 使用 requestAnimationFrame优化页面滚动

// 在页面滚动时对显示范围进行计算

  // 延迟到整个dom加载完后再调用,并且异步到所有事件后执行

  $(function(){

  //animationShow优化滚动效果,scrollShow为实际计算显示范围及操作DOM的函数

      setTimeout( function() {

          window.Scroller.on(‘scrollend’, animationShow);

          window.Scroller.on(‘scrollmove’, animationShow);

      })

  });

  function animationShow(){

      return window.requestAnimationFrame ?window.requestAnimationFrame(scrollShow) : scrollShow();

  }

对于scroll的滚动优化还可以采用防抖(Debouncing)和节流(Throttling)的方式,但是防抖和节流的方式还是要借助于setTimeout,因此和requestAnimationFrame相比,还是requestAnimationFrame实现效果好一些。

三、针对操作DOM的性能优化方法总结

为了减少DOM操作对页面性能产生的影响,在实现页面的交互效果时一定要注意一下几点:

1.减少在循环内进行DOM操作,在循环外部进行DOM缓存

//优化前代码

function Loop() {

   console.time(“loop1”);

   for (var count = 0; count < 15000; count++) {

       document.getElementById(‘text’).innerHTML += ‘dom’;

   }

   console.timeEnd(“loop1”);

}

//优化后代码

function Loop2() {

    console.time(“loop2”);

    var content = ;

    for (var count = 0; count < 15000; count++) {

        content += ‘dom’;

    }

    document.getElementById(‘text2’).innerHTML += content;

    console.timeEnd(“loop2”);

}

两个函数的执行时间对比:

优化前的代码中,每进行一次循环,都会读取一次divinnerHtml属性,并且对这个属性进行了重新赋值,即每循环一次就会操作两次DOM,因此执行时间很长,页面性能差。

在优化后的代码中,将要更新的DOM内容进行缓存,在循环时只操作字符串,循环结束后字符串的值写入到div中,只进行了一次查找innerHtml属性和一次对该属性重新赋值的操作,因此同样的循环次数先,优化后的方法执行时间远远少于优化前。

2.只控制DOM节点的显示或隐藏,而不是直接去改变DOM结构

在抽奖项目中频繁操作DOM来控制文字滚动的方法(demo:https://gxt19940130.github.io/demo/dom.html 导致页面性能很差,最后修改为如下代码。

<div class=“staff-list” :class=“list”>

   <ul class=“staff-list-ul”>

       <li v-for=“item in staffList” v-show=“isShow($index)”>

           <div>{{{item.staff_name | addSpace}}} </div>

           <div class=“staff_phone”>{{item.phone_no}} </div>

       </li>

   </ul>

</div>

上面代码的优化原理即先生成所有DOM节点,但是所有节点均不显示出来,利用vue.js中的v-show,根据计算的随机数来控制显示某个<li>,来达到文字滚动效果。

如果采用jquery,则需要将生成的所有<li>全部存放在<ul>下,并且隐藏它们,在根据生成的随机数组,利用jquery查找index与生成的随机数对应的<li>并显示,达到文字滚动效果。

优化后demo: https://gxt19940130.github.io/demo/demo_gxt/dom_by_vue.html

对比结果可查看2.4

3.操作DOM前,先把DOM节点删除或隐藏

var list1 = $(“.list1”);

list1.hide();

for (var i = 0; i < 15000; i++) {

    var item = document.createElement(“li”);

    item.append(document.createTextNode(‘0’));

    list1.append(item);

}

list1.show();

display属性值为none的元素不在渲染树中,因此对隐藏的元素操作不会引发其他元素的重排。如果要对一个元素进行多次DOM操作,可以先将其隐藏,操作完成后再显示。这样只在隐藏和显示时触发2次重排,而不会是在每次进行操作时都出发一次重排。

页面rendering时间对比:

下图为同样的循环次数下未隐藏节点直接进行DOM操作的rendering时间(图一)和隐藏节点再进行DOM操作的rendering时间(图二)

由对比图可以看出,总时间、js执行时间以及rendering时间都明显减少,并且避免了painting以及其他的一些操作。

4. 最小化重绘和重排

//优化前代码

var element = document.getElementById(‘mydiv’);

element.style.height = “100px”;  

element.style.borderLeft = “1px”;  

element.style.padding = “20px”;

在上面的代码中,每对element进行一次样式更改都会影响该元素的集合结构,最糟糕情况下会触发三次重排。

优化方式:利用js或jquery对该元素的class重新赋值,获得新的样式,这样减少了多次的DOM操作。

//优化后代码

//js操作

.newStyle {  

    height: 100px;  

    borderleft: 1px;  

    padding: 20px;  

}  

element.className = “newStyle”;

//jquery操作

$(element).css({

    height: 100px;  

    borderleft: 1px;  

    padding: 20px;

})

到此本文结束,如果对于问题分析存在不正确的地方,还请及时指出,多多交流。

参考文章:

  • 高性能JS-DOM
  • Effective前端6:避免页面卡顿
  • 前端性能优化
  • 高性能滚动 scroll 及页面渲染优化
  • 脑洞大开:为啥帧率达到 60 fps 就流畅?
  • 如何使用Chrome Timeline 工具(译)

 

Array.prototype.map()

map() 方法创建一个新数组,其结果是该数组中的每个元素都调用一个提供的函数后返回的结果。

let numbers = [1, 5, 10, 15];
let doubles = numbers.map((x) => {
   return x * 2;
});

// doubles is now [2, 10, 20, 30]
// numbers is still [1, 5, 10, 15]


let numbers = [1, 4, 9];
let roots = numbers.map(Math.sqrt);

// roots is now [1, 2, 3]
// numbers is still [1, 4, 9]

语法

let array = arr.map(function callback(currentValue, index, array) { 
    // Return element for new_array 
}[, thisArg])

参数

callback
生成新数组元素的函数,使用三个参数:

currentValue
callback 的第一个参数,数组中正在处理的当前元素。
index
callback 的第二个参数,数组中正在处理的当前元素的索引。
array
callback 的第三个参数,map 方法被调用的数组。
thisArg
可选的。执行 callback 函数时 使用的this 值。

返回值

一个新数组,每个元素都是回调函数的结果。

描述

map 方法会给原数组中的每个元素都按顺序调用一次  callback 函数。callback 每次执行后的返回值(包括 undefined)组合起来形成一个新数组。 callback 函数只会在有值的索引上被调用;那些从来没被赋过值或者使用 delete 删除的索引则不会被调用。

callback 函数会被自动传入三个参数:数组元素,元素索引,原数组本身。

如果 thisArg 参数有值,则每次 callback 函数被调用的时候,this 都会指向 thisArg 参数上的这个对象。如果省略了 thisArg 参数,或者赋值为 null 或 undefined,则 this 指向全局对象 。

map 不修改调用它的原数组本身(当然可以在 callback 执行时改变原数组)。

使用 map 方法处理数组时,数组元素的范围是在 callback 方法第一次调用之前就已经确定了。在 map 方法执行的过程中:原数组中新增加的元素将不会被 callback 访问到;若已经存在的元素被改变或删除了,则它们的传递到 callback 的值是 map 方法遍历到它们的那一时刻的值;而被删除的元素将不会被访问到。

用一个仅有一个参数的函数来mapping一个数字数组

下面的代码表示了当函数需要一个参数时map的工作方式。这个参数会遍历原始数组中的元素。

var numbers = [1, 4, 9];
var doubles = numbers.map(function(num) {
  return num * 2;
});

// doubles is now [2, 8, 18]
// numbers is still [1, 4, 9]

一般的 map 方法

下面的例子演示如何在一个 String  上使用 map 方法获取字符串中每个字符所对应的 ASCII 码组成的数组:

var map = Array.prototype.map
var a = map.call("Hello World", function(x) { 
  return x.charCodeAt(0); 
})
// a的值为[72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100]

querySelectorAll 应用

下面代码展示了如何去遍历用 querySelectorAll 得到的动态对象集合。在这里,我们获得了文档里所有选中的选项,并将其打印:

var elems = document.querySelectorAll('select option:checked');
var values = Array.prototype.map.call(elems, function(obj) {
  return obj.value;
});

反转字符串

var str = '12345';
Array.prototype.map.call(str, function(x) {
  return x;
}).reverse().join(''); 

// Output: '54321'
// Bonus: use '===' to test if original string was a palindrome

兼容旧环境(Polyfill)Edit

map 是在最近的 ECMA-262 标准中新添加的方法;所以一些旧版本的浏览器可能没有实现该方法。在那些没有原生支持 map 方法的浏览器中,你可以使用下面的 Javascript 代码来实现它。所使用的算法正是 ECMA-262,第 5 版规定的。假定ObjectTypeError, 和 Array 有他们的原始值。而且 callback.call 的原始值也是 Function.prototype.call

// 实现 ECMA-262, Edition 5, 15.4.4.19
// 参考: http://es5.github.com/#x15.4.4.19
if (!Array.prototype.map) {
  Array.prototype.map = function(callback, thisArg) {

    var T, A, k;

    if (this == null) {
      throw new TypeError(" this is null or not defined");
    }

    // 1. 将O赋值为调用map方法的数组.
    var O = Object(this);

    // 2.将len赋值为数组O的长度.
    var len = O.length >>> 0;

    // 3.如果callback不是函数,则抛出TypeError异常.
    if (Object.prototype.toString.call(callback) != "[object Function]") {
      throw new TypeError(callback + " is not a function");
    }

    // 4. 如果参数thisArg有值,则将T赋值为thisArg;否则T为undefined.
    if (thisArg) {
      T = thisArg;
    }

    // 5. 创建新数组A,长度为原数组O长度len
    A = new Array(len);

    // 6. 将k赋值为0
    k = 0;

    // 7. 当 k < len 时,执行循环.
    while(k < len) {

      var kValue, mappedValue;

      //遍历O,k为原数组索引
      if (k in O) {

        //kValue为索引k对应的值.
        kValue = O[ k ];

        // 执行callback,this指向T,参数有三个.分别是kValue:值,k:索引,O:原数组.
        mappedValue = callback.call(T, kValue, k, O);

        // 返回值添加到新数组A中.
        A[ k ] = mappedValue;
      }
      // k自增1
      k++;
    }

    // 8. 返回新数组A
    return A;
  };      
}
 

Array.prototype.some()


some() 方法测试数组中的某些元素是否通过由提供的函数实现的测试。

const isBiggerThan10 = (element, index, array) => {
return element > 10;
}

[2, 5, 8, 1, 4].some(isBiggerThan10);
// false

[12, 5, 8, 1, 4].some(isBiggerThan10);
// true

语法: arr.some(callback[, thisArg])

参数

callback
用来测试每个元素的函数。
thisArg
执行 callback 时使用的 this 值。

描述

some 为数组中的每一个元素执行一次 callback 函数,直到找到一个使得 callback 返回一个“真值”(即可转换为布尔值 true 的值)。如果找到了这样一个值,some 将会立即返回 true。否则,some 返回 falsecallback 只会在那些”有值“的索引上被调用,不会在那些被删除或从来未被赋值的索引上调用。

callback 被调用时传入三个参数:元素的值,元素的索引,被遍历的数组。

如果为 some 提供了一个 thisArg 参数,将会把它传给被调用的 callback,作为 this 值。否则,在非严格模式下将会是全局对象,严格模式下是 undefined

some 被调用时不会改变数组。

some 遍历的元素的范围在第一次调用 callback. 时就已经确定了。在调用 some 后被添加到数组中的值不会被 callback 访问到。如果数组中存在且还未被访问到的元素被 callback 改变了,则其传递给 callback 的值是 some 访问到它那一刻的值。

兼容

if (!Array.prototype.some)
{
Array.prototype.some = function(fun /*, thisArg */)
{
'use strict';

if (this === void 0 || this === null)
throw new TypeError();

var t = Object(this);
var len = t.length >>> 0;
if (typeof fun !== ‘function’)
throw new TypeError();

var thisArg = arguments.length >= 2 ? arguments[1] : void 0;
for (var i = 0; i < len; i++)
{
if (i in t && fun.call(thisArg, t[i], i, t))
return true;
}

return false;
};
}