Eric Chang's Blog


  • 首页

  • 归档

  • 分类

  • 标签

  • 生活

  • 关于

  • 搜索

对Leetcode 264题的一些思考

发表于 2016-05-10 | 分类于 Algorithm |

引言

  • 本文展示了我对于leetcode264题的思考,如有不正确之处,请各位大神多多指教。

题目

  • Write a program to find the n-th ugly number.
  • Ugly numbers are positive numbers whose prime factors only include 2, 3, 5. For example, 1, 2, 3, 4, 5, 6, 8, 9, 10, 12 is the sequence of the first 10 ugly numbers.
  • Note that 1 is typically treated as an ugly number.

解法

  • 思路:下一个丑数是上一个丑数乘以一个数(2 3 5),但是,这样算出来的丑数不一定是有序的,比如,上一个丑数是3,3*2=6也是丑数,但是比他小的4 5也是丑数,就被隔过去了。
  • 所以如果能将上面的思路改写成一个有序的方法就好了。
  • 在丑数的序列中,总能找到这样一个数,它乘2刚刚大于目前序列中最大的丑数,也总能找到这样一个数,它乘3刚刚大于目前序列中最大的丑数,也总能找到这样一个数,它乘4刚刚大于目前序列中最大的丑数,我们要找的“下一个丑数”即为上面三个数中最大的一个。
  • 程序中,我们用m2、m3、m5三个下标,表示上述三个数在已经有序的丑数序列中的位置。
  • 之所以每一循环,三个if均需检测,是因为如果遇到了某个丑数能够有多种方法分解(2 3 5)质因数,那么此时,m2、m3、m5三个下标可能需要同时移动,否则他们乘2(3或5)后就不是刚刚好大于目前序列中最大的丑数了。

程序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
public class Solution {

public int nthUglyNumber(int n) {
if(n==1){
return 1;
}

int m2 = 0;
int m3 = 0;
int m5 = 0;
int tmp = 0;
int count = 1;

int[] uglyNumber = new int[n];
uglyNumber[0]=1;

while(count<n){
tmp = Math.min(uglyNumber[m2]*2,Math.min(uglyNumber[m3]*3,uglyNumber[m5]*5));
if(tmp==uglyNumber[m2]*2) m2++;
if(tmp==uglyNumber[m3]*3) m3++;
if(tmp==uglyNumber[m5]*5) m5++;
uglyNumber[count] = tmp;
count++;
}

return uglyNumber[n-1];
}
}

链表的反转

发表于 2016-05-08 | 分类于 Algorithm |

引言

  • 本文展示了链表的反转的代码,用递归和非递归的方式实现。
  • 详细的链表反转见leetcode 206的解法。

递归的实现

1
2
3
4
5
6
7
8
9
10
11
12
13
public class Solution {
public ListNode reverseList(ListNode head) {
if(head==null) return null;
if(head.next==null) return head;

ListNode p = head.next;
ListNode n = reverseList(p);

head.next = null;
p.next = head;
return n;
}
}

非递归的实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode(int x) { val = x; }
* }
*/

public class Solution {
public ListNode reverseList(ListNode head) {
if(head==null){
return null;
}

ListNode point1 = head;
ListNode point2 = head.next;
ListNode point3 = (point2==null)?null:point2.next;

while(point2!=null){
//System.out.println("----");
point3 = point2.next;
point2.next = point1;
point1 = point2;
point2 = point3;
}
head.next=null;

return point1;
}
}

树的广度优先遍历和深度优先遍历

发表于 2016-05-07 | 分类于 Algorithm |

引言

  • 本文介绍了树的广度优先和深度优先遍历,并给出程序。

原理

  • 广度优先遍历(BFT breadth-first traversal):广度优先遍历是连通图的一种遍历策略,它的思想是从一个顶点V0开始,辐射状地优先遍历其周围较广的区域,故得名。树的广度优先遍历是指优先遍历完某一层的所有节点,然后再依次向下层遍历。
  • 深度优先遍历(DFT depth-first traversal):树的深度优先遍历是指首先遍历树的某个分支上的所有节点,在遍历另一个分支的节点,对于节点中的分支也这样处理。
  • 广度优先和深度优先各有其侧重点,在不同的情境下可以选择使用。
  • 本文不讨论广度优先和深度优先的适用场景,只给出两者的具体实现java代码,以供参考。

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
package test;

import java.util.ArrayDeque;
import java.util.Queue;
import java.util.Stack;

public class Test {

//树的节点的定义
public static class TreeNode {
int val;
TreeNode left;
TreeNode right;

TreeNode(int x) {
val = x;
}
}

//构建树
public static TreeNode BinaryTree(int[] array) {
return makeBinaryTreeByArray(array, 1);
}

public static TreeNode makeBinaryTreeByArray(int[] array, int index) {
if (index < array.length) {
int value = array[index];
if (value != 0) {
TreeNode t = new TreeNode(value);
array[index] = 0;
t.left = makeBinaryTreeByArray(array, index * 2);
t.right = makeBinaryTreeByArray(array, index * 2 + 1);
return t;
}
}
return null;
}

public static void levelOrderTraversal(TreeNode root) {
// 用队列
if (root == null) {
return;
}

Queue<TreeNode> q1 = new ArrayDeque<>();
q1.add(root);
while (!q1.isEmpty()) {
TreeNode tmp = q1.poll();
System.out.print(tmp.val+" ");
if (tmp.left != null) {
q1.add(tmp.left);
}

if (tmp.right != null) {
q1.add(tmp.right);
}
}
}

public static void depthOrderTraversal(TreeNode root){
// 用栈
if(root==null){
return;
}

Stack<TreeNode> s = new Stack<>();
s.push(root);
while(!s.isEmpty()){
TreeNode tmp = s.pop();
System.out.print(tmp.val+" ");
if(tmp.right!=null){
s.push(tmp.right);
}

if(tmp.left!=null){
s.push(tmp.left);
}
}
}

/**
* 13
* / \
* 65 5
* / \ \
* 97 25 37
* / /\ /
* 22 4 28 32
*/

public static void main(String[] args) {
int[] arr = {0,13,65,5,97,25,0,37,22,0,4,28,0,0,32,0};
TreeNode root = BinaryTree(arr);
levelOrderTraversal(root); //13 65 5 97 25 37 22 4 28 32
System.out.print("\n");
depthOrderTraversal(root); //13 65 97 22 25 4 28 5 37 32
}
}

数据库设计原则

发表于 2016-05-06 | 分类于 Database |

引言

  • 本文小结了数据设计原则;
  • 数据库设计对于数据库的可维护性、可扩展性至关重要,某些原则必须严格遵守;

数据库设计范式

  • 第一范式:属性具有原子性,不可再分解,即不能表中有表;
  • 第二范式:唯一性约束,每条记录有唯一标示,所有的非主键字段均需依赖于主键字段;
  • 第三范式:冗余性约束,非主键字段间不能相互依赖;

数据库设计原则

  • 完整性:
    • not null声明禁止插入空值;
    • check子句限制属性域;
  • 去冗余:
    • 避免冗余属性,冗余属性会带来数据不一致性;
    • 学生选课系统中,老师可以开课、学生可以选课,数据库设计中,课程可以由课程编号和课程名称等确定;
    • 如果现在维护两个表,一个表A存储课程信息(课程编号、名称、简介、学分、院系等),另一个表B存储开课信息(有哪些课程开课),如果B中重复存储了A的课程名称、简介、学分、院系等信息,一旦A中的信息更新,B中和A中信息便出现不一致;
    • 正确的做法是,B中只存储课程编号,并以此和A相关联;
  • 解耦合:
    • 一个表只存储它应该存储的信息,和此表无关的信息放到另一个表去存储,表之间尽量解耦;
    • 上面的例子中,A中存储且只存储面向课程的信息,另外有表C,存储且只存储面向学生的信息(学号、姓名、性别、年龄、选课id等),对于“课程级别”的信息,应当坚决的存储在A而不是C中,而且尽量避免将A、C合并成一个表(可能刚开始是设计成一个表),而且A、C间尽量解耦;
  • 字段不可再分:
    • 一个字段中不要出现分隔符,或者在一个字段中存储多个信息;
    • 例如,first name和last name不要放在同一个字段中,稳定版本号和临时版本号不要放在同一个字段中;
  • 考虑性能:
    • 上述原则可能造成多表连接查询的情况出现,降低性能;
    • 如果性能成为主要矛盾,则上述原则也不绝对;

数据库命名原则

  • 数据库的命名会直接影响到上层应用的名称,所以要和业务部门仔细讨论、慎重确定;
  • 每个属性名在数据库中只有唯一的含义,number这个属性名可能表示电话号码或是房间号,这是一种容易引起歧义的命名;
  • 数据库的名词要一致,不能在这个地方叫一个名字,到另外一个表又叫另外一个名字;
  • 一般来说,Table命名用单数,Column命名用单数;
  • Table不用Prefix前缀来表示不同的组,而用schema来划分命名空间(postgresql中);
  • 命名用snake_case,不要有其他特殊字符;
  • 名称中不要有sql关键字;
  • 如果确实需要使用sql关键字,可用双引号包围,比如CREATE TABLE "order"(...);
  • 主键的名字永远都是同一的,就是id,外键名称才需要加table的名字,诸如xxx_id、yyy_id;
  • 命名不要用缩写,比如date缩写成dt;
  • 用 create_date/update_date/sample_date这些含义更明确的名称代替date这个命名,这样不仅表达更准确,而且避免了用关键字的麻烦;
  • timestamp类型的字段要有timezone(时区),字段名用xxx_date的形式,仅表示年月日用xxx_day,仅表示时分秒用xxx_time;
  • 表示数量、次数等概念的字段名称最好写为xxx_count,不要写为xxx_number/xxx_num/xxx_no等;
  • boolean类型的命名要用is_xxx格式;

数据库设计其他注意事项

  • 每个表都要有主键,名称是id,类型为bigint;
  • 主键的类型是设为integer还是long,取决于这个系统用多长时间,如果要用100年,主键还是设置为long类型较好,这样用的很久以后id也不会超出范围;
  • 一个字段不要有多个用途,空间不是问题,清晰才是重点;
  • 不要过早优化,先把东西做出来再说,遇到性能问题再去优化;
  • 对于varchar类型的字段,当字符串并不是非常明确到底限制是多少的时候,通常选择255这个长度,varchar(50)并不比varchar(255)节省空间,varchar(50)仅仅是表示最多分配50个字符而已;
  • varchar(100)类型在PostgreSQL中代表100个字符,而在Oracle中代表100字节,具体的占用空间数目和语言、编码方式有关;
  • 对外键要加Index;
  • 数据库里面的密码一定要加密,不能保存明文;
  • 用is_deleted=true来表示本条记录的业务上的删除,不要在数据库中真正删除记录,或者仅仅是版本化修改,这样能防止数据丢失;

数据库性能提升方案

  • 使用索引会大大提升查询效率,同时降低在被索引的表上INSERT和DELETE效率;
  • 分离频繁和不频繁使用的数据到多个表中;
    • 例如,原先,一个表中保存用户名、密码、年龄、个人简介、学校等信息,但是发现访问用户名、密码、年龄的频率远高于其他字段,此时就应当将这个表分为两个表,分别存储频繁访问项和非频繁访问项;

数据库安全策略

  • 至少保存3个月的系统访问日志;
  • 数据库中的表可以有创建和更新时间戳,及所创建/修改行的用户标示;
  • 不删除字段,而是打上一个被删除的标记;
  • 版本化修改;

大型数据库设计

  • 负载均衡;
  • 读写分离;
  • 分布式存储;
  • 参考这篇文章;

数据库查询优化方案小结

发表于 2016-05-02 | 分类于 Database |

引言

  • 本文小结了数据查询的优化方案;

查询耗时点

  • 解析SQL语句时间;
  • 磁盘存取(查询所用CPU时间);
  • 磁盘IO;
  • 并行/分布式数据库的网络通信时间;
  • 其中,磁盘存取一般认为是耗时最多的点;

数据库层面的优化

  • 优化器设计(可将用户输入语句转换为等价的效率更高的执行语句);
  • 优化索引设计;
  • 优化查询算法:
    • 在等价的查询语句中,选择读磁盘最少的那个;
    • 对于简单的查询语句,可通过线性扫描和搜索引擎处理;
    • 对于复杂的查询,将它转换为简单查询的并和交;
    • 用外部归并排序算法对大于内存的关系进行排序;

用户层面的优化

  • 避免出现SELECT * FROM table 语句,要明确查出的字段;
  • 在一个SQL语句中,如果一个where条件过滤的数据库记录越多,定位越准确,则该where条件越应该前移;
  • 使用内层限定原则,在拼写SQL语句时,将查询条件分解、分类,并尽量在SQL语句的最里层进行限定,以减少数据的处理量;
  • 在判断有无符合条件的记录时建议不要用SELECT COUNT (*)和select top 1 语句;
  • 小心使用 IN 和 OR,需要注意In集合中的数据量,建议集合中的数据不超过200个;
  • 应绝对避免在order by子句中使用表达式;
  • <> 用 < 、 > 代替,>用>=代替,<用<=代替,这样可以有效的利用索引;
  • 如果需要从关联表读数据,关联的表一般不要超过7个;
  • 在一个事务中,对同一个表的多个insert语句应该集中在一起执行;

数据库事务

发表于 2016-05-01 | 分类于 Database |

引言

  • 事务是数据库的重要概念;
  • 事务处理是数据库的核心机制之一;
  • 本文总结数据库事务的相关概念;
  • 事务的概念可参考这篇文章

事务的性质

  • 事务是数据库并发控制的基本单元;
  • 事务是一个操作序列,不可分割,要么都执行,要么都不执行;
  • 事务开始前和结束后,数据库中的数据应当保持一致性;
  • 对数据库修改的多个事务彼此隔离,每个事务不应当影响或依赖其他事务;

数据库事务的四个特性及含义

性质 含义
原子性(atomicity) 一个事务(transaction)中的所有操作,要么全部完成,要么全部不完成,不会结束在中间某个环节;
事务在执行过程中发生错误,会被回滚(Rollback)到事务开始前的状态,就像这个事务从来没有执行过一样;
可以维护一个日志(或快照)记录事务执行前数据库的状态,如果事务执行失败,则回滚到事务执行前的状态;
转账时,不可能发生转出账户扣款了,转入账户未到账的情况;
一致性(consistency) 在事务开始之前和事务结束以后,数据库的完整性没有被破坏;
转账时,账户总金额不能多也不能少,保持一致性;
隔离性(isolation) 数据库允许多个并发事务同时对齐数据进行读写和修改的能力,隔离性可以防止多个事务并发执行时由于交叉执行而导致数据的不一致;
事务隔离分为不同级别,包括读未提交(Read uncommitted)、读提交(read committed)、可重复读(repeatable read)和串行化(Serializable),下面详细讲了;
持久性(durability) 事务处理结束后,对数据的修改就是永久的,即便系统出现故障,即真正持久化到数据库中;

事务执行进度

  • begin:开始执行事务;
  • aborted:事务执行一半终止了;
  • roll back:事务已经回滚;
  • commited:事务已提交,即执行成功,已经持久化到数据库中;

事务并发产生的问题

  • 脏读:
    • 一个事务读取了另一个事务操作但未提交的数据;
    • 事务T1更新了数据还未提交,这时事务T2来读取相同的数据,则T2读到的数据其实是错误的数据,即脏数据,如果T1回滚的话,T2读到的数据是不可靠的;
    • 基于脏数据所作的操作是不可能正确的;
  • 丢失更新:
    • 事务T1读取了数据,并执行了一些操作,然后更新数据,事务T2也做相同的事,则T1和T2更新数据时可能会覆盖对方的更新,从而引起错误;
    • 用排他锁解决;
  • 不可重复读:
    • 一个事务中的多个相同的查询返回了不同数据;
    • 一个事务的两次读取中,读取相同的资源得到不同的值,当事务T2在事务T1的两次读取之间更新数据,则会发生此种错误(重点在修改);
    • 用共享锁解决;
  • 幻读:
    • 事务并发执行时,其中一个事务对另一个事务中操作的结果集的影响;
    • 事务T1对一定范围内执行操作,T2对相同的范围内执行不兼容的操作,这时会发生幻读;
    • T1删除符合条件C1的所有数据,T2又插入了一些符合条件C1的数据,则在T1中再次查找符合条件C1的数据还是可以查到,这对T1来说好像是幻觉一样,这时的读取操作称为幻读(重点在新增或删除);
    • 用排它锁解决;

事务隔离级别

  • 下面是数据库事务从高到低的四个隔离级别;
级别名称 含义
可串行化(Serializable) 提供严格的事务隔离;
它要求事务序列化执行,事务只能一个接着一个地执行,不能并发执行;
可重复读(Repeatable Read) 事务T1读取一个数据块,另外,事务T2在事务T1未commit的情况下,试图写同样的数据块,此时,T2的写操作会被阻塞,直到T1commit后,写操作才被commit进入数据库;
已提交读(Read committed) 事务T1读取一个数据块,另外,事务T2在事务T1未commit的情况下,成功对同样的数据块进行写操作并commit,事务T1再读这个数据块时,结果和前一次不同;
即允许事务T1在两次读操作期间,其他事务对相同的数据块进行写操作;
未提交读(Read uncommitted) 事务T1在事务T2对于一个数据块的写操作未commit的情况下,可以读取T2修改后的结果;
  • 数据库事务隔离级别分别存在的并发问题
隔离级别 是否存在脏读 是否存在不可重复读 是否存在幻读
Read uncommitted 存在 存在 存在
Read committed 不存在 存在 存在
Repeatable Read 不存在 不存在 存在
Serializable 不存在 不存在 不存在
  • 以上所有的隔离级别都不允许脏写(一个数据项已经被另外一个尚未提交或中止的事务写入,则不允许对该数据项执行写操作);
  • 数据库隔离界别越高,并发性能越低;

事务并发的处理方法

  • 共享锁(S锁):
    • 获准共享锁的事务职能读取数据,不能修改数据;
    • 如果事务T对数据A加上共享锁后,则其他事务只能对A再加共享锁,不能加排他锁;
  • 排他锁(X锁):
    • 获准排他锁的事务既能读取数据,也能修改数据;
    • 如果事务T对数据A加上排他锁后,则其他事务不能在对A加任何类型的封锁;

死锁及其解决方法

  • 死锁实例1:
    • 现有事务A和B;
    • A在b上拥有排它锁,B正在申请b上的共享锁,故B需要等待A释放b上的排它锁;
    • B在a上拥有共享锁,A正在申请a上的排它锁,故A需要等待B释放a上的共享锁;
    • A和B相互等待,形成死锁;
  • 死锁实例2:
    • 将数据库设置为可重复读的隔离级别;
    • 事务A等待事务B执行完毕,以便commit对对象a的更改(B已经读取了a);
    • 事务B等待事务A执行完毕,以便commit对对象b的更改(A已经读取了b);
  • 死锁的解决:
    • 必须有一个事务主动回滚;
    • 回滚的事务自动释放了锁;
    • 另一个未回滚的事务就可以继续向下执行;

Angularjs 指令详解

发表于 2016-04-09 | 分类于 JavaScript |

引言

  • angularjs有许多内置指令,这些指令主要用于操纵DOM、指定路由、绑定事件处理器、执行数据绑定等等;
  • angularjs不仅有丰富的内置指令,而且可以自定义指令,实现自己需要的功能;
  • angularjs指令系统是angularjs最重要的一个知识点,在本文中加以总结,如有不完善之处请您指出;
  • 非常感谢慕课网的大漠穷秋老师,本文的很多代码复制自大漠穷秋老师在慕课网angularjs课程的示例代码;

内置指令

  • angularjs以ng开头的指令均为内置指令,详见官方文档、内置指令总结;
  • 所有内置指令如下:

in-build-directive

编写指令

  • 最简单的指令模板如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<!DOCTYPE html>
<html lang="en" ng-app="testApp">
<head>
<meta charset="UTF-8">
<title></title>

<link rel="stylesheet" type="text/css" href="lib/bootstrap-3.3.1/css/bootstrap.min.css">
</head>
<body>
<div ng-controller="testCtrl">
<hello></hello>
<div hello></div>
<div class="hello"></div>
<!-- directive:hello -->
</div>

<script src="lib/bootstrap-3.3.1/js/bootstrap.min.js"></script>
<script src="lib/angular-1.3.3/angular.min.js"></script>
<script src="test.js"></script>
</body>
</html>
1
2
3
4
5
6
7
8
9
10
//test.js
var mod = angular.module('testApp', []);

mod.directive('hello',function(){
return{
restrict:'AEMC',
template:'<div>this is a directive test!</div>',
replace:true
}
});
  • 自定义指令参数

directive-params

  • 对自定义指令声明参数的说明:
    • restrict:替换自定义元素类型,E(元素)、C(类)、A(属性)、M(注释);
    • template:替换的模板,如template: <div>this is a directive test.</div>;
    • templateURL: 替换的模板的URL,如templateUrl: 'hello.html';
    • replace:true,将自定义标签的内容全部替换掉;
    • transclude:true,自定义标签的内容不会被替换,而是被放置在template中的<div ng-transclude></div>中;
    • compile: function(){...},自定义compile函数,一般放置暴露给外部看的(比如其他directive)一些公共函数;
    • link: function(){...},自定义link函数,一般放置本directive中操纵DOM、添加事件监听器等的代码;
    • 作用域问题:在上述代码中,四个被指令替换的DOM标签其实所在的是同一个作用域,这样做不太安全,如果需要将作用域分开,则在指令中添加scope:{}即可,下面会详细讲解;

独立scope

  • 实现独立scope的方法是在指令中添加scope:{}即可;
  • 代码如下,改动任意一个input标签的内容,其他的directive不受影响,即directive作用域独立:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<!doctype html>
<html ng-app="MyModule">
<head>
<meta charset="utf-8">
<link rel="stylesheet" href="css/bootstrap-3.0.0/css/bootstrap.css">
</head>
<body>
<hello></hello>
<hello></hello>
<hello></hello>
<hello></hello>
</body>
<script src="framework/angular-1.3.0.14/angular.js"></script>
<script src="IsolateScope.js"></script>
</html>
1
2
3
4
5
6
7
8
9
10
//IsolateScope.js
var myModule = angular.module("MyModule", []);
myModule.directive("hello", function() {
return {
restrict: 'AE',
scope:{},
template: '<div><input type="text" ng-model="userName"/>{{userName}}</div>',
replace: true
}
});

指令和controller的交互

  • 在link函数中完成简单的directive和controller的交互,下面程序实现了在link中添加事件监听器并调用controller的特定方法:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title></title>
<link rel="stylesheet" href="css/bootstrap.min.css">
<link rel="stylesheet" href="css/ng-table.css">
<style>
</style>

</head>
<body ng-app="myApp">

<div ng-controller="myCtrl">
<hello>滑动加载</hello>
</div>

<script src="js/jquery-2.1.4.js"></script>
<script src="js/angular.min.js"></script>
<script src="js/ng-table.js"></script>
<script src="app.js"></script>

<script>

</script>

</body>
</html>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//app.js
var mod = angular.module('myApp', []);

mod.controller("myCtrl",function($scope){
$scope.loadData = function(){
console.log("正在加载数据");
}
});

mod.directive('hello',function(){
return{
restrict:'AEMC',
link: function(scope, element, attr){
element.bind("mouseenter",function(){
scope.loadData();
});
}
}
});
  • 一个指令也可同时调用多个controller的函数:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title></title>
<link rel="stylesheet" href="css/bootstrap.min.css">
<link rel="stylesheet" href="css/ng-table.css">
<style>
</style>

</head>
<body ng-app="myApp">

<div ng-controller="myCtrl">
<hello howtoload="loadData()">滑动加载</hello>
</div>

<div ng-controller="myCtrl2">
<hello howtoload="loadData2()">滑动加载2</hello>
</div>

<script src="js/jquery-2.1.4.js"></script>
<script src="js/angular.min.js"></script>
<script src="js/ng-table.js"></script>
<script src="app.js"></script>

<script>

</script>

</body>
</html>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
//app.js

var mod = angular.module('myApp', []);

mod.controller("myCtrl",function($scope){
$scope.loadData = function(){
console.log("正在加载数据");
}
});

mod.controller("myCtrl2",function($scope){
$scope.loadData2 = function(){
console.log("正在加载数据...2222");
}
});

mod.directive('hello',function(){
return{
restrict:'AEMC',
link: function(scope, element, attrs){
element.bind("mouseenter",function(event){
scope.$apply(attrs.howtoload); //一律小写,不用驼峰法则,没有括号()
});
}
}
});

指令间通信

  • 指令间通信的方法是:scope: {}定义独立作用域,controller暴露方法给别的指令,require引用其他指令;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
<!doctype html>
<html ng-app="MyModule">

<head>
<meta charset="utf-8">
<link rel="stylesheet" href="css/bootstrap-3.0.0/css/bootstrap.css">
<script src="framework/angular-1.3.0.14/angular.js"></script>
<script src="Directive&Directive.js"></script>
</head>

<body>
<div class="row">
<div class="col-md-3">
<superman strength>动感超人---力量</superman>
</div>
</div>
<div class="row">
<div class="col-md-3">
<superman strength speed>动感超人2---力量+敏捷</superman>
</div>
</div>
<div class="row">
<div class="col-md-3">
<superman strength speed light>动感超人3---力量+敏捷+发光</superman>
</div>
</div>
</body>

</html>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
//Directive&Directive.js

var myModule = angular.module("MyModule", []);
myModule.directive("superman", function() {
return {
scope: {},
restrict: 'AE',
controller: function($scope) {
$scope.abilities = [];
this.addStrength = function() {
$scope.abilities.push("strength");
};
this.addSpeed = function() {
$scope.abilities.push("speed");
};
this.addLight = function() {
$scope.abilities.push("light");
};
},
link: function(scope, element, attrs) {
element.addClass('btn btn-primary');
element.bind("mouseenter", function() {
console.log(scope.abilities);
});
}
}
});
myModule.directive("strength", function() {
return {
require: '^superman',
link: function(scope, element, attrs, supermanCtrl) {
supermanCtrl.addStrength();
}
}
});
myModule.directive("speed", function() {
return {
require: '^superman',
link: function(scope, element, attrs, supermanCtrl) {
supermanCtrl.addSpeed();
}
}
});
myModule.directive("light", function() {
return {
require: '^superman',
link: function(scope, element, attrs, supermanCtrl) {
supermanCtrl.addLight();
}
}
});

scope绑定

  • @绑定:将当前属性作为字符串传递,实现绑定;
    • 下面代码中,flavor:'@'实现的功能可以由注释掉的link函数代替;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
<!doctype html>
<html ng-app="MyModule">
<head>
<meta charset="utf-8">
<link rel="stylesheet" href="css/bootstrap-3.0.0/css/bootstrap.css">
</head>
<body>
<div ng-controller="MyCtrl">
<drink flavor="{{ctrlFlavor}}"></drink>
</div>
</body>
<script src="framework/angular-1.3.0.14/angular.js"></script>
<script src="ScopeAt.js"></script>
</html>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
var myModule = angular.module("MyModule", []);
myModule.controller('MyCtrl', ['$scope', function($scope){
$scope.ctrlFlavor="百威";
}])
myModule.directive("drink", function() {
return {
restrict:'AE',
scope:{
flavor:'@'
},
template:"<div>{{flavor}}</div>"
// ,
// link:function(scope,element,attrs){
// scope.flavor=attrs.flavor;
// }
}
});
  • =实现与父scope的属性双向绑定
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<!doctype html>
<html ng-app="MyModule">
<head>
<meta charset="utf-8">
<link rel="stylesheet" href="css/bootstrap-3.0.0/css/bootstrap.css">
</head>
<body>
<div ng-controller="MyCtrl">
Ctrl:
<br>
<input type="text" ng-model="ctrlFlavor">
<br>
Directive:
<br>
<drink flavor="ctrlFlavor"></drink>
</div>
</body>
<script src="framework/angular-1.3.0.14/angular.js"></script>
<script src="ScopeEqual.js"></script>
</html>
1
2
3
4
5
6
7
8
9
10
11
12
13
var myModule = angular.module("MyModule", []);
myModule.controller('MyCtrl', ['$scope', function($scope){
$scope.ctrlFlavor="百威";
}])
myModule.directive("drink", function() {
return {
restrict:'AE',
scope:{
flavor:'='
},
template:'<input type="text" ng-model="flavor"/>'
}
});
  • &:传递一个父scope的函数,稍后调用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<!doctype html>
<html ng-app="MyModule">
<head>
<meta charset="utf-8">
<link rel="stylesheet" href="css/bootstrap-3.0.0/css/bootstrap.css">
</head>
<body>
<div ng-controller="MyCtrl">
<greeting greet="sayHello(name)"></greeting>
<greeting greet="sayHello(name)"></greeting>
<greeting greet="sayHello(name)"></greeting>
</div>
</body>
<script src="framework/angular-1.3.0.14/angular.js"></script>
<script src="ScopeAnd.js"></script>
</html>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
var myModule = angular.module("MyModule", []);
myModule.controller('MyCtrl', ['$scope', function($scope){
$scope.sayHello=function(name){
alert("Hello "+name);
}
}])
myModule.directive("greeting", function() {
return {
restrict:'AE',
scope:{
greet:'&'
},
template:'<input type="text" ng-model="userName" /><br/>'+
'<button class="btn btn-default" ng-click="greet({name:userName})">Greeting</button><br/>'
}
});

angularjs 初始化过程

  • 理解angularjs的初始化过程有助于我们进一步理解link和compile函数;
  • 初始化过程如下:
    • ng-app寻找angular作用边界,在ng-app作用域范围内创建$rootScope,以后各层级的$scope均在$rootScope基础上创建,实现树形作用域结构;
    • 解析module中的各种组件名称对应的回调函数的注册表,回调函数不被执行,注册表中的组件均可被用于依赖注入;
    • controller立即执行;
    • Compile:根据从服务器获取的或者dierective中的HTML模板编译HTML,对指定的模板进行转换,生成Linking函数,此时DOM是静态DOM;
    • Link:将Compile结果与Scope结合,产生动态视图,包括在元素上注册时间监听,即动态DOM;
    • 对于同一个指定的多个实例(如ng-repeat),compile只会执行一次,而link对于指令的每个实例都会执行一次;

操作系统重点概念汇总

发表于 2016-04-07 | 分类于 computer |

引言

  • 最近利用下班的空闲时间看了看《现代操作系统》,感觉讲得非常好,虽然某些细节仍不理解,但是受益匪浅,让我对计算机有了全新的认识;
  • 这篇博客总结了操作系统的基本概念,如果以后有用到其中的知识,我准备再深入研究细节;

为什么要有操作系统

  • 空间上,为上层应用程序提供一个清晰的、一致的、友好的硬件抽象,由于硬件种类繁多,软件编写者不可能就各种不同的硬件接口编写不同版本的程序,所以屏蔽硬件的不一致性的任务就交给操作系统;
  • 时间上,保证各个程序有序运行,并充分利用硬件资源,比如时分复用、线程调度等都是具体实现;

有关CPU的概念

  • CPU的两种模式为内核态和用户态,为了安全,在内核态时CPU可使用全部硬件,用户态时不行;
  • 每种CPU都有自己的指令集,用于执行取指、解码、执行的各个过程;
  • CPU有自己的寄存器,用于存储计算暂时结果,它比内存快很多;
  • 时间多路复用/时分复用/多线程技术/超线程技术:CPU切换执行不同的线程,线程执行进度保存在程序计数器中(本质是一个寄存器),当切换到其他线程前,需要保存所有寄存器的状态,以便下次执行这个线程时重新装入这些寄存器的值;
  • 流水线:CPU的取指、解码和执行单元相互分离,执行指令n时,可以对n+1条指令解码,并读取指令n+2;
  • 超标量技术:CPU的取指、解码和执行这些指令级的运算的并行运算技术,实现超标量的物理基础是CPU的算术逻辑单元、位移单元、乘法器相互分离,可以并行执行多条指令;
  • 多核技术: 一个CPU上装有多个小芯片,每个芯片能够独立完成取指、解码和执行等过程;

进程

  • 进程是正在执行的一个程序;
  • 每个进程有其单独的地址空间,存有:可执行程序、程序的数据、程序的堆栈,如果进程申请的地址空间大于内存,则使用虚拟内存技术来解决;
  • 每个进程有其单独的资源集,包含:程序计数器、堆栈指针、寄存器等;
  • 运行过程:当一个CPU核心切换到某个进程时,为这个进程分配内存资源和CPU核执行时间资源(时间片),当时间片用完后,当前运行的指针被保存下来,进程被暂时挂起,CPU核切换到另一个进程执行;
  • 守护进程:一种在后台执行的进程(电子邮件、web页面、新闻);
  • 句柄:父进程和子进程之间的通信;
  • 进程状态:就绪、运行、阻塞,在这三种状态间切换叫做进程调度;
  • 进程调度的例子:
    • 某一进程不能继续运行(如进程的输入尚未准备好)–阻塞;
    • 某一进程运行时间太长,CPU需要切换到其他进程–就绪;
    • 某一进程开始执行–运行;

线程

  • 线程是比进程更小的一个概念,一个进程可以包含多个线程;
  • 所线程共享其所在进程的地址空间、内存资源、全局变量、打开文件,但不能同时读取同一块内存(线程锁控制);
  • 线程自己拥有的只是一点在运行中必不可少的资源(如程序计数器,一组寄存器和栈);
  • 进程和线程的区别:
    • 进程有独立的地址空间,一个进程崩溃后,在保护模式下不会对其它进程产生影响,而线程只是一个进程中的不同执行路径。线程有自己的堆栈和局部变量,但线程之间没有单独的地址空间,一个线程死掉就等于整个进程死掉,所以多进程的程序要比多线程的程序健壮,但在进程切换时,耗费资源较大,效率要差一些。
    • 具体请参考这篇文章;

进程间通信

  • 为什么进程间要有通信机制?
    • 考察命令 cat abc.txt | grep "sss",上述命令中,cat启动一个进程,这个进程的结果要传给grep启动的这个进程,这就需要进程间通信;
    • 两个打印进程如果没有通信(CPU轮训执行这两个进程),同时向打印机的打印队列中放入待打印的文件名,并将打印队列的指针移动一位,就可能出现错误,比如A进程覆盖B进程的待打印文件;
  • 进程间的竞争问题的解决
    • 总的思想:阻止多个进程同时读写共享的数据;
    • 临界区:进程的共享内存或共享文件成为临界区,不得使多个进程同时进入其临界区;
  • 临界区的具体实现方法:
    • 当一个进程进入临界区后,CPU屏蔽中断,此时,CPU无法切换到其他进程,直到当前进程退出临界区;
    • 当一个进程进入临界区后,将锁变量设置为1,表示临界区内有进程,其他进程直到锁变量变为0后才能进入临界区;
  • 上述方法的缺点:
    • 如果当前临界区内有进程,则另一个想要进入临界区的进程就不得不等待,直到临界区内无进程,这非常耗时;
    • 如果有一个高优先级H进程、低优先级L进程,L在临界区中,H在临界区外,H等待L退出临界区,但是由于L优先级比H低,L不会被调度、不会出临界区,则H一直在等待;
  • 信号量和互斥量:
    • 信号量和互斥量是两个非常重要的进程间通信的机制,两者不同,互斥量的本质是二元信号量;
    • 信号量的例子:维护一个打印队列,最长队列为N,最短为0;生产者可以将打印文件名放入队列,消费者可从打印队列中取出文件名;生产者进程维护一个信号量记录队列中占用的槽总数,初始信号量为0,每放入一个信号量加一,信号量为N时生产者进程阻塞;消费者进程维护一个信号量记录队列中空的打印槽,初始信号量为N,每取出一个信号量减一,信号量为0时生产者进程阻塞;另外,为了保证生产者进程、消费者进程不同时修改打印队列(临界区),另外维护一个二元的信号量mutex(即互斥量),它的值只能为0或1,每当有进程进入临界区,其值为1,其他进程不能进入,进入临界区的进程退出后,mutex值变为0,其他进程可以进入临界区;
    • 两者的区别:互斥量用于线程的互斥(二元,要么锁住,要么解锁),信号量用于线程的同步(多元);从作用域来说,信号量是进程间或线程间(linux仅线程间),互斥锁是线程间;

进程调度

  • 为什要有进程调度?当CPU要选择执行哪个进程的时候,如果两个或多个进程处于就绪状态,这时就存在选择的问题,需要进程调度解决;
  • 何时启动进程调度机制?
    • 在创建一个新进程后,决定运行父进程还是子进程;
    • 在一个进程退出时,系统必须选择另外一个处于就绪状态的进程执行,否则会浪费CPU资源;
    • 当一个进程阻塞时,必须选择另一个进程运行;
    • 在一个I/O中断发生时,必须做出调度决策;
  • 进程调度的目标:
    • 公平:地位等价的进程占用CPU资源的程度应当差不多相等;
    • 效率:让CPU以及I/O资源尽可能忙碌,所有部分不要闲着,这就需要对“CPU密集型进程”“I/O密集型进程”进行合理调度;
  • 进程调度的两种策略:
    • 非抢占式:CPU挑选一个进程,让它运行直至被阻塞(阻塞在IO或者等待另一个进程)或自行释放CPU;
    • 抢占式:让一个进程运行一段时间,强行挂起该进程,让另一个进程运行;
  • 具体的进程调度策略:
    • 轮转调度:所有进程排成一个圆圈,转圈圈调度;
    • 优先级调度:动态赋予进程优先级,优先级高的先执行;
    • 多级队列调度:一个进程需要100个时间片才能完成,CPU每次执行它时只执行一个时间片的时间,如果采用轮转调度,要频繁的从磁盘取出和写入进程,但是如果该进程在第一次获得CPU时分配1个时间片、在第二次获得CPU时分配2个时间片、在第三次获得CPU时分配4个时间片、在第四次获得CPU时分配8个时间片等等,这样就能减少磁盘读写,提升CPU密集型的进程的执行效率,这就是多级队列调度;
  • 死锁:
    • 定义:如果一个进程集合中的每个进程都在等待只能由该进程集合中的其他进程才能引发的事件,那么该进程集合就是死锁的;
    • 死锁的条件和避免死锁的方法有严格的逻辑推导,这里不再赘述;
    • 哲学家就餐问题

存储管理

  • 直接使用物理地址的危害:
    • 用户程序可以寻址内存的每个字节,很容易破坏操作系统,除非有特殊的硬件保护;
    • 如果每个用户程序都直接使用内存的所有物理地址,并行的执行多个用户程序变得困难;
    • 解决方法:给每个用户进程分配单独的地址空间;
  • 地址空间:
    • 地址空间是对内存这个硬件的抽象;
    • 具体实现:用基址寄存器和界限寄存器确定进程的地址空间,这两个寄存器是CPU的重要硬件;
    • 交换技术(解决物理内存不够大的问题):在物理内存不够大时,可以将目前未在执行的进程存回磁盘,运行时再调入内存;
    • 虚拟内存(解决物理内存不够大的问题):地址空间映射的物理内存在内存条上是不连续的、甚至有些在磁盘中,但是暴露给进程看的虚拟内存地址是连续的;
  • 虚拟内存/分页:
    • 每个进程拥有自己的地址空间;
    • 这个地址空间被分为许多内部地址连续的块,即“页”,和页相关的信息存储在页表中;
    • 每一页都能够映射到物理内存中,“页”在物理地址中称作“页框”,映射关系由内存管理单元(MMU)统一管理,如果要使用一个地址的数据,虚拟地址首先被送到MMU转换为物理地址;
    • 必要时可以有多级的映射关系;
    • 缺页中断:当MMU发现虚拟内存地址映射的物理地址不在内存中时,向CPU发送缺页中断;
    • 页面置换:当发生缺页中断时,必须选择一个页面换出内存,这样才能将磁盘中需要的数据换入内存;
    • 页面置换算法的效率问题:如果一个页面即将被使用但是被换出了内存,这样就很低效,为了提升效率,有如下的页面置换算法:最近未使用页面置换算法、先进先出算法、二次机会页面置换算法、时钟页面置换算法、最近最少使用页面置换算法;
  • 空闲内存管理:
    • 位图:内存被分为若干多个内存单元(几个字-几千字节都可以)进行管理;每个内存单元要么空闲、要么占用,以“0”“1”映射到位图中;搜索位图中的连续空闲地址比较耗时;
    • 链表:维护一个链表,每个节点记录一块空闲的内存区域的起始地址、长度、指向下一个链表的指针;当两个空闲区域相邻时,将链表中的相邻两个结点合并即可;

Angularjs 源码阅读体会

发表于 2016-03-30 | 分类于 JavaScript |

说明

  • 本文是我在阅读Angularjs源码时的一些体会,写出来分享给大家,不足之处请多多指正。
  • 本文以angularjs 1.5.x版本源码为例;
  • 本文参考:文章一、系列分析文章

Angularjs在初始化的时候做了什么

尝试绑定jQuery对象,如果没有则采用内置的jqLite(30561行):

1
2
3
//try to bind to jquery now so that one can write jqLite(document).ready()
//but we will rebind on bootstrap again.
bindJQuery();

publishExternalAPI初始化angular环境,这也是angularjs实现其重要特性–扩展指令、依赖注入的重要步骤:

  • 把一些基础api挂载到angular上(2455行):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
extend(angular, {
'bootstrap': bootstrap,
'copy': copy,
'extend': extend,
'merge': merge,
'equals': equals,
'element': jqLite,
'forEach': forEach,
'injector': createInjector,
'noop': noop,
'bind': bind,
'toJson': toJson,
'fromJson': fromJson,
'identity': identity,
'isUndefined': isUndefined,
'isDefined': isDefined,
'isString': isString,
'isFunction': isFunction,
'isObject': isObject,
'isNumber': isNumber,
'isElement': isElement,
'isArray': isArray,
'version': version,
'isDate': isDate,
'lowercase': lowercase,
'uppercase': uppercase,
'callbacks': {counter: 0},
'getTestability': getTestability,
'$$minErr': minErr,
'$$csp': csp,
'reloadWithDebugInfo': reloadWithDebugInfo
});
  • 在angular上添加了module方法(2488行):
    • 在window下面创建全局的angular对象,并且返回一个高阶函数,赋值给了angular.module属性;
    • setupModuleLoader返回的内容中包含了provider、factory、service、value、constant、controller、directive等诸多对象,方便用angular.module链式调用这些对象;
1
angularModule = setupModuleLoader(window);
1
2
3
4
5
6
7
//链式调用
angular.module('demoApp', [])
.factory()
.controller()
.directive()
.config()
.run();

当dom ready时,开始执行程序的初始化

  • 执行初始化的代码(30708行):
1
2
3
jqLite(document).ready(function() {
angularInit(document, bootstrap);
});
  • angularInit函数(1591行):
    • 获取ng-app的应用根节点(含有xx-app属性的dom);
    • 获取启动模块(xx-app的值xx为 [‘ng-‘, ‘data-ng-‘, ‘ng:’, ‘x-ng-‘] 任一一种;
    • 将上述结果传给bootstrap函数:bootstrap(appElement, module ? [module] : [], config);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// The element `element` has priority over any other element
forEach(ngAttrPrefixes, function(prefix) {
var name = prefix + 'app';

if (!appElement && element.hasAttribute && element.hasAttribute(name)) {
appElement = element;
module = element.getAttribute(name);
}
});
forEach(ngAttrPrefixes, function(prefix) {
var name = prefix + 'app';
var candidate;

if (!appElement && (candidate = element.querySelector('[' + name.replace(':', '\\:') + ']'))) {
appElement = candidate;
module = candidate.getAttribute(name);
}
});
  • boostrap函数(1679行):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
function bootstrap(element, modules, config) {
// ...省略若干代码
var doBootstrap = function() {
element = jqLite(element);

// 首先判断该dom是否已经被注入(即这个dom已经被bootstrap过)
// 注意这里的injector方法是Angular为jqLite中提供的,区别于一般的jQuery api
if (element.injector()) {
var tag = (element[0] === document) ? 'document' : startingTag(element);
//Encode angle brackets to prevent input from being sanitized to empty string #8683
throw ngMinErr(
'btstrpd',
"App Already Bootstrapped with this Element '{0}'",
tag.replace(/</,'<').replace(/>/,'>'));
}

modules = modules || [];

// 添加匿名模块
modules.unshift(['$provide', function($provide) {
$provide.value('$rootElement', element);
}]);

if (config.debugInfoEnabled) {
// Pushing so that this overrides `debugInfoEnabled` setting defined in user's `modules`.
modules.push(['$compileProvider', function($compileProvider) {
$compileProvider.debugInfoEnabled(true);
}]);
}

// 添加ng模块
modules.unshift('ng');

// 到这里modules可能是: ['ng', [$provide, function($provide){...}], 'xx']
// xx: ng-app="xx"

// 创建injector对象,注册所有的内置模块
var injector = createInjector(modules, config.strictDi);

// 利用injector的依赖注入,执行回调
injector.invoke(['$rootScope', '$rootElement', '$compile', '$injector',
function bootstrapApply(scope, element, compile, injector) {
scope.$apply(function() {
// 标记该dom已经被注入
element.data('$injector', injector);
// 编译整个dom
compile(element)(scope);
});
}]
);
return injector;
};

// ...省略若干代码

if (window && !NG_DEFER_BOOTSTRAP.test(window.name)) {
return doBootstrap();
}
// ...省略若干代码
}
  • 除了系统会自动调用bootsrap函数,也可以自己手动调用:
1
2
3
4
5
angular.module('demo', [])
.controller('WelcomeController', function($scope) {
$scope.greeting = 'Welcome!';
});
angular.bootstrap(document, ['demo']);

$get的分析

  • $get见源码16163行;
  • 构造函数如下:
1
2
3
4
5
6
7
8
9
10
11
12
function Scope() {
this.$id = nextUid();
this.$$phase = this.$parent = this.$$watchers =
this.$$nextSibling = this.$$prevSibling =
this.$$childHead = this.$$childTail = null;
this.$root = this;
this.$$destroyed = false;
this.$$listeners = {};
this.$$listenerCount = {};
this.$$watchersCount = 0;
this.$$isolateBindings = null;
}
  • 含义:

    • $id, 通过nextUid方法来生成一个唯一的标识
    • $$phase, 这是一个状态标识,一般在dirty check时用到,表明现在在哪个阶段
    • $parent, 代表自己的上级scope属性
    • $$watchers, 保存scope变量当前所有的监控数据,是一个数组
    • $$nextSibling, 下一个兄弟scope属性
    • $$prevSibling, 前一个兄弟scope属性
    • $$childHead, 第一个子级scope属性
    • $$childTail, 最后一个子级scope属性
    • $$destroyed, 表示是否被销毁
    • $$asyncQueue, 代表异步操作的数组
    • $$postDigestQueue, 代表一个在dirty check之后执行的数组
    • $$listeners, 代表scope变量当前所有的监听数据,是一个数组
  • 创建子级作用域是通过$new方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
$new: function(isolate, parent) {
var child;

parent = parent || this;

if (isolate) {
child = new Scope();
child.$root = this.$root;
} else {
// Only create a child scope class if somebody asks for one,
// but cache it to allow the VM to optimize lookups.
if (!this.$$ChildScope) {
this.$$ChildScope = createChildScopeClass(this);
}
child = new this.$$ChildScope();
}
child.$parent = parent;
child.$$prevSibling = parent.$$childTail;
if (parent.$$childHead) {
parent.$$childTail.$$nextSibling = child;
parent.$$childTail = child;
} else {
parent.$$childHead = parent.$$childTail = child;
}

// When the new scope is not isolated or we inherit from `this`, and
// the parent scope is destroyed, the property `$$destroyed` is inherited
// prototypically. In all other cases, this property needs to be set
// when the parent scope is destroyed.
// The listener needs to be added after the parent is set
if (isolate || parent != this) child.$on('$destroy', destroyChildScope);

return child;
}
  • 分析:
    • isolate标识来创建独立作用域,这个在创建指令,并且scope属性定义的情况下,会触发这种情况,还有几种别的特殊情况,假如是独立作用域的话,会多一个$root属性,这个默认是指向rootscope的;
    • 如果不是独立的作用域,则会生成一个内部的构造函数,把此构造函数的prototype指向当前scope实例;
    • 通用的操作就是,设置当前作用域的$$childTail,$$childTail.$$nextSibling,$$childHead,this.$$childTail为生成的子级作用域;设置子级域的$parent为当前作用域,$$prevSibling为当前作用域最后一个子级作用域;

$watch的分析

  • $watch在16452行,具体如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
$watch: function(watchExp, listener, objectEquality, prettyPrintExpression) {
var get = $parse(watchExp);

if (get.$$watchDelegate) {
return get.$$watchDelegate(this, listener, objectEquality, get, watchExp);
}
var scope = this,
array = scope.$$watchers,
watcher = {
fn: listener,
last: initWatchVal,
get: get,
exp: prettyPrintExpression || watchExp,
eq: !!objectEquality
};

lastDirtyWatch = null;

if (!isFunction(listener)) {
watcher.fn = noop;
}

if (!array) {
array = scope.$$watchers = [];
}
// we use unshift since we use a while loop in $digest for speed.
// the while loop reads in reverse order.
array.unshift(watcher);
incrementWatchersCount(this, 1);

return function deregisterWatch() {
if (arrayRemove(array, watcher) >= 0) {
incrementWatchersCount(scope, -1);
}
lastDirtyWatch = null;
};
}
  • 对象含义:

    • fn, 代表监听函数,当监控表达式新旧不相等时会执行此函数
    • last, 保存最后一次发生变化的监控表达式的值
    • get, 保存一个监控表达式对应的函数,目的是用来获取表达式的值然后用来进行新旧对比的
    • exp, 保存一个原始的监控表达式
    • eq, 保存$watch函数的第三个参数,表示是否进行深度比较
  • 检查传递进来的监听参数是否为函数,如果是一个有效的字符串,则通过parse来解析生成一个函数,否则赋值为一个noop占位函数,最后生成一个包装函数,函数体的内容就是执行刚才生成的监听函数,默认传递当前作用域.

  • 检查监控表达式是否为字符串并且执行表达式的constant为true,代表这个字符串是一个常量,那么,系统在处理这种监听的时候,执行完一次监听函数之后就会删除这个$watch.最后往当前作用域里的$$watchers数组头中添加$watch信息,注意这里的返回值,利用JS的闭包保留了当前的watcher,然后返回一个函数,这个就是用来删除监听用的.

$digest分析

  • 见源码16812行;
  • digest方法是dirty check的核心,主要思路是先执行$$asyncQueue队列中的表达式,然后开启一个loop来的执行所有的watch里的监听函数,前提是前后两次的值是否不相等,假如ttl超过系统默认值,则dirth check结束,最后执行$$postDigestQueue队列里的表达式.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
$digest: function() {
var watch, value, last, fn, get,
watchers,
length,
dirty, ttl = TTL,
next, current, target = this,
watchLog = [],
logIdx, asyncTask;

beginPhase('$digest');
// Check for changes to browser url that happened in sync before the call to $digest
$browser.$$checkUrlChange();

if (this === $rootScope && applyAsyncId !== null) {
// If this is the root scope, and $applyAsync has scheduled a deferred $apply(), then
// cancel the scheduled $apply and flush the queue of expressions to be evaluated.
$browser.defer.cancel(applyAsyncId);
flushApplyAsync();
}

lastDirtyWatch = null;

do { // "while dirty" loop
dirty = false;
current = target;

while (asyncQueue.length) {
try {
asyncTask = asyncQueue.shift();
asyncTask.scope.$eval(asyncTask.expression, asyncTask.locals);
} catch (e) {
$exceptionHandler(e);
}
lastDirtyWatch = null;
}

traverseScopesLoop:
do { // "traverse the scopes" loop
if ((watchers = current.$$watchers)) {
// process our watches
length = watchers.length;
while (length--) {
try {
watch = watchers[length];
// Most common watches are on primitives, in which case we can short
// circuit it with === operator, only when === fails do we use .equals
if (watch) {
get = watch.get;
if ((value = get(current)) !== (last = watch.last) &&
!(watch.eq
? equals(value, last)
: (typeof value === 'number' && typeof last === 'number'
&& isNaN(value) && isNaN(last)))) {
dirty = true;
lastDirtyWatch = watch;
watch.last = watch.eq ? copy(value, null) : value;
fn = watch.fn;
fn(value, ((last === initWatchVal) ? value : last), current);
if (ttl < 5) {
logIdx = 4 - ttl;
if (!watchLog[logIdx]) watchLog[logIdx] = [];
watchLog[logIdx].push({
msg: isFunction(watch.exp) ? 'fn: ' + (watch.exp.name || watch.exp.toString()) : watch.exp,
newVal: value,
oldVal: last
});
}
} else if (watch === lastDirtyWatch) {
// If the most recently dirty watcher is now clean, short circuit since the remaining watchers
// have already been tested.
dirty = false;
break traverseScopesLoop;
}
}
} catch (e) {
$exceptionHandler(e);
}
}
}

// Insanity Warning: scope depth-first traversal
// yes, this code is a bit crazy, but it works and we have tests to prove it!
// this piece should be kept in sync with the traversal in $broadcast
if (!(next = ((current.$$watchersCount && current.$$childHead) ||
(current !== target && current.$$nextSibling)))) {
while (current !== target && !(next = current.$$nextSibling)) {
current = current.$parent;
}
}
} while ((current = next));

// `break traverseScopesLoop;` takes us to here

if ((dirty || asyncQueue.length) && !(ttl--)) {
clearPhase();
throw $rootScopeMinErr('infdig',
'{0} $digest() iterations reached. Aborting!\n' +
'Watchers fired in the last 5 iterations: {1}',
TTL, watchLog);
}

} while (dirty || asyncQueue.length);

clearPhase();

while (postDigestQueue.length) {
try {
postDigestQueue.shift()();
} catch (e) {
$exceptionHandler(e);
}
}
}
  • 核心就是两个loop,外loop保证所有的model都能检测到,内loop则是真实的检测每个watch,watch.get就是计算监控表达式的值,这个用来跟旧值进行对比,假如不相等,则执行监听函数;
  • 比较完之后,把新值传给watch.last,然后执行watch.fn也就是监听函数,传递三个参数,分别是:最新计算的值,上次计算的值(假如是第一次的话,则传递新值),最后一个参数是当前作用域实例,这里有一个设置外loop的条件值,那就是dirty = true,也就是说只要内loop执行了一次watch,则外loop还要接着执行,这是为了保证所有的model都能监测一次,虽然这个有点浪费性能,不过超过ttl设置的值后,dirty check会强制关闭,并抛出异常;
  • 当检查完一个作用域内的所有watch之后,则开始深度遍历当前作用域的子级或者父级,虽然这有些影响性能,就像这里的注释写的那样yes, this code is a bit crazy

$apply分析

  • 见源码17121行;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
$apply: function(expr) {
try {
beginPhase('$apply');
try {
return this.$eval(expr);
} finally {
clearPhase();
}
} catch (e) {
$exceptionHandler(e);
} finally {
try {
$rootScope.$digest();
} catch (e) {
$exceptionHandler(e);
throw e;
}
}
}
  • 这个方法一般用在,不在ng的上下文中执行js代码的情况,比如原生的DOM事件中执行想改变ng中某些model的值,这个时候就要使用$apply方法了;
  • 代码中,首先让当前阶段标识为$apply,这个可以防止使用$apply方法时检查是否已经在这个阶段了,然后就是执行$eval方法, 这个方法上面有讲到,最后执行$digest方法,来使ng中的M或者VM改变.

jQuery3新特性

发表于 2016-03-10 | 分类于 translation |

说明

  • 本文为翻译文章,原文在这里
  • 本文作者:Aurelio De Rosa
  • 本文总结了jQuery3新特性,作为jQuery开发者的参考

引言

  • jQuery影响网页开发已经有十年的时间,它一路走来源于它的诸多优势。 jQuery为用户提供了一个易于使用的与DOM交互方式,比如:执行Ajax请求,创建动画,等等。此外,不同于纯DOM的API,jQuery实现了复合模式。正因为如此,你在不用知道元素数量的前提下,可以方便调用一个jQuery集合或是一个jQuery方法。
  • 在未来的几个星期,一个重要的时间节点将要到来,jQuery将发布版本3,其中修复了很多的bug,增加了新的方法,弃用并删除了一些功能,改变了一些功能的行为。在这篇文章中,我将讲解jQuery3引入的最重要的变化。

新特性

  • 在下面的章节,我将讨论jQuery3中添加的最重要的特性。

FOR…OF LOOP

  • jQuery3将提供for…of 循环来遍历一个jQuery集合的DOM元素。这种新的迭代器是ECMAScript 2015(又名的ECMAScript6)规范的一部分。它可以让你方便的迭代对象(包括阵列,地图,设置,等等)。
  • 当使用这种新的迭代方式的时候,您会收到一个不是jQuery的集合,从中仅可以在同一时间访问一个DOM元素。这个迭代器将使你可以在一个jQuery集合执行操作的方式略有提升。
  • 为了理解这个迭代器是如何工作的,我们举个例子。比如你想分配一个ID给页面的每个输入元素。 jQuery的3之前你可以这样写:
1
2
3
4
var $inputs = $('input');
for(var i = 0; i < $inputs.length; i++) {
$inputs[i].id = 'input-' + i;
}
  • 在jQuery3中可以这样写:
1
2
3
4
var $inputs = $('input');var i = 0; 
for(var input of $inputs) {
input.id = 'input-' + i++;
}

$.GET() AND $.POST()的新属性

  • jQuery3增加了$.get()和$.post()的新属性,他们是ajax的具体实现。添加新的属性是:
1
2
$.get([settings])
$.post([settings])
  • setting是一个对象,可以拥有许多属性。你可以给$.ajax()提供相同的对象。要了解更多内容,请参阅$.ajax()。
  • 在给$.get()、$.post()传送对象和给$.ajax()传送对象的最大的区别是方法的属性始终被忽略掉。产生这种情况的原因是$.get()、$.post()对象有一个预设的HTTP方法来执行Ajax请求(GET方法对应$.get()、POST方法对应$.post())。最基本的一个原则是,你不能尝试通过$.get()去发送POST请求。
1
2
3
4
$.get({
url: 'https://www.audero.it',
method: 'POST' // This property is ignored
});
  • 尽管显式设置了请求的方法为POST,这段代码仍将发出GET而非POST请求。

使用REQUESTANIMATIONFRAME()播放动画

  • 所有现代浏览器,包括Internet Explorer10及以上,都支持requestAnimationFrame。在实际操作中,jQuery3播放动画时,使用这个API将使得动画的播放更加平滑,而且消耗更少的CPU资源。

UNWRAP()

  • jQuery3对于unwrap()函数增加了一个可选的选择参数。该方法现在可以这么使用:
1
unwrap([selector])
  • 由于这种变化,你可以在unwrap函数中添加选择器。如果选择器存在匹配的DOM对象,则匹配的子元素是展开;否则,不进行操作。

未来的变化

  • jQuery3还修改了它的一些功能的行为。

VISIBLE 和 :HIDDEN

  • 新版的jQuery更改了:visible 和:hidden的含义,如果元素存在盒模型(包括那些宽度和高度为零的盒模型),这样的元素将被认为是:visible的。例如,
    元素和一些没有内容的行内元素在jQuery3中就会被:visible选择器选中。所以,如果您页面上结构如下:
1
2
<div></div>
<br />
  • 而且您写了如下的代码:
1
console.log($('body :visible').length);
  • 在jQuery1和jQuery2中,您将获取0,在jQuery3中,您将获得2.

DATA()

  • 另一个重要变化是关于data()方法的。它已被更改为符合 Dataset API specifications的方法。 jQuery3将所有属性的key值变更为camel case。要理解这种变化,请考虑以下代码:
1
<div id="container"></div>
  • 如果您使用的是jQuery3之前的版本,你可以这么写:
1
2
3
4
5
6
var $elem = $('#container');

$elem.data({
'my-property': 'hello'});

console.log($elem.data());
  • 在控制台你将得到如下输出:
1
{my-property: "hello"}
  • 但是如果您使用jQuery3,将得到:
1
{myProperty: "hello"}
  • 注意,jQuery3属性的名称是camel case的,没有“-”符号,而以前的版本则有。

THE DEFERRED OBJECT

  • jQuery3改变了递延对象的行为,是Promise对象的先驱,以提高其与promise/ A+对象的兼容性。这个对象和它的历史是很有趣的。为了更多地了解它,你可以阅读官方文档或阅读我的书,涵盖jQuery3的知识点。
  • 在jQuery1.x和2.x中,回调函数中的未捕获异常被传递给一个异常中止程序的执行。不同于原生的Promise 对象,它会一层一层抛出异常,直到它达到window.onerror对象。如果你还没有为此函数定义事件(这是一种罕见的情况),异常消息会显示出来,程序流被终止。
  • jQuery3遵循和原生的Promise对象相同的模式。因此,引发的异常被转换成一个rejection 对象并且执行失败情况下的回调函数。一旦完成,该过程继续,并且随后执行成功情况下的回调函数。
  • 为了帮助您了解这种差异,让我们看一个小例子。考虑下面的代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
var deferred = $.Deferred();

deferred
.then(function() {
throw new Error('An error');
})
.then(
function() {
console.log('Success 1');
},
function() {
console.log('Failure 1');
}
)
.then(
function() {
console.log('Success 2');
},
function() {
console.log('Failure 2');
}
);

deferred.resolve();
  • 在jQuery1和jQuery2中,只有第一个函数(抛出异常的那个函数)被执行。另外,因为我们没有定义任何window.onerror的处理函数,控制台消息将输出“Uncaught Error: An error”,程序执行将中止。
  • 在jQuery3中,程序的行为是完全不同的。你会看到控制台消息显示“Failure 1”和“Success 2”。该异常由第一个失败处理函数来处理,一旦它已经被处理,接下来成功的函数可以被执行。

SVG 文件

  • 没有jQuery版本,包括jQuery3,正式支持SVG文件。然而事实是,许多方法都可以工作,比如那些操纵类名称的函数,已在jQuery3中被更新。因此,在这个即将到来的jQuery版本,你就可以使用SVG文档的方法,如addClass()和hasClass()方法。

方法和属性已过时或删除

  • 到目前我们介绍了jQuery3新增的功能,同时它也移除和弃用的几个功能。
  • 弃用 BIND(), UNBIND(), DELEGATE() UNDELEGATE()方法

    • on()方法以前曾经介绍过,是用来为提供一个统一的接口来代替jQuery的bind()、delegate()和 live()方法。同时,jQuery建议用off()提供的统一的接口来代替unbind(),undelegated()和die()方法。
    • jQuery3弃用了所有的这些方法,将来的版本也不会重新使用。请在你的项目中专注于使用on() 和off()。
  • 弃用LOAD(), UNLOAD() 和 ERROR()方法

    • jQuery3弃用了LOAD(), UNLOAD() 和 ERROR()方法。这些方法其实早在jQuery1.8中就被弃用了,但并未被绝对禁止使用。如果你正在使用依赖于一个或一个以上的这些方法,升级到jQuery3则会破坏你的代码的可用性。因此,在决定升级前要注意。
  • 弃用CONTEXT, SUPPORT 和 SELECTOR属性

    • jQuery3 弃用了context, support和 selector属性。正如我在上一节中提到的,如果你还在项目中使用它们,升级到jQuery3会破坏你的代码的可用性。
  • Bug的修正

    • jQuery3修复了以前版本中的一些重要的错误。在下面的部分,我将重点讲解两方面,这将使您的工作方式的巨大变化。
  • 对于 width() 和height()函数不会有更多的四舍五入

    • jQuery3修复了包括width()和height()等相关函数的bug。这些方法将不再舍入到最近的像素,这使得它在某些情况下难以定位。
    • 要理解这个问题,举例来说,你有一个有100像素宽的容器元素,分成三等份,每一份为宽度为整体的三分之一(即33.333333%),在jQuery历史版本中,如果你要按照下面的代码获取每一份的宽度,你将会得到四舍五入后的宽度33像素。jQuery3中修复了这个bug,你将会得到一个浮点数。
1
2
3
4
5
div class="container">
<div>My name</div>
<div>is</div>
<div>Aurelio De Rosa</div>
</div>
1
$('.container div').width();
  • WRAPALL()函数
    • jQuery的新版本修复了一个wrapAll()方法的错误。在jQuery3之前的版本中,当向wrapAll()传递函数时,它独立包装jQuery的集合中的元素。换句话说,它的行为和向wrap()传递函数作用相同。
    • 为了解决这个问题,因为函数被在jQuery3中仅仅被调用了1次,它没有通过jQuery的集合传递元素的索引。最后,函数上下文(this对象)将引用jQuery集合中的第一个元素。

下载jQuery3测试版本1

  • 可通过如下URL下载

    • 非压缩版本: https://code.jquery.com/jquery-3.0.0-beta1.js
    • 压缩版本: https://code.jquery.com/jquery-3.0.0-beta1.min.js
  • 也可用npm下载:

1
npm install jquery@3.0.0-beta1

结论

  • 很多人说,jQuery已死,现代Web开发不会用到jQuery了。然而,它的发展仍在继续,但其78.5%的使用率的统计结果违背了这些说法。
  • 在这篇文章中,我已经带您领略了jQuery3的新特性。正如您可能已经注意到,这个版本不会打破任何现有的项目,因为它不会引入许多重大更改。尽管如此,有一些更改在升级前需要牢记。第三方的依赖升级前,该项目的审查会帮助你发现任何异常行为或损坏的功能。

再次声明

  • 本文是这篇文章的翻译,作者是Aurelio De Rosa,译者是饮水思源。
  • 非常感谢作者分享jQuery3新特性,让我们对jQuery有了全新的认识和体验。
1234…11
Eric Chang

Eric Chang

www.yinshuisiyuan.net

101 日志
15 分类
51 标签
github weibo linkedin mail
  • 阮一峰
  • Azure
  • Python
  • Nodejs
© 2015 - 2020 Eric Chang
由 Hexo 强力驱动
主题 - NexT.Pisces


知识共享许可协议本作品采用知识共享署名-非商业性使用 4.0 国际许可协议进行许可。