api-string

String类

1、创建对象

String s = "abc";//常量池中创建一个对象

String s1 = new String("abc");//堆内存中创建两个对象,一个string类型的对象,一个“abc”对象

System.out.println(s == s1);//false,比较地址

System.out.println(s.equals(s1));//true,object中的equals方法比较的是地址,但object类中equals方法比较的是内容。

2、构造函数

通过数组创建字符串

byte[] arr = {64, 66, 67, 68};
String s = new String(arr);
System.out.println(s);//ABCD

截取数组中的一部分,创建字符串

char[] arr = {'a', 'b', 'c', 'd'};
String s = new String(arr, 1, 3);
System.out.println(s);//bcd

3、获取

1、length() 长度

2、charAt(int index) 根据索引获取字符

3、根据字符获取索引

indexOf(int ch) 

indexOf(int ch, int fromIndex)

indexOf(String str)

indexOf(String str, fromIndex)

lastIndexOf(int ch)

lastIndexOf(int ch, int fromIndex)

lastIndexOf(String str)

lastIndexOf(String str, int fromIndex)

4、获取字符串中的一部分字符串

subString(int beginIndex, int endIndex)//包括开始,不包括结束

subString(int beginIndex)

5、转换

(1)、String[] split 切割,将字符串转为字符串数组

split(String regex)//切割,正则,将字符串转为数组

String str = "张三.李四.王五";

String[] arr = s.split("\\.");

(2)、char[] toCharArray 切割,将字符串转为字符数组

(3)、byte[] getBytes 将字符串变成字节数组

(4)、字符串中的字母大小写转换

String toUpperCase(): 大写
String toLowerCase(): 小写

(5)、将字符串中的内容进行替换

String replace(char oldch, char newch)

String replace(String s1, String s2)

(6)、去掉字符串两端空格

String trim()

(7)、将字符串进行连接

String concat(String);

(8)、将基本数据类型转换为字符串

String valueOf(int short byte long double float)

6、判断

(1)、两个字符串内容是否相同

boolean equals(Object obj)

boolean equalsIgnoreCase(String str);//忽略大小写

(2)、字符串中是否包含指定字符串

boolean contains(String str)

(3)、字符串是否以指定字符串开头/结尾

boolean startsWith(String str);

boolean endsWith(String str);

7、比较

int compareTo(String anotherString)

比较两个字符串中各个字符的Unicode值。

此字符串按字典顺序小于字符串参数,返回小于0的值,大于的话,返回一个大于0的值,相等的话,返回0。

8、练习

(1)、字符串数组排序

public static void main(String[] args){
    String[] arr = {"nba","abc","hjk","efs","wyn",};
    printArray(arr);
    sortString(arr);
    printArray(arr);
}

public static void printArray(String[] arr){
    System.out.print("[");
    for(int i=0; i<arr.length; i++){
        System.out.print(arr[i]+", ");
    }
    System.out.print("]");
}

public static void sortString(String[] arr){
    for(int i=0; i< arr.length-1; i++){
        for(int j=i+1; j< arr.length; j++){
            if(arr[i].compareTo(arr[j])>0)//字符串比较 用compareTo方法
                swap(arr,i,j);
        }
    }
}

public static void swap(String[] arr, int i, int j){
    String temp = arr[i];
    arr[i] = arr[j];
    arr[j] = temp;    
}

(2)、一个子串在整串中出现的次数

方法一:

通过indexOf获取第一次出现的位置。

记录出现的位置,通过subString在剩余的字符串中继续查找子串。

循环查找,直到indexOf返回-1。

public static int getKeyStringCount(String str, String key){
    int count = 0;
    int index = 0;
    while((index = str.indexOf(key)) > 0){
        str = str.subString(index+key.length());
        count++;
    }
    return count;
}

缺点:每次取子串,字符串常量池中都会增加字符串。

方法二:

记录每一次出现的位置,下一次查找,通过indexOf(String str, fromindex)方法,从该位置继续往下查找,直到返回-1。

public static int getKeyStringCount(String str, String key){
    int count = 0;
    int index = 0;
    while((index = str.indexOf(key,index)) > 0){
        index = index + key.length();
        count++;
    }
    return count;
}

(3)、两个字符串中最大相同的子串

public static void main(String[] args){
    String s1 = "jskkskabcdlslslsl";
    String s2 = "xjxabcdjas";
    String s = getMaxSubstring(s1, s2);
    System.out.println("s="+s);
}

public static String getMaxSubstring(String s1, String s2){
    String max = null, min = null;
    max = (s1.length()>s2.length())?s1:s2;
    min = max.equals(s1)?s2:s1;
    for(int i=0; i<min.length(); i++){
        for(int a=0, b=s2.length()-i; b!=s2.length()+1, a++, b++){
            String sub = s2.subString(a, b);
            if(s1.contains(sub))
                return sub;
        }
    }
    return null;
}

thread-communication

一、多线程通信

多个线程在处理同一资源,但是任务不同。

1、示例

input类:输入

resource类: name、sex

output类:输出

input和output作为两个线程,处理同一个资源resource。

class Resource{
    String name;
    String sex;
}

class Input implements Runnable{
    Resource r;
    Input(Resource r){
        this.r = r;
    }
    public void run(){
        int x = 0;
        while(true){
            synchronized(r){//同步锁r与output保持一致
                if(x == 0){
                    r.name = "张三";
                    r.sex = "男";
                }else{
                    r.name = "李四";
                    r.sex = "女";
                }
            }
            x = (x+1)%2;
        }
    }
}

class Output implements Runnable{
    Resource r;
    Input(Resource r){
        this.r = r;
    }
    public void run(){
        while(true){                    
            synchronized(r){
                System.out.println(r.name+"...."+r.sex);
            }
        }
    }
}

class ResourceDemo{
    public static void main(String[] args){
        Resource r = new Resource();
        Input in = new Input(r);
        Output out = new Output(r);
        Thread t1 = new Thread(in);
        Thread t2 = new Thread(out);
        t1.start();
        t2.start();
    }
}

线程分析:

input拿到执行权后,输入张三男,output拿到执行权,输出张三、男,input再拿到执行输入李四女,还是input拿到执行权,输入张三男,覆盖了李四女,output拿到执行权还是输出张三男。因此,无法实现input输入一次,output就输出一次的需求。

2、多线程通信-等待唤醒机制

实现input输入一次,output就紧接着输出一次。

等待唤醒机制涉及的方法:

1、wait():让线程处于冻结状态,被wait的线程会被存储到线程池中。

2、notify():唤醒线程池中一个线程(任意)。

3、notifyAll():唤醒线程池中的所有线程。

4、以上方法都必须定义在同步中。因为这些方法都是用于操作线程状态的方法。必须要明确到底操作的是哪个锁上的线程。

5、r锁即为线程input和output的监视器。

6、为什么操作线程的方法wait、notify、notifyAll定义在了Object类中,而没有定义在Thread类中。因为这些方法是监视器的方法。监视器就是锁。锁可以是任意对象,任意对象都继承自Object类。

class Resource{
    String name;
    String sex;
    boolean flag = false;
}

class Input implements Runnable{
    Resource r;
    Input(Resource r){
        this.r = r;
    }
    public void run(){
        int x = 0;
        while(true){
            synchronized(r){//同步锁r与output保持一致
                if(r.flag)
                    try{
                        r.wait()
                    }catch(InterruptedException e){
                        //...
                    }
                if(x == 0){
                    r.name = "张三";
                    r.sex = "男";
                }else{
                    r.name = "李四";
                    r.sex = "女";
                }
                r.flag = true;
                r.notify();
            }
            x = (x+1)%2;
        }
    }
}

class Output implements Runnable{
    Resource r;
    Input(Resource r){
        this.r = r;
    }
    public void run(){
        while(true){                    
            synchronized(r){
                if(!r.flag)
                    try{
                        r.wait()
                    }catch(InterruptedException e){
                        //...
                    }
                    System.out.println(r.name+"...."+r.sex);
                    r.flag = false;
                    r.notify();
            }
        }
    }
}

class ResourceDemo{
    public static void main(String[] args){
        Resource r = new Resource();
        Input in = new Input(r);
        Output out = new Output(r);
        Thread t1 = new Thread(in);
        Thread t2 = new Thread(out);
        t1.start();
        t2.start();
    }
}

线程分析:input和output分别取得执行权,并相互唤醒对方。

代码优化:不要直接访问Resource中的变量,提高安全性。

class Resource{
    private String name;
    private String sex;
    private boolean flag = false;
    public synchronized void set(String name, String sex){
        if(flag){
            try{
                    this.wait()
                }catch(InterruptedException e){
                    //...
                }
            this.name = name;
            this.sex = sex;
            flag = true;
            this.notify();//同步函数的锁是this
        }
    }
    public synchronized void out(){
        if(!flag){
            try{
                    this.wait()
                }catch(InterruptedException e){
                    //...
                }
        }
        System.out.println(name+"...."+sex);
        flag = false;
        this.notify();
    }
}

class Input implements Runnable{
    Resource r;
    Input(Resource r){
        this.r = r;
    }
    public void run(){
        int x = 0;
        while(true){
            if(x == 0){
                r.set("张三","男");
            }else{
                r.set("张三","女");
            }
            x = (x+1)%2;
        }
    }
}

class Output implements Runnable{
    Resource r;
    Input(Resource r){
        this.r = r;
    }
    public void run(){
        while(true){                    
            r.out();
        }
    }
}

class ResourceDemo{
    public static void main(String[] args){
        Resource r = new Resource();
        Input in = new Input(r);
        Output out = new Output(r);
        Thread t1 = new Thread(in);
        Thread t2 = new Thread(out);
        t1.start();
        t2.start();
    }
}

3、多线程通信-多生产者多消费者问题

class Resource{
    private String name;
    private int count = 1;
    private boolean flag = false;
    public synchronized void set(String name){
        if(flag)
            try{
                this.wait()
            }catch(InterruptedException e){
                //...
            }
        this.name = name + count;
        count++;
        System.out.println(Thread.currentThread().getName()+"...生产者..."+this.name);
        flag = true;
        this.notify();//同步函数的锁是this
    }
    public synchronized void out(){
        if(!flag)
            try{
                this.wait()
            }catch(InterruptedException e){
                //...
            }
        System.out.println(Thread.currentThread().getName()+"...消费者..."+this.name);
        flag = false;
        this.notify();
    }
}

class Producer implements Runnable{
    private Resource r;
    Producer(Resource r){
        this.r = r;
    }
    public void run(){
        while(true){
            r.set("烤鸭");
        }
    }
}

class Consumer implements Runnable{
    private Resource r;
    Producer(Resource r){
        this.r = r;
    }
    public void run(){
        while(true){
            r.out();
        }
    }
}

//只有一个生产者和一个消费者,安全
class ProducerConsumerDemo{
    public static void main(String[] args){
        Resource r = new Resource();
        Producer pro = new Producer();
        Consumer con = new Consumer();
        Thread t1 = new Thread(pro);
        Thread t2 = new Thread(con);
        t1.start();
        t2.start();
    }
}

//存在多个生产者和多个消费者,存在安全隐患
class ProducerConsumerDemo{
    public static void main(String[] args){
        Resource r = new Resource();
        Producer pro = new Producer();
        Consumer con = new Consumer();
        Thread t1 = new Thread(pro);
        Thread t2 = new Thread(pro);
        Thread t3 = new Thread(con);
        Thread t4 = new Thread(con);
        t1.start();
        t2.start();
        t3.start();
        t4.start();
    }
}

线程分析:只有一个生产者,一个消费者时,没有问题。但是当有多个生产者和多个消费者是会存在安全隐患,甚至死锁。

隐患分析:

t0,t1,t2,t3都有执行资格。

线程t0拿到执行权,生产烤鸭1,flag为true。

t0再次拿到执行权被冻结。

t1拿到执行权也被冻结。此时,t0和t1都进入线程池。

t2拿到执行权,消费了烤鸭1,flag为false。唤醒线程池中被冻结的线程t0或t1。假设唤醒了t0。此时,t0,t2,t3有执行资格。

t2继续拿到执行权被冻结,进入线程池(t1,t2)。

t3拿到执行权也被冻结,进入线程池(t1,t2,t3)。

被唤醒的t0拿到执行权,没有判断if(flag),直接往下执行,生产烤鸭2,flag为true。唤醒线程池中的一个线程。

如果t1被唤醒,此时t0和t1有执行资格,线程池中还有t2和t3。

t0拿到执行权,被冻结进入线程池。

被唤醒的t1拿到执行权,还是没有判断if(flag),从被冻结的位置,直接往下执行,又生产了烤鸭3。

问题出现,烤鸭2没有被消费的情况下,生产了烤鸭3。

隐患解决方法:t1拿到执行权后,要判断flag为true,不要再生产烤鸭3。

因此要将if改为while

public synchronized void set(String name){
    while(flag)
        try{
            this.wait()
        }catch(InterruptedException e){
            //...
        }
    ...
}

public synchronized void out(){
    while(!flag)
        try{
            this.wait()
        }catch(InterruptedException e){
            //...
        }
    ...
}
死锁分析:

t0和t1被冻结进入线程池。

t2拿到执行权,消费烤鸭1,flag为false,唤醒t0。

t2和t3先后拿到执行权被冻结进入线程池。

t0拿到执行权,生产烤鸭,flag变为true,唤醒线程池中的任意一个线程。此时,线程池中有t1,t2,t3。

如果唤醒的是t1,此时有执行资格的为t0和t1。先后拿到执行权,由于flag为true,都被冻结进入线程池。此时,t0,t1,t2,t3都被冻结在线程池中。

没有线程被唤醒,没有线程有执行资格,没有线程能拿到执行权,死锁。

死锁解决:

t0拿到执行权,生产烤鸭,flag变为true,唤醒线程池中的线程时,全部唤醒。

public synchronized void set(String name){
    ...
    this.notifyAll();
}

public synchronized void out(String name){
    ...
    this.notifyAll();
}

4、多线程通信-多生产者多消费者问题-JDK1.5新特性-Lock

Lock接口是1.5版本对synchronized的替代。

同步代码块和同步函数对于锁的操作是隐式的。

jdk1.5以后将同步和锁封装成了对象,并将操作锁的隐式方式定义到了该对象中,将隐式动作变成了显式动作。

Object obj = new Object();
void show(){
    synchronized(obj){
        //...
    }
}

Lock lock = new ReentrantLock();
void show(){
    try{
        lock.lock();//获取锁
        //...
    }catch(){
        //...
    }finally{
        lock.unlock();
    }
}

5、多线程通信-多生产者多消费者问题-JDK1.5新特性-Condition

Object obj = new Object();
void show(){
    synchronized(obj){
        //...
    }
}

同步代码块中的锁对象,只有一组操作线程的方法,就是继承自object的wait(),notify(),notifyAll()。

Lock lock = new ReentrantLock();
void show(){
    try{
        lock.lock();//获取锁
        //...
    }catch(){
        //...
    }finally{
        lock.unlock();
    }
}

jdk1.5以后将操作线程的方法封装为了对象即为condition接口。

Lock替代的是synchronized的方法和语句的使用,Condition替代了Object监视器(即锁)的方法(wait,notify,notifyAll)的使用。

通过Lock接口的newCondition方法,就可以使一个实现了Lock接口的锁对象生成多组监视器方法。

interface Condition{
    await();
    signal();
    signalAll();
}

Lock lock = new ReentrantLock();
Condition c1 = lock.newCondition();
Condition c2 = lock.newCondition();
优化烤鸭示例代码
import java.util.concurrent.locks.*

class Resource{
    private ...
    ...
    //创建一个锁对象
    Lock lock = new ReentrantLock();
    //通过已有的锁获取该锁上的监视器对象
    Condition con = lock.newCondition();
    public void set(String name){
        lock.lock();
        try{
            while(flag)
                //try{this.wait();}catch(...){}
                try{con.await();}catch(...){}
                ...
                //notifyAll();
                con.signalAll();
        }finally{
            lock.unlock();
        }
    }
    public void out(){
        lock.lock();
        try{
            while(!flag)
                //try{this.wait();}catch(...){}
                try{con.await();}catch(...){}
                ...
                //notifyAll();
                con.signalAll();
        }finally{
            lock.unlock();
        }
    }
}
notifyAll解决方法缺点

唤醒了所有的线程,t1还有可能优于t2和t2拿到执行权,降低了效率。

应该想办法做到:set的线程t0和t1只唤醒out的t2和t3,而out的线程t2和t3只唤醒t0和t1。

lock和condition满足条件,四个线程共用一把锁lock,但是一把锁有两个监视器,一个监视器用来监视set,另一个监视器用来监视out。

优化烤鸭示例代码
Lock lock = new ReentrantLock();
Condition producer_con = lock.newCondition();
Condition consumer_con = lock.newCondition();
public void set(String name){
    lock.lock();
    try{
        while(flag)
            //try{this.wait();}catch(...){}
            //try{con.await();}catch(...){}
            try{producer_con();}catch(...){}
            ...
            //notifyAll();
            //con.signalAll();
            //唤醒消费者的线程的一个线程即可
            consumer_con.signal();
    }finally{
        lock.unlock();
    }
}
public void out(){
    lock.lock();
    try{
        while(!flag)
            //try{this.wait();}catch(...){}
            //try{con.await();}catch(...){}
            try{consumer_con.await();}catch(...){}
            ...
            //notifyAll();
            //con.signalAll();
            producer_con.signal();
    }finally{
        lock.unlock();
    }
}
lock & condition

Lock接口:替代了同步代码块或者同步函数。将同步的隐式锁操作变成显式操作。同时更为灵活。可以给一个锁加上多组监视器。

lock():获取锁

unlock():释放锁

Condition接口:替代了Object中的wait,notify,notifyAll方法。将这些监视器的方法进行了封装,变成Condition监视器对象。可以任意锁进行组合。

await():相当于wait()方法
signal():相当于notify()方法
signalAll():相当于notifyAll()方法

6、wait和sleep的区别

1、wait可以指定时间,也可以不指定时间。sleep必须指定时间。

2、在同步中,对cpu的执行权和锁的处理不同。
wait:释放执行权,也释放锁。
sleep:释放执行权,不释放锁。

7、停止线程方式 - 定义标记

1、stop方法。已过时。

2、run方法结束

任务中都有循环结构,只要控制住循环就可以结束任务。

控制循环通常就用定义标记来完成。

class StopThread implements Runnable{
    private boolean flag = true;
    public void run(){
        while(flag){
            System.out.println(Thread.currentThread().getName()+"...");
        }
    }
    public void setFlag(){
        flag = false;
    }
}

class StopThreadDemo{
    public static void main(String[] args){
        StopThread st = new StopThread();
        Thread t1 = new Thread(st);
        Thread t2 = new Thread(st);
        t1.start();
        t2.start();

        int num = 1;
        for(;;){
            if(++num == 50){
                st.setFlag(0;
                break;
            }
            System.out.println("main..."+num);
        }
        System.out.println("over");
    }
}

如果线程处于冻结状态,无法读取标记,可以用Thread类中的interrupt方法将线程从冻结状态强制恢复到运行状态中来,让线程具备cpu的执行资格。

强制动作会发生中断异常,需要处理。

class StopThread implements Runnable{
    private boolean flag = true;
    public void run(){
        while(flag){
            try{
                wait();
            }catch(InterruptedException e){
                System.out.printlin(Thread.currentThread().getName()+"..."+e);
            }
            System.out.printlin(Thread.currentThread().getName()+"...+++");
        }
    }
    public void setFlag(){
        flag = false;
    }
}

class StopThreadDemo{
    public static void main(String[] args){
        StopThread st = new StopThread();
        Thread t1 = new Thread(st);
        Thread t2 = new Thread(st);
        t1.start();
        t2.start();

        int num = 1;
        for(;;){
            if(++num == 50){
                //st.setFlag(0;
                t1.interrupt();
                t2.interrupt();
                break;
            }
            System.out.println("main..."+num);
        }
        System.out.println("over");
    }
}

8、多线程 - 守护线程

setDaemon:后台线程,守护线程,用户线程。

运行时,跟前台线程一样,争夺cpu资源。

前台线程需要手动结束,后台线程当前台线程全部结束后自动结束。

setDaemon在线程开启之前调用。

t1.setDaemon(true);
t1.start();
t2.setDaemon(true);
t2.start();

java多线程

创建线程

创建线程方式一:继承thread类。

步骤:

1、定义一个子类继承Thread类。

2、覆盖Thread类中的run方法。

3、直接创建Thread的子类对象创建线程。

4、调用start方法开启线程并调用线程的任务run方法执行。

class Demo extends Thread{
    private String name;
    Demo(String name){
        this.name = name;
    }
    public void run(){
        for(int x=0; x<10; x++){
            System.out.println(name+"...x="+x);
        }
    }
}

class ThreadDemo2{
    public static void main(String[] args){
        Demo d1 = new Demo("张三");
        Demo d2 = new Demo("李四");
        d1.start();
        d2.start();
    }
}
创建线程方式二:实现Runnable接口。

步骤:

1、定义类实现Runnable接口。

2、覆盖接口中的run方法,将线程的任务代码封闭到run方法中。

3、通过Thread类创建线程对象,并将实现Runnable接口的子类对象,作为Thread类的构造函数的参数进行传递。

为什么?因为线程的任务都封装在Runnable接口子类对象的run方法中。
所以要

4、调用线程对象的start方法开启线程。

class Demo implements Runnable{
    public void run(){
        show();
    }
    public void show(){
        for(int x=0; x<20; x++){
            system.out.println(Thread.currentThread().getname()+"........"+x);
        }
    }
}

class TreadDemo{
    public static void main(sting[] args){
        Demo d = new Demo();
        Thread t1 = new Thread(d);
        Thread t2 = new Thread(d);
        t1.start;
        t2.start;
    }
}
线程安全隐患排查原则

1、查找共享数据

2、共享数据代码的操作不止一条

事例:两个储户到银行存钱,每次存100,共存三次。银行总数从100到600递增。

class Bank{
    private int sum;
    public void add(int num){
        sum = sum + num;
        try{
            Thread.sleep();
        }catch(InterruptedException e){
            //...
        }
        System.out.println("sum="+sum);
    }
}

class Cus implements Runnable{
    private b = new Bank();
    public void run(){
        for(int x=0; x<3; x++){
            b.add(100);
        }
    }
}

class BankDemo{
    public static void main(String[] args){
        Cus c = new Cus();
        Thread t1 = new Thread(c);
        Thread t2 = new Thread(c);
        t1.start();
        t2.start();
    }
}
分析多线程安全隐患:

共享数据有:private b

执行代码有一句:b.add(100);

b.add里有一个共享数据:private int sum;

执行语句有两句:sum = sum + num; system.out.printls…;

此处会产生安全隐患:比如第一次当线程0进来时,sum = 100,但没有执行system.out.println语句,第二次线程1进来,此时sum = 100+100,再执行线程0和线程1的输出语句时,就都成了200。

解决方式一:

用同步代码块把产生线程安全隐患的代码封装,注意要保持多线程用的是同一个锁。

class Bank{
    private int sum;
    private Object obj = new Object();
    public void add(int num){
        synchronized(obj){//三个线程共用一个对象锁obj
            sum = sum + num;
            try{
                Thread.sleep();
            }catch(InterruptedException e){
                //...
            }
            System.out.println("sum="+sum);
        }
    }
}
解决方式二:

同步函数

class Bank{
    private int sum;
    public synchronized void add(int num){//同步函数
        sum = sum + num;
        try{
            Thread.sleep();
        }catch(InterruptedException e){
            //...
        }
        System.out.println("sum="+sum);
    }
}
同步的好处和弊端

好处:解决了线程的安全问题

弊端:降低了效率,因为同步外的线程都会判断同步锁。

同步函数和同步代码块的区别:

1、同步函数使用的锁是this,同步代码块的锁是任意的对象。

2、建议使用同步代码块。

静态同步函数的锁

静态同步函数的锁是该函数所属的字节码文件对象

该对象可以用this.getClass()方法获取,也可以用当前类名.class获取。

class Bank{
    private int sum;
    public static synchronized void add(int num){
        sum = sum + num;
        try{
            Thread.sleep();
        }catch(InterruptedException e){
            //...
        }
        System.out.println("sum="+sum);
    }
}

该静态同步函数add的同步锁即为该函数所属的字节码文件对象,即为Bank.class。

单例模式涉及的多线程问题
//饿汉式
class Single{
    private static final Singles = new Single();
    private Single(){};
    public static Single getInstance(){
        return s;
    }
}

//懒汉式
class Single{
    private static final Singles = null;
    private Single(){};
    public static Single getInstance(){
        if(s == null)
            s = new Single();
            return s;
    }
}

class SingleDemo{
    public static void main(String[] args){

    }
}

饿汉式多线程安全隐患分析:

如果把getInstance函数封装为多线程的任务,1、多线程存在共享数据s;2、只有一条语句return s;因此,不会存在安全隐患。

懒汉式多线程安全隐患分析:

如果把getInstance函数封装为多线程的任务,1、多线程存在共享数据s;2、有多条语句;因此,存在安全隐患。比如:线程0进来时,s == null,挂住了,线程1双进来了,s == null,挂住了。线程0取得执行权,生成对象并返回。线程1又取得执行权,又生成了对象并返回。就不能保证单例设计模式对象的唯一性了。

因此,懒汉式需要同步函数。

public static synchronized Single getInstance(){
    if(s == null)
        s = new Single();
        return s;
}

解决了安全问题,但是每一次线程进来时都会判断同步锁是否一样,降低了效率。

同步代码块解决安全问题以及效率问题。

public static Single getInstance(){
    if(s == null){ //a处的if
        synchronized(Single.class){
            if(s == null) //b处的if
                s = new Single();
                return s;
        }
    }
    return s;
}

分析:线程0进入getInstance,判断a处的s == null,进入if,挂起。线程1进入判断a处的s == null,进入if挂起。线程0取得执行权,判断b处的s == null,生成对象并返回。线程1取得执行权,判断b处的s已经不为null了,跳出if。线程2进入getInstance,判断a处的s已经不为null了,直接return s。

多线程死锁事例
class Test implements Runnable{
    private boolean flag = true;
    public void run(){
        if(flag){
            while(true)
                synchronized(MyLock.locka){
                    System.out.prinln(Thread.currentThread().getName()+"if...locka...");
                synchronized(MyLock.lockb){
                        System.out.prinln(Thread.currentThread().getName()+"if...lockb...");                    
                    }
                }
        }else{
            while(true)
                synchronized(MyLock.lockb){
                    System.out.prinln(Thread.currentThread().getName()+"else...lockb...");    
                synchronized(MyLock.locka){
                        System.out.prinln(Thread.currentThread().getName()+"else...locka...");    
                    }
                }
        }
    }
}

class MyLock{
    public static final Object locka = new Object();
    public static final Object lockb = new Object();
}

class DeadLockTest{
    public static void main(String[] args){
        Test a = new Test();
        Thread t1 = new Thread(a);
        Thread t2 = new Thread(a);
        t1.start();
        try{
            Thread.sleep(10);//冻结main主线程10毫秒
        }catch(){
            //...
        }
        a.flag = false;
        t2.start();
    }
}

网格布局

原文地址:CSS Grid布局指南

英文出处:A Complete Guide to CSS Grid Layout

简介

CSS Grid布局 (又名”网格”),是一个基于二维网格布局的系统,主要目的是改变我们基于网格设计的用户接口方式。如我们所知,CSS 总是用于网页的样式设置,但它并没有起到很好的作用。刚开始的时候我们使用表格(table),然后使用浮动(float)、 定位(position)和内联块(inline-block),但所有这些方法本质上来讲都是hacks,存留了很多需要实现的重要功能问题(例如,垂直居中)。虽然Flexbox可以起到一定的补救作用,但是它只可以实现简单的一维布局,并不适用于复杂的二维布局(实际上 Flexbox 和 Grid 可以一起结合使用起到最佳效果)。网格是 CSS 第一次专门创建的模块,用来解决我们之前在制作网站时使用hacks处理布局问题。

这里有两件事情启发我创建本指南。第一个是 Rachel Andrew 的令人敬畏的书–为 CSS Grid 布局做好准备。这本书很详尽明确的的介绍了Grid,如果你想很好的掌握Grid的基础知识,我强烈建议你去购买。另外一个很大的灵感来自于 Chris Coyier 的– Flexbox完整指南,这本书是我了解Flebox的一个很优秀的资源。这里,我还想补充一句,当你使用谷歌搜索”flexbox”时,会出现很多类似的资源,但是为什么不直接利用最好的资源呢?

我书写此指南的目的是基于目前最新版本,规范其网格概念。所以我不会再次提及过时的 IE 语法,并且随着规范的成熟,我会尽力定期更新此指南。

基础知识与浏览器支持

Grid 的入门是很容易的。你只需要定义一个容器元素并设置display:grid,使用grid-template-columns 和 grid-template-rows属性设置网格的列与 行的大小,然后使用grid-column 和 grid-row属性将其子元素放入网格之中。与flexbox类似,网格项的源顺序无关紧要。为了更好地使你的网格与媒体查询相结合使用,你可以在 CSS 中任意放置。想象一下你定义的整个页面布局,然后如果想要完全重新布局以适应不同的屏幕宽度,这时仅仅使用几行 CSS 代码就可以实现。Grid是曾经介绍过的最强大 CSS 模块之一。

关于 Grid 一件很重要的事情就是它现在还不适用于项目使用。目前还处于 W3C 的工作草案之中,并且默认情况下,还不被所有的浏览器所支持。Internet Explorer 10 和 11 已经可以实现支持,但也是利用一种过时的语法实现的。现在出于示例演示,我建议你使用启用了特殊标志的 Chrome, Opera 或者 Firefox 。在 Chrome,导航到chrome://flags 并启用” web 实验平台功能”。该方法同样适用于 Opera (opera://flags)。在Firefox中,启用 layout.css.grid.enabled 标志。

除了Microsoft,浏览器厂商似乎想要等到Grid规范成熟后再加以推广。这是一件好事,因为这意味着我们就不需要担心学习多个语法。

等待 Grid 的使用,只是时间的问题。但是现在你需要开始学习它了。

重要术语

在深入研究Grid之前,我们需要理解其相关术语概念。因为这里涉及到的术语在概念上都有点类似,如果你没有首先记住Grid规范中的相关定义,你就会很容易将其与另一个概念相混淆。但是不需要担心,这里的属性并不是很多。

网格容器(Grid Container)

当一个元素设置display: grid属性时,它就会成为所有网格项(Grid Items)的父元素。在下面的示例中,container就是网格容器。

<div class="container">
    <div class="item item-1"></div>
    <div class="item item-2"></div>
    <div class="item item-3"></div>
</div>

网格项(Grid Item)

网格容器的孩子(e.g. 子元素)。这里item元素都是网格项,但是sub-item不包含其中。

<div class="container">
    <div class="item"></div> 
    <div class="item">
        <p class="sub-item"></p>
    </div>
    <div class="item"></div>
</div>

网格线(Grid Line)

分界线构成了网格的结构。他们可以是垂直的(“列网格线”)也可以是水平的(“行网格线”),并且存在于一行或一列的任一侧。下面图片中的黄线就是列网格线的一个例子。

Grid布局指南

Grid布局指南

网格轨道(Grid Track)

两个相邻网格线之间的空间。你可以把它们想像成网格的行或列。下图所示的是第二行和第三行网格线之间的网格轨道。

Grid布局指南

Grid布局指南

网格单元格(Grid Cell)

两个相邻的行和两个相邻的列之间的网格线空间。它是网格的一个”单位”。下面图片所示的是行网格线 1 和 2 与列网格线 2 和 3 之间的网格单元格。

Grid布局指南

Grid布局指南

网格区域(Grid Area)

四条网格线所包围的所有空间。网格区域可由任意数量的网格单元格组成。下面图片所示的是行网格线 1 和 3 和列网格线 1 和 3 之间的网格区域。

Grid布局指南

Grid布局指南

网格容器属性(Grid Container)

display

定义一个元素成为网格容器,并对其内容建立一个网格格式的上下文。

属性值:
  • grid: 产生一个块级的网格

  • inline-grid: 产生内联级网格

    .container{
        display: grid | inline-grid   
    }
    

注: column, float, clear, 和 vertical-align 元素对网格容器不起作用。

grid-template-rows

利用以空格分隔的值定义网格的列和行。值的大小代表轨道的大小,并且它们之间的空格表示网格线。

属性值:
  • : 可以是一个长度、百分比或者是网格中自由空间的一小部分(使用fr单位)

  • : 你选择的任意名称

  • subgrid - 如果你的网格容器本身就是一个网格项(即嵌套网格),你可以使用此属性指定行和列的大小继承于父元素而不是自身指定。

    .container{
        grid-template-columns: <track-size> ... | <line-name> <track-size> ... | subgrid;
        grid-template-rows: <track-size> ... | <line-name> <track-size> ... | subgrid;
    }
    

示例:

当你在值之间留有空格时,网络线就会自动分配数值名称:

.container{
    grid-template-columns: 40px 50px auto 50px 40px;
    grid-template-rows: 25% 100px auto;
}

Grid布局指南

Grid布局指南

但是你也可以显示命名,请参考下面括号语法中的名称命名方式:

.container{
    grid-template-columns: [first] 40px [line2] 50px [line3] auto [col4-start] 50px [five] 40px [end];
    grid-template-rows: [row1-start] 25% [row1-end] 100% [third-line] auto [last-line];
}

Grid布局指南

Grid布局指南

请注意,一条网格线可以具有有多个名称。例如,这里的第二行将有两个名字: row1-end 和 row2-start:

.container{
    grid-template-rows: [row1-start] 25% [row1-end row2-start] 25% [row2-end];
}

如果你的定义中包含重复的部分,你可以使用 repeat() 表示法进行精简:

.container{
    grid-template-columns: repeat(3, 20px [col-start]) 5%;
}

等效于:

.container{
    grid-template-columns: 20px [col-start] 20px [col-start] 20px [col-start] 5%;
}

fr 单位允许你将一个轨道大小设置为网格容器内自由空间的一小部分。如下所示,每个网格项就会占据网格容器宽度的三分之一:

.container{
    grid-template-columns: 1fr 1fr 1fr;
}

这里自由空间表示除去非弹性项以后剩余的空间。在此示例中的 fr 单位的可用空间表示减去50px以后的空间大小:

.container{
    grid-template-columns: 1fr 50px 1fr 1fr;
}

grid-template-areas

使用grid-area属性定义网格区域名称,从而定义网格模板。网格区域重复的名称就会导致内容跨越这些单元格。句点表示一个空单元格。语法本身提供了一种可视化的网格结构。

属性值:
  • : 使用grid-area属性定义网格区域名称

  • .: 句点表示一个空单元格

  • none: 无网格区域被定义

    .container{
        grid-template-areas: "<grid-area-name> | . | none | ..."
                          "..."
    }
    
示例:
.item-a{
    grid-area: header;
}
.item-b{
    grid-area: main;
}
.item-c{
    grid-area: sidebar;
}
.item-d{
    grid-area: footer;
}
.container{
    grid-template-columns: 50px 50px 50px 50px;
    grid-template-rows: auto;
    grid-template-areas: "header header header header"
                         "main main . sidebar"
                         "footer footer footer footer"
}

这将创建一个四列三行的网格。最上面的一行为header区域。中间一行由两个main区域,一个空单元格和一个sidebar区域。最后一行是footer区域。

Grid布局指南

Grid布局指南

你所声明的每一行都需要具有相同数目的单元格。

你可以使用任意数量的句点(.)声明单个空单元格。只要句点之间没有空格就表示一个空单元格。

注意,你只是使用此语法进行网格区域命名,而不是网格线命名。当你使用此语法时,区域两边的线就会得到自动命名。如果网格区域名称为foo,则其行线和列线的名称就将为foo-start,最后一行线及其最后一列线的名字就会为foo-end。这意味着一些线就可能具有多个名称,如上面示例中所示,拥有三个名称: header-start, main-start, 以及footer-start。

grid-column-gap和grid-row-gap

指定网格线的大小。你可以把它想像成在行/列之间设置间距宽度。

属性值:
  • <line-size>: 一个长度值
1
2
3
4
.container{
grid-column-gap: <line-size>;
grid-row-gap: <line-size>;
}
示例:
.container{
    grid-template-columns: 100px 50px 100px;
    grid-template-rows: 80px auto 80px; 
    grid-column-gap: 10px;
    grid-row-gap: 15px;
}

Grid布局指南

Grid布局指南

间距仅仅在列/行之间产生,而不会在边缘区。

grid-gap

grid-column-gap 和 grid-row-gap的简写值。

属性值:
  • : 长度值
1
2
3
.container{
grid-gap: <grid-column-gap> <grid-row-gap>;
}
示例:
.container{
    grid-template-columns: 100px 50px 100px;
    grid-template-rows: 80px auto 80px; 
    grid-gap: 10px 15px;
}

如果没有指定grid-row-gap属性的值,默认与grid-column-gap属性值相同

justify-items

沿列轴对齐网格项中的内容(相反于align-item属性定义的沿行轴对齐)。此值适用于容器内所有的网格项。

属性值:
  • start: 内容与网格区域的左端对齐

  • end: 内容与网格区域的右端对齐

  • center: 内容处于网格区域的中间位置

  • stretch: 内容宽度占据整个网格区域空间(默认值)

1
2
3
.container{
justify-items: start | end | center | stretch;
}
示例:
.container{
    justify-items: start;
}

Grid布局指南

Grid布局指南
.container{
    justify-items: end;
}

Grid布局指南

Grid布局指南
.container{
    justify-items: center;
}

Grid布局指南

Grid布局指南
.container{
    justify-items: stretch;
}

Grid布局指南

Grid布局指南

这也可以使用justify-self属性对各个网格项进行设置。

align-items

沿行轴对齐网格项中的内容(相反于justify-item属性定义的沿列轴对齐)。此值适用于容器内所有的网格项。

属性值:
  • start: 内容与网格区域的顶端对齐

  • end: 内容与网格区域的底部对齐

  • center: 内容处于网格区域的中间位置

  • stretch: 内容高度占据整个网格区域空间(默认值)

1
2
3
.container{
align-items: start | end | center | stretch;
}
示例:
.container{
    align-items: start;
}

Grid布局指南

Grid布局指南
.container{
    align-items: end;
}

Grid布局指南

Grid布局指南
.container{
    align-items: center;
}

Grid布局指南

Grid布局指南
.container{
    align-items: stretch;
}

Grid布局指南

Grid布局指南

这也可以使用align-self属性对各个网格项进行设置。

justify-content

当你使用px这种非响应式的单位对你的网格项进行大小设置时,就有可能出现一种情况–你的网格大小可能小于其网格容器的大小。在这种情况下,你就可以设置网格容器内网格的对齐方式。此属性会将网格沿列轴进行对齐(相反于align-content属性定义的沿行轴对齐)。

属性值:
  • start: 网格与网格容器的左端对齐

  • end: 网格与网格容器的右端对齐

  • center: 网格处于网格容器的中间

  • stretch: 调整网格项的大小,使其宽度填充整个网格容器

  • space-around: 在网格项之间设置偶数个空格间隙,其最边缘间隙大小为中间空格间隙大小的一半

  • space-between: 在网格项之间设置偶数个空格间隙,其最边缘不存在空格间隙

  • space-evenly: 在网格项之间设置偶数个空格间隙,同样适用于最边缘区域

1
2
3
.container{
justify-content: start | end | center | stretch | space-around | space-between | space-evenly;
}

示例:

.container{
    justify-content: start;
}

Grid布局指南

Grid布局指南
.container{
    justify-content: end; 
}

Grid布局指南

Grid布局指南
.container{
    justify-content: center;  
}

Grid布局指南

Grid布局指南
.container{
    justify-content: stretch; 
}

Grid布局指南

Grid布局指南
.container{
    justify-content: space-around;    
}

Grid布局指南

Grid布局指南
.container{
    justify-content: space-between;   
}

Grid布局指南

Grid布局指南
.container{
  justify-content: space-evenly;    
}

Grid布局指南

Grid布局指南

align-content

当你使用px这种非响应式的单位对你的网格项进行大小设置时,就有可能出现一种情况–你的网格大小可能小于其网格容器的大小。在这种情况下,你就可以设置网格容器内网格的对齐方式。此属性会将网格沿行轴进行对齐(相反于justify-content属性定义的沿列轴对齐)。

属性值:
  • start: 网格与网格容器的顶端对齐

  • end: 网格与网格容器的底部对齐

  • center: 网格处于网格容器的中间

  • stretch: 调整网格项的大小,使其高度填充整个网格容器

  • space-around: 在网格项之间设置偶数个空格间隙,其最边缘间隙大小为中间空格空隙大小的一半

  • space-between: 在网格项之间设置偶数个空格间隙,其最边缘不存在空格间隙

  • space-evenly: 在网格项之间设置偶数个空格间隙,同样适用于最边缘区域

1
2
3
.container{
align-content: start | end | center | stretch | space-around | space-between | space-evenly;
}

#####示例:

.container{
    align-content: start; 
}

Grid布局指南

Grid布局指南
.container{
    align-content: end;   
}

Grid布局指南

Grid布局指南
.container{
    align-content: center;    
}

Grid布局指南

Grid布局指南
.container{
    align-content: stretch;   
}

Grid布局指南

Grid布局指南
.container{
    align-content: space-around;  
}

Grid布局指南

Grid布局指南
.container{
    align-content: space-between; 
}

Grid布局指南

Grid布局指南
.container{
    align-content: space-evenly;  
}

Grid布局指南

Grid布局指南

grid-auto-columns和grid-auto-rows

指定任何自动生成的网格轨道(隐式网格跟踪)的大小。当你显式定位行或列(使用 grid-template-rows/grid-template-columns属性)时,就会产生超出定义范围内的隐式网格轨道。

属性值:
  • <track-siz>: 可以是长度、 百分比或网格自由空间的一小部分(使用fr单位)
1
2
3
4
.container{
grid-auto-columns: <track-size> ...;
grid-auto-rows: <track-size> ...;
}

为了说明隐式网格轨道是如何被创造出来的,请思考如下代码:

.container{
    grid-template-columns: 60px 60px;
    grid-template-rows: 90px 90px
}

Grid布局指南

Grid布局指南

这里创建了一个2 x 2 的网格。

但是现在你想象你使用grid-column 和 grid-row 来定位网格项,如下所示:

.item-a{
    grid-column: 1 / 2;
    grid-row: 2 / 3;
}
.item-b{
    grid-column: 5 / 6;
    grid-row: 2 / 3;
}

Grid布局指南

Grid布局指南

这里我们定义.item b开始于列线 5 并结束于在列线 6,但是我们从来没有定义列线 5 或 6。因为我们引用不存在的线,宽度为0的隐式轨道的就会被创建用来填补空白。我们可以使用grid-auto-columns 和 grid-auto-rows属性来设置这些隐式轨道的宽度:

.container{
    grid-auto-columns: 60px;
}

Grid布局指南

Grid布局指南

grid-auto-flow

如果你不显式的在网格中放置网格项,自动布局算法就会自动踢出此网格项。此属性用来控制自动布局算法的工作原理。

属性值:
  • row: 告诉自动布局算法填充每一行,必要时添加新行

  • column: 告诉自动布局算法填充每一列,必要时添加新列

  • dense: 告诉自动布局算法试图填补网格中之前较小的网格项留有的空白

1
2
3
.container{
grid-auto-flow: row | column | row dense | column dense
}

注意:dense值可能会导致更改网格项的顺序。

示例:

考虑如下HTMl代码:

<section class="container">
    <div class="item-a">item-a</div>
    <div class="item-b">item-b</div>
    <div class="item-c">item-c</div>
    <div class="item-d">item-d</div>
    <div class="item-e">item-e</div>
</section>

这里定义了一个两列五行的网格,并将 grid-auto-flow属性设置为row(即默认值):

.container{
    display: grid;
    grid-template-columns: 60px 60px 60px 60px 60px;
    grid-template-rows: 30px 30px;
    grid-auto-flow: row;
}

将网格项放置在网格中时只需要其中的两个网格项:

.item-a{
    grid-column: 1;
    grid-row: 1 / 3;
}
.item-e{
    grid-column: 5;
    grid-row: 1 / 3;
}

因为我们将grid-auto-flow属性设置为了row,所以我们的网格看起来会像这个样子。注意我们我们没有对其进行设置的三个网格项(item-b, item-c and item-d),会沿行轴进行布局。

Grid布局指南

Grid布局指南

如果我们将grid-auto-flow属性设置为 column,item-b, item-c 和 item-d 就会沿列轴进行布局。

.container{
    display: grid;
    grid-template-columns: 60px 60px 60px 60px 60px;
    grid-template-rows: 30px 30px;
    grid-auto-flow: column;
}

Grid布局指南

Grid布局指南

grid

在一行声明中设置一下所有属性的简写形式:grid-template-rows, grid-template-columns, grid-template-areas, grid-auto-rows, grid-auto-columns, 以及 grid-auto-flow。它将 grid-column-gap 和 grid-row-gap属性设置为初始值,即使它们不能显示的设置此属性。

属性值:
  • none: 将所有的子属性设置为初始值

  • subgrid: 将grid-template-rows 和 grid-template-columns属性值设置为subgrid,其余子属性设置为初始值

  • <grid-template-rows> / <grid-template-columns>: 将grid-template-rows 和 grid-template-columns属性值设置为指定值,其余子属性设置为初始值

  • <grid-auto-flow>[<grid-auto-rows> [ / <grid-auto-columns>] ] : grid-auto-flow, grid-auto-rows 和 grid-auto-columns属性分别接受相同的值,如果省略了grid-auto-columns属性,它将设置为grid-auto-rows属性的值。如果两者均被忽略,那么都将被设置为初始值。

1
2
3
.container{
grid: none | subgrid | <grid-template-rows> / <grid-template-columns> | <grid-auto-flow> [<grid-auto-rows> [/ <grid-auto-columns>]];
}
示例:

下面两个代码块是等效的:

.container{
    grid: 200px auto / 1fr auto 1fr;
}

.container{
    grid-template-rows: 200px auto;
    grid-template-columns: 1fr auto 1fr;
    grid-template-areas: none;
}

同样,下面的两个代码块也是等效的:

.container{
    grid: column 1fr / auto;
}

.container{
    grid-auto-flow: column;
    grid-auto-rows: 1fr;
    grid-auto-columns: auto;
}

它还接受一次性设置所有属性,更复杂但非常方便的语法。指定grid-template-areas, grid-auto-rows 和 grid-auto-columns属性,其他所有子属性都将设置为其初始值。你现在所做的是在其网格区域内,指定网格线名称和内联轨道大小。下面是最简单的描述:

.container{
    grid: [row1-start] "header header header" 1fr [row1-end]
          [row2-start] "footer footer footer" 25px [row2-end]
          / auto 50px auto;
}

等效于:

.container{
    grid-template-areas: "header header header"
                         "footer footer footer";
    grid-template-rows: [row1-start] 1fr [row1-end row2-start] 25px [row2-end];
    grid-template-columns: auto 50px auto;    
}

网格项属性(Grid Items)

grid-column-start/grid-column-end/grid-row-start/grid-row-end

使用特定的网格线确定网格项在网格内的位置。grid-column-start/grid-row-start 属性表示网格项的网格线的起始位置,grid-column-end/grid-row-end属性表示网格项的网格线的终止位置。

属性值:
  • <line>: 可以是一个数字来引用相应编号的网格线,或者使用名称引用相应命名的网格线

  • span <number>: 网格项包含指定数量的网格轨道

  • span <name>: 网格项包含指定名称网格项的网格线之前的网格轨道

  • auto: 表明自动定位,自动跨度或者默认跨度之一

1
2
3
4
5
6
.item{
grid-column-start: <number> | <name> | span <number> | span <name> | auto
grid-column-end: <number> | <name> | span <number> | span <name> | auto
grid-row-start: <number> | <name> | span <number> | span <name> | auto
grid-row-end: <number> | <name> | span <number> | span <name> | auto
}

示例:

.item-a{
    grid-column-start: 2;
    grid-column-end: five;
    grid-row-start: row1-start
    grid-row-end: 3
}

Grid布局指南

Grid布局指南
.item-b{
    grid-column-start: 1;
    grid-column-end: span col4-start;
    grid-row-start: 2
    grid-row-end: span 2
}

Grid布局指南

Grid布局指南

如果没有声明grid-column-end/grid-row-end属性,默认情况下网格项的跨度为1。

网格项可以互相重叠。可以使用z-index属性控制堆叠顺序。

grid-column/grid-row

grid-column-start + grid-column-end, 和 grid-row-start + grid-row-end属性分别的简写形式。
属性值:
  • <start-line> / <end-line>: 每一个属性均接收一个相同值,包括跨度。
1
2
3
4
.item{
grid-column: <start-line> / <end-line> | <start-line> / span <value>;
grid-row: <start-line> / <end-line> | <start-line> / span <value>;
}
示例:
.item-c{
    grid-column: 3 / span 2;
    grid-row: third-line / 4;
}

Grid布局指南

Grid布局指南

如果没有声明结束网格线值,默认网格轨道跨度为1.

grid-area

给网格项进行命名以便于模板使用grid-template-areas属性创建时可以加以引用。另外也可以被grid-row-start + grid-column-start + grid-row-end + grid-column-end属性更为简洁的加以引用。

属性值:
  • <name>: 你所定义的名称

  • <row-start> / <column-start> / <row-end> / <column-end>: 可以为数字或者名称

1
2
3
.item{
grid-area: <name> | <row-start> / <column-start> / <row-end> / <column-end>;
}
示例:

对网格项进行命名的一种方式:

.item-d{
    grid-area: header
}

grid-row-start + grid-column-start + grid-row-end + grid-column-end属性的一种简写方式:

.item-d{
    grid-area: 1 / col4-start / last-line / 6
}

Grid布局指南

Grid布局指南

justify-self

沿列轴对齐网格项中的内容(相反于align-item属性定义的沿行轴对齐)。此值适用于单一网格项中的内容。

属性值:
  • start: 内容与网格区域的左端对齐

  • end: 内容与网格区域的右端对齐

  • center: 内容处于网格区域的中间位置

  • stretch: 内容宽度占据整个网格区域空间(默认值)

1
2
3
.item{
justify-self: start | end | center | stretch;
}
示例
1
2
3
.item-a{
justify-self: start;
}

Grid布局指南

Grid布局指南
.item-a{
    justify-self: end;
}

Grid布局指南

Grid布局指南
.item-a{
    justify-self: center;
}

Grid布局指南

Grid布局指南
.item-a{
    justify-self: stretch;
}

Grid布局指南

Grid布局指南

设置网格中所有网格项的对齐方式,可以使用网格容器上的justify-items属性。

align-self

沿行轴对齐网格项中的内容(相反于justify-item属性定义的沿列轴对齐)。此值适用于单一网格项中的内容。

属性值:
  • start: 内容与网格区域的顶端对齐

  • end: 内容与网格区域的底部对齐

  • center: 内容处于网格区域的中间位置

  • stretch: 内容高度占据整个网格区域空间(默认值)

1
2
3
.item{
align-self: start | end | center | stretch;
}

示例:

1
2
3
.item-a{
align-self: start;
}

Grid布局指南

Grid布局指南
1
2
3
.item-a{
align-self: end;
}

Grid布局指南

Grid布局指南
1
2
3
.item-a{
align-self: center;
}

Grid布局指南

Grid布局指南
1
2
3
.item-a{
align-self: stretch;
}

Grid布局指南

Grid布局指南

使网格中所有的网格项对齐,可以使用网格容器上的align-items属性。

特别声明:本文来自于Chris House写的指南,此份指南由Chris himself所写,并且会不断的保持更新。

bootstrap笔记

Bootstrap:最受欢迎的HTML、CSS和JS框架,用于开发响应式布局、移动设备优先的WEB项目。

启步

1、响应式布局标签:

<meta name="viewport" content="width=device-width, initial-scale=1">

2、为了让最新浏览器运行在最新的渲染模式下,建议将此 标签加入到页面中:

<meta http-equiv="X-UA-Compatible" content="IE=edge">

3、让部分国产浏览器如360采用高速模式渲染页面:

<meta name="renderer" content="webkit">

全局CSS样式

一、概览

1、移动设备优先

在移动设备浏览器上,通过为视口(viewport)设置 meta 属性为 user-scalable=no 可以禁用其缩放(zooming)功能。

<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">

2、布局容器

.container 类用于固定宽度并支持响应式布局的容器。

<div class="container">
  ...
</div>

.container-fluid 类用于 100% 宽度,占据全部视口(viewport)的容器。

<div class="container-fluid">
  ...
</div>
二、栅格系统

1、媒体查询

Less文件中使用以下媒体查询(media query)来创建关键的分界点阈值。

/* 超小屏幕(手机,小于 768px) */
/* 没有任何媒体查询相关的代码,因为这在 Bootstrap 中是默认的(还记得 Bootstrap 是移动设备优先的吗?) */

/* 小屏幕(平板,大于等于 768px) */
@media (min-width: @screen-sm-min) { ... }

/* 中等屏幕(桌面显示器,大于等于 992px) */
@media (min-width: @screen-md-min) { ... }

/* 大屏幕(大桌面显示器,大于等于 1200px) */
@media (min-width: @screen-lg-min) { ... }

2、栅格参数

| 超小屏幕 手机(<768px) 
| 小屏幕 平板(>=768px) 
| 中等屏幕 显示器(>=992px) 
| 大屏幕 大显示器(>=1200px)
.container 最大宽度 自动 750px 970px 1170px
类前缀 .col-xs- .col-sm- .col-md- .col-lg-
槽宽 30px(每列左右各15px) 30px 30px 30px

实例:使用单一的一组 .col-md-* 栅格类,就可以创建一个基本的栅格系统,在手机和平板设备上一开始是堆叠在一起的(超小屏幕到小屏幕这一范围),在桌面(中等)屏幕设备上变为水平排列。所有“列(column)必须放在 ” .row 内。

.col-md

grid demo
<div class="row">
  <div class="col-md-1">.col-md-1</div>
  <div class="col-md-1">.col-md-1</div>
  <div class="col-md-1">.col-md-1</div>
  <div class="col-md-1">.col-md-1</div>
  <div class="col-md-1">.col-md-1</div>
  <div class="col-md-1">.col-md-1</div>
  <div class="col-md-1">.col-md-1</div>
  <div class="col-md-1">.col-md-1</div>
  <div class="col-md-1">.col-md-1</div>
  <div class="col-md-1">.col-md-1</div>
  <div class="col-md-1">.col-md-1</div>
  <div class="col-md-1">.col-md-1</div>
</div>
<div class="row">
  <div class="col-md-8">.col-md-8</div>
  <div class="col-md-4">.col-md-4</div>
</div>
<div class="row">
  <div class="col-md-4">.col-md-4</div>
  <div class="col-md-4">.col-md-4</div>
  <div class="col-md-4">.col-md-4</div>
</div>
<div class="row">
  <div class="col-md-6">.col-md-6</div>
  <div class="col-md-6">.col-md-6</div>
</div>

3、实例:移动设备和桌面屏幕

是否不希望在小屏幕设备上所有列都堆叠在一起?那就使用针对超小屏幕和中等屏幕设备所定义的类吧,即 .col-xs- 和 .col-md-。请看下面的实例,研究一下这些是如何工作的。

<!-- Stack the columns on mobile by making one full-width and the other half-width -->
<div class="row">
  <div class="col-xs-12 col-md-8">.col-xs-12 .col-md-8</div>
  <div class="col-xs-6 col-md-4">.col-xs-6 .col-md-4</div>
</div>

<!-- Columns start at 50% wide on mobile and bump up to 33.3% wide on desktop -->
<div class="row">
  <div class="col-xs-6 col-md-4">.col-xs-6 .col-md-4</div>
  <div class="col-xs-6 col-md-4">.col-xs-6 .col-md-4</div>
  <div class="col-xs-6 col-md-4">.col-xs-6 .col-md-4</div>
</div>

<!-- Columns are always 50% wide, on mobile and desktop -->
<div class="row">
  <div class="col-xs-6">.col-xs-6</div>
  <div class="col-xs-6">.col-xs-6</div>
</div>

4、列偏移

使用 .col-md-offset- 类可以将列向右侧偏移。这些类实际是通过使用 选择器为当前元素增加了左侧的边距(margin)。例如,.col-md-offset-4 类将 .col-md-4 元素向右侧偏移了4个列(column)的宽度。

<div class="row">
  <div class="col-md-4">.col-md-4</div>
  <div class="col-md-4 col-md-offset-4">.col-md-4 .col-md-offset-4</div>
</div>
<div class="row">
  <div class="col-md-3 col-md-offset-3">.col-md-3 .col-md-offset-3</div>
  <div class="col-md-3 col-md-offset-3">.col-md-3 .col-md-offset-3</div>
</div>
<div class="row">
  <div class="col-md-6 col-md-offset-3">.col-md-6 .col-md-offset-3</div>
</div>

5、列排序

通过使用 .col-md-push- 和 .col-md-pull- 类就可以很容易的改变列(column)的顺序。

<div class="row">
  <div class="col-md-9 col-md-push-3">.col-md-9 .col-md-push-3</div>
  <div class="col-md-3 col-md-pull-9">.col-md-3 .col-md-pull-9</div>
</div>
三、排版

1、标题

h1到h6做为主标题 samll做为副标题

<h1>h1. Bootstrap heading <small>Secondary text</small></h1>

2、对齐

<p class="text-left">Left aligned text.</p>
<p class="text-center">Center aligned text.</p>
<p class="text-right">Right aligned text.</p>
<p class="text-justify">Justified text.</p>
<p class="text-nowrap">No wrap text.</p>

3、地址

<address>
  <strong>Twitter, Inc.</strong><br>
  795 Folsom Ave, Suite 600<br>
  San Francisco, CA 94107<br>
  <abbr title="Phone">P:</abbr> (123) 456-7890
</address>

<address>
  <strong>Full Name</strong><br>
  <a href="mailto:#">first.last@example.com</a>
</address>

4、内联列表

<ul class="list-inline">
  <li>...</li>
</ul>

5、水平排列的描述

<dl class="dl-horizontal">
  <dt>...</dt>
  <dd>...</dd>
</dl>

四、表格

1、响应式表格

将任何 .table 元素包裹在 .table-responsive 元素内,即可创建响应式表格,其会在小屏幕设备上(小于768px)水平滚动。当屏幕大于 768px 宽度时,水平滚动条消失。

<div class="table-responsive">
  <table class="table">
    ...
  </table>
</div>

五、表单

1、基本实例

单独的表单控件会被自动赋予一些全局样式。所有设置了 .form-control 类的 <input><textarea><select> 元素都将被默认设置宽度属性为 width: 100%;。 将 label 元素和前面提到的控件包裹在 .form-group 中可以获得最好的排列。

<form>
  <div class="form-group">
    <label for="exampleInputEmail1">Email address</label>
    <input type="email" class="form-control" id="exampleInputEmail1" placeholder="Email">
  </div>
  <div class="form-group">
    <label for="exampleInputPassword1">Password</label>
    <input type="password" class="form-control" id="exampleInputPassword1" placeholder="Password">
  </div>
  <div class="form-group">
    <label for="exampleInputFile">File input</label>
    <input type="file" id="exampleInputFile">
    <p class="help-block">Example block-level help text here.</p>
  </div>
  <div class="checkbox">
    <label>
      <input type="checkbox"> Check me out
    </label>
  </div>
  <button type="submit" class="btn btn-default">Submit</button>
</form>

2、…

理解数据绑定过程 - `$watch`, `$apply`和`$digest`

项目开发过程中,在自定义指令里,用jquery捕捉到事件后改变scope里的变量,模板里的变量没有发生变化。幸运的是,从中文社区找到一篇关于数据绑定过程的文章,不仅解决了这个问题,对angular的理解也更加深了一层。

博文地址:理解$watch ,$apply 和 $digest — 理解数据绑定过程

英文地址:$watch How the $apply Runs a $digest

一、浏览器循环事件和Angular.js扩展

我们的浏览器一直在等待事件,比如用户交互。假如你点击一个按钮或者在输入框里输入东西,事件的回调函数就会在javascript解释器里执行,然后你就可以做任何DOM操作,等回调函数执行完毕时,浏览器就会相应地对DOM做出变化。 Angular拓展了这个事件循环,生成一个angular context的执行环境(记住,这是个重要的概念),为了解释什么是context以及它如何工作,我们还需要解释更多的概念。

二、$watch队列($watch list)

页面上绑定一个model就会向$watch队列里插入一条 $watch 。$watch 检测它监视的model是否发生了变化。

1、demo
index.html
User: <input type="text" ng-model="user" />
Password: <input type="password" ng-model="pass" />

页面绑定了两个scope的变量,所以$watch list里面加入了两个$watch。

2、demo
controllers.js
app.controller('MainCtrl', function($scope) {
  $scope.foo = "Foo";
  $scope.world = "World";
});
index.html
Hello, {{ World }}

这里,即便我们在$scope上添加了两个东西,但是只有一个绑定在了UI上,因此在这里只生成了一个$watch.

3、demo
controllers.js
app.controller('MainCtrl', function($scope) {
  $scope.people = [...];
});
index.html
<ul>
  <li ng-repeat="person in people">
      {{person.name}} - {{person.age}}
  </li>
</ul>

这里又生成了多少个$watch呢?每个person有两个(一个name,一个age),然后ng-repeat又有一个,因此10个person一共是(2 * 10) +1,也就是说有21个$watch。

4、总结

因此,每一个绑定到了UI上的数据都会生成一个$watch。

那这些$watch是什么时候生成的呢?

当我们的模版加载完毕时,也就是在linking阶段(Angular分为compile阶段和linking阶段—译者注),Angular解释器会寻找每个directive,然后生成每个需要的$watch。

三、$digest循环

还记得我前面提到的扩展的事件循环吗?当浏览器接收到可以被angular context处理的事件时,$digest循环就会触发

这个循环是由两个更小的循环组合起来的:

一个处理evalAsync队列。

另一个处理$watch队列,这个也是本篇博文的主题。 这个是处理什么的呢?$digest将会遍历我们的$watch

  • 嘿,$watch,你的值是什么?

    • 是9。
  • 好的,它改变过吗?

    • 没有,先生。

*(这个变量没变过,那下一个)

  • 你呢,你的值是多少?

    • 报告,是Foo。
  • 刚才改变过没?

    • 改变过,刚才是Bar。
  • (很好,我们有DOM需要更新了)

  • 继续询问直到$watch队列都检查过。

这就是所谓的dirty-checking。

既然所有的$watch都检查完了,那就要问了:有没有$watch更新过?如果有至少一个更新过,这个循环就会再次触发,直到所有的$watch都没有变化。这样就能够保证每个model都已经不会再变化。

记住如果循环超过10次的话,它将会抛出一个异常,防止无限循环。

当$digest循环结束时,DOM相应地变化。

1、demo
controllers.js
app.controller('MainCtrl', function() {
  $scope.name = "Foo";

  $scope.changeFoo = function() {
      $scope.name = "Bar";
  }
});
index.html
{{ name }}
<button ng-click="changeFoo()">Change the name</button>

这里我们只有一个$watch,因为ng-click不生成$watch(函数是不会变的)。

  • 按下按钮

  • 浏览器接收到一个事件,进入angular context(后面会解释为什么)。

  • $digest循环开始执行,查询每个$watch是否变化。

  • 由于监视$scope.name的$watch报告了变化,它会强制再执行一次$digest循环。

  • 新的$digest循环没有检测到变化。

  • 浏览器拿回控制权,更新与$scope.name新值相应部分的DOM。

四、通过$apply进入angular context

谁来决定什么事件可以进入angular context,而哪些又不能进入呢?$apply!

  • 如果当事件触发时,你调用$apply,它会进入angular context,如果没有调用就不会进入。

  • 现在你可能会问:刚才的例子里我也没有调用$apply啊,为什么?Angular为了做了!

  • 当你点击带有ng-click的元素时,事件就会被封装到一个$apply调用。

  • 例如你有一个ng-model=”foo”的输入框,然后你敲一个f,事件就会这样调用$apply(“foo = ‘f’;”)。

五、Angular什么时候不会自动为我们$apply呢?

这是Angular新手共同的痛处。为什么我的jQuery不会更新我绑定的东西呢?因为jQuery没有调用$apply,事件没有进入angular context,$digest循环永远没有执行。

1、demo
app.js
app.directive(‘clickable’, function() {

return {
  restrict: "E",
  scope: {
    foo: '=',//通过 '=' 双向绑定,隔离的scope得以与controller里的父scope通信
    bar: '='//双向绑定
  },
  template: '<ul style="background-color: lightblue"><li>{{foo}}</li><li>{{bar}}</li></ul>',
  link: function(scope, element, attrs) {
    element.bind('click', function() {
      scope.foo++;
      scope.bar++;
    });
  }
}

});

app.controller('MainCtrl', function($scope) {
  $scope.foo = 0;
  $scope.bar = 0;
});

代码意图:将foo和bar从controller里绑定到一个list里面,每次点击这个元素的时候,foo和bar都会自增1。

但是当我们点击元素ul时,发现元素没有变化还是0。

这是因为点击事件是一个没有封装到$apply里的事件。虽然如此,但是scope里的变量(foo和bar)确实是自增了的,只是因为没能通过$apply进入angular context,所以模板里的dom没能得到更新。

也就是说,如果我们自己执行一次$apply,那么这些$watch就会看到变化,然后根据需要更新dom。

试试看看这个例子:http://jsbin.com/opimat/2/

  • 当我们先点击ul时,像上面所讲的,虽然scope里的foo和bar都自增了,但由于element.bind(‘clilck’)事件没有封装到$apply里,所以dom没有得到更新,模板里看不到变化。

  • 但当我们点击 change hello按钮时,由于ng-click事件被封装到了$apply里,所以进入了angular context里,$digest循环执行,询问每个$watch时,发现不仅$sopce.hello的值发生了变化,scope.foo和scope.bar也发生了变化。

  • 浏览器更新dom时,就可以看到三处变化了。

2、demo

现在你在想那并不是你想要的,你想要的是点击蓝色区域的时候就更新点击数。

a、很简单,执行一下$apply就可以了:

element.bind('click', function() {
  scope.foo++;
  scope.bar++;

  scope.$apply();
});

$apply是我们的$scope(或者是direcvie里的link函数中的scope)的一个函数,调用它会强制一次$digest循环(除非当前正在执行循环,这种情况下会抛出一个异常,这是我们不需要在那里执行$apply的标志)。

试试看:http://jsbin.com/opimat/3/edit

b、有用啦,但是有一种更好的使用$apply的方法:

element.bind('click', function() {
  scope.$apply(function() {
      scope.foo++;
      scope.bar++;
  });
})

有什么不一样的?差别就是在第一个版本中,我们是在angular context的外面更新的数据,如果有发生错误,Angular永远不知道。很明显在这个像个小玩具的例子里面不会出什么大错,但是想象一下我们如果有个alert框显示错误给用户,然后我们有个第三方的库进行一个网络调用然后失败了,如果我们不把它封装进$apply里面,Angular永远不会知道失败了,alert框就永远不会弹出来了。

六、使用 $watch 来监视你自己的东西

你已经知道了我们设置的任何绑定都有一个它自己的$watch,当需要时更新DOM,但是我们如果要自定义自己的watches呢?简单

1、demo
app.js
app.controller('MainCtrl', function($scope) {
  $scope.name = "Angular";

  $scope.updated = -1;

  $scope.$watch('name', function() {
    $scope.updated++;
  });
});
index.html
<body ng-controller="MainCtrl">
  <input ng-model="name" />
  Name updated: {{updated}} times.
</body>

这就我们创造的一个新的$watch方法。

  • 第一个参数是一个字符串或函数,在这里只是一个字符串,就是我们要监视的变量的名字。

  • 第二个参数是一个回调函数,当变量发生变化时被调用。

  • 我们要知道的第一件事就是当controller执行到这个 $watch 时,它会立即执行一次,困此我们设置updated为 -1 。

试试看:http://jsbin.com/ucaxan/1/edit

2、demo
app.js
app.controller('MainCtrl', function($scope) {
  $scope.name = "Angular";

  $scope.updated = 0;

  $scope.$watch('name', function(newValue, oldValue) {
    if (newValue === oldValue) { return; } // AKA first run
    $scope.updated++;
  });
});
index.html
<body ng-controller="MainCtrl">
  <input ng-model="name" />
  Name updated: {{updated}} times.
</body>
  • watch的第二个参数接受两个参数,新值和旧值。

  • 我们可以用他们来略过第一次的执行。

  • 通常你不需要略过第一次执行,但在这个例子里面你是需要的。灵活点嘛少年。

3、demo
app.js
app.controller('MainCtrl', function($scope) {
  $scope.user = { name: "Fox" };

  $scope.updated = 0;

  $scope.$watch('user', function(newValue, oldValue) {
    if (newValue === oldValue) { return; }
    $scope.updated++;
  });
});
index.html
<body ng-controller="MainCtrl">
  <input ng-model="user.name" />
  Name updated: {{updated}} times.
</body>

我们想要监视$scope.user对象里的任何变化,和以前一样这里只是用一个对象来代替前面的字符串。

试试看:http://jsbin.com/ucaxan/3/edit

  • 没用,为啥?

  • 因为$watch默认是比较两个对象所引用的是否相同,在例子1和2里面,每次更改$scope.name都会创建一个新的基本变量,因此$watch会执行,因为对这个变量的引用已经改变了。

  • 在上面的例子里,我们在监视$scope.user,当我们改变$scope.user.name时,对$scope.user的引用是不会改变的,我们只是每次创建了一个新的$scope.user.name,但是$scope.user永远是一样的。

4、demo
app.js
app.controller('MainCtrl', function($scope) {
  $scope.user = { name: "Fox" };

  $scope.updated = 0;

  $scope.$watch('user', function(newValue, oldValue) {
    if (newValue === oldValue) { return; }
    $scope.updated++;
  }, true);
});
index.html
<body ng-controller="MainCtrl">
  <input ng-model="user.name" />
  Name updated: {{updated}} times.
</body>

试试看:http://jsbin.com/ucaxan/4/edit

  • 现在有用了吧!因为我们对$watch加入了第三个参数,它是一个bool类型的参数,表示的是我们比较的是对象的值而不是引用。

  • 由于当我们更新$scope.user.name时$scope.user也会改变,所以能够正确触发。

七、总结

1、好吧,我希望你们已经学会了在Angular中数据绑定是如何工作的。我猜想你的第一印象是dirty-checking很慢,好吧,其实是不对的。它像闪电般快。但是,是的,如果你在一个模版里有2000-3000个watch,它会开始变慢。但是我觉得如果你达到这个数量级,就可以找个用户体验专家咨询一下了

2、无论如何,随着ECMAScript6的到来,在Angular未来的版本里我们将会有Object.observe那样会极大改善$digest循环的速度。同时未来的文章也会涉及一些tips&tricks。

3、另一方面,这个主题并不容易,如果你发现我落下了什么重要的东西或者有什么东西完全错了,请在github上提交问题或提交修正。

八、回到总题

总结:数据绑定机制:

1、angular发现一个model就会向$watch list里插入一个$watch,用来监测这个model是否发生了变化 。

2、当事件触发后,比如ng-click,事件就会被封装到一个$apply。$apply决定了哪些事件触发后可以进入angular context,哪些事件触发后不会进入到angular context。

3、$apply调用后,进入到angular context。

4、$digest循环执行,询问每个$watch,看看它监测的model有没有发生变化。

5、有变化再循环,直到没有发现变化为止。

6、浏览器拿回控制权,更新相应的dom。

这也就解释了为什么jquery改变scope的变量时,为什么dom没有更新。因为jQuery没有调用$apply,事件没有进入angular context,$digest循环永远没有执行。

angularJS中的指令directive

原文:

AngularJS 指令实践指南(一)

AngularJS 指令实践指南(二)

一、创建自定义指令

var app = angular.module('myapp', []);

app.directive('rabbitHeader', function() {
  return {
      restrict: 'AE',
      replace: 'true',
      template: '<h3>Hello World!!</h3>'
  };
});
1、表现形式
  • html元素名

    <rabbit-header></rabbit-header>
    
  • 元素的属性

    <div rabbit-header></div>
    
  • 类名

    <div class="rabbit-header"></div>    
    
  • 注释

    <!-- directive: rabbit-header -->
    
2、属性

restrict:

  • A:只限属性使用

  • C:只限类名使用

  • E:只限元素名使用

  • M:只限注释使用

template | templateUrl

  • template: 不一定要是简单的字符串,可以包含其它指令或表达式{} 等

  • templateUrl: 指向模板的一个地址

replace

  • true: 生成的html内容会替换掉定义此指令的html

  • false: 生成的html内容会插入到定义此指令的元素中

3、Link函数和Scope
  • 默认情况下指令不会创建新的子scope。

  • 如果指令存在于一个controller下,它会使用这个contoller的scope。

  • 指令的scope在link函数中使用。

index.html

<body ng-controller="MainCtrl">
  <input type="text" ng-model="color" placeholder="Enter a color" />
  <hello-world></hello-world>
</body>

app.js

app.directive('helloWorld', function() {
  return {
    restrict: 'AE',
    replace: true,
    template: '<p style="background-color:{{color}}">Hello World',
    link: function(scope, elem, attrs) {
      elem.bind('click', function() {
        elem.css('background-color', 'white');
        scope.$apply(function() {
          scope.color = "white";
        });
      });
      elem.bind('mouseover', function() {
        elem.css('cursor', 'pointer');
      });
    }
  };
});

link函数中的三个参数:

  • scope - 在这个例子中,指令的scope就是controller的scope。

  • ele - 指令的jQLite(jQuery的子集)包装DOM元素。如果你在引入AngularJS之前引入了jQuery,那么这个元素就是jQuery元素,而不是jQLite元素。由于这个元素已经被jQuery/jQLite包装了,所以我们就在进行DOM操作的时候就不需要再使用 $()来进行包装。

  • attr - 一个包含了指令所在元素的属性的标准化的参数对象。举个例子,你给一个HTML元素添加了一些属性:,那么可以在 link 函数中通过 attrs.someAttribute 来使用它。

link函数主要用来为DOM元素添加事件监听、监视模型属性变化、以及更新DOM。

在上面的指令代码片段中,我们添加了两个事件, click,和 mouseover。click 处理函数用来重置的背景色,而 mouseover 处理函数改变鼠标为 pointer。在模板中有一个表达式 {color},当父scope中的 color 发生变化时,它用来改变 Hello World 文字的背景色。 这个plunker演示了这些概念

4、compile函数

compile 函数在 link 函数被执行之前用来做一些DOM改造。它接收下面的参数:

  • tElement – 指令所在的元素
  • attrs – 元素上赋予的参数的标准化列表

要注意的是 compile 函数不能访问 scope,并且必须返回一个 link 函数。但是如果没有设置 compile 函数,你可以正常地配置 link 函数,(有了compile,就不能用link,link函数由compile返回)。compile函数可以写成如下的形式:

app.directive('test', function() {
  return {
    compile: function(tElem,attrs) {
      //do optional DOM transformation here
      return function(scope,elem,attrs) {
        //linking function here
      };
    }
  };
});

大多数的情况下,你只需要使用 link 函数。这是因为大部分的指令只需要考虑注册事件监听、监视模型、以及更新DOM等,这些都可以在 link 函数中完成。 但是对于像 ng-repeat 之类的指令,需要克隆和重复 DOM 元素多次,在 link 函数执行之前由 compile 函数来完成。这就带来了一个问题,为什么我们需要两个分开的函数来完成生成过程,为什么不能只使用一个?要回答好这个问题,我们需要理解指令在Angular中是如何被编译的!

5、指令是如何被编译的

1、编译阶段 compile

  • angular应用启动后,$compile服务遍历所有DOM元素。

  • 所有指令都被识别后,angular执行他们的compile方法。

  • compile 方法返回一个 link 函数,被添加到稍后执行的 link 函数列表中。

2、链接阶段 linking

  • 所有收集到的link函数都将被一一执行。

  • 指令创造出来的模板会在正确的scope下被解析和处理,然后返回具有事件响应的真实的DOM节点。

6、改变指令的Scope

默认情况下,指令获取它父节点的controller的scope。

如果将父controller的scope暴露给指令,那么他们可以随意地修改 scope 的属性。

在某些情况下,你的指令希望能够添加一些仅限内部使用的属性和方法。但是如果我们在父的scope中添加,会污染父scope。

所以我们可以选择以下两种方式:

  • 一个子scope – 这个scope原型继承子父scope。

    app.directive('helloWorld', function() {
      return {
        scope: true,  // use a child scope that inherits from parent
        restrict: 'AE',
        replace: 'true',
        template: '<h3>Hello World!!</h3>'
      };
    });
    
  • 一个隔离的scope – 一个孤立存在不继承自父scope的scope。

    app.directive('helloWorld', function() {
      return {
        scope: {},  // use a new isolated scope
        restrict: 'AE',
        replace: 'true',
        template: '<h3>Hello World!!</h3>'
      };
    });
    

这个指令使用了一个隔离的scope。

隔离的scope在我们想要创建可重用的指令的时候是非常有好处的。

通过使用隔离的scope,我们能够保证我们的指令是自包含的,可以被很容易的插入到HTML应用中。

它内部不能访问父的scope,所保证了父scope不被污染。

在我们的 helloWorld 指令例子中,如果我们将 scope 设置成 {},那么上面的代码将不会工作。 它会创建一个新的隔离的scope,那么相应的表达式 { color } 会指向到这个新的scope中,它的值将是 undefined.

使用隔离的scope并不意味着我们完全不能访问父scope的属性。其实有一些技术可以允许我们访问父scope的属性,甚至监视他们的变化。

二、隔离scope与父scope通信

1、隔离scope与父scope之间的数据绑定

假设我们已经初始化完成app这个变量所指向的Angular模块。那么我们的 helloWorld 指令如下面代码所示:

app.directive('helloWorld', function() {
  return {
    scope: {},
    restrict: 'AE',
    replace: true,
    template: '<p style="background-color:{{color}}">Hello World</p>',
    link: function(scope, elem, attrs) {
      elem.bind('click', function() {
        elem.css('background-color','white');
        scope.$apply(function() {
          scope.color = "white";
        });
      });
      elem.bind('mouseover', function() {
        elem.css('cursor', 'pointer');
      });
    }
  };
});

使用这个指令的HTML标签如下:

<body ng-controller="MainCtrl">
  <input type="text" ng-model="color" placeholder="Enter a color"/>
  <hello-world/>
</body>

上面的代码现在是不能工作的。因为我们用了一个隔离的scope,指令内部的 {color} 表达式被隔离在指令内部的scope中(不是父scope)。

但是外面的输入框元素中的 ng-model 指令是指向父scope中的 color 属性的。

所以,我们需要一种方式来绑定隔离scope和父scope中的这两个参数。

在Angular中,这种数据绑定可以通过为指令所在的HTML元素添加属性和在指令定义对象中配置相应的 scope 属性来实现。

让我们来细究一下建立数据绑定的几种方式。

选择一:使用 @ 实现单向文本绑定

在下面的指令定义中,我们指定了隔离scope中的属性 color 绑定到指令所在HTML元素上的参数 colorAttr。

在HTML标记中,你可以看到 {color}表达式被指定给了 color-attr 参数。当表达式的值发生改变时,color-attr 参数也跟着改变。隔离scope中的 color 属性的值也相应地被改变。

app.directive('helloWorld', function() {
  return {
    scope: {
      color: '@colorAttr'
    },
    ....
    // the rest of the configurations
  };
});

更新后的HTML标记代码如下:

<body ng-controller="MainCtrl">
  <input type="text" ng-model="color" placeholder="Enter a color"/>
  <hello-world color-attr="{{color}}"/>
</body>

我们称这种方式为单项绑定,是因为在这种方式下,你只能将字符串(使用表达式)传递给参数。

当父scope的属性变化时,你的隔离scope模型中的属性值跟着变化。你甚至可以在指令内部监控这个scope属性的变化,并且触发一些任务。

然而,反向的传递并不工作。你不能通过对隔离scope属性的操作来改变父scope的值。

注意点:
当隔离scope属性和指令元素参数的名字一样时,你可以以更简单的方式设置scope绑定:

app.directive('helloWorld', function() {
  return {
    scope: {
      color: '@'
    },
    ....
    // the rest of the configurations
  };
});

相应使用指令的HTML代码如下:

选择二:使用 = 实现双向绑定

让我们将指令的定义改变成下面的样子:

app.directive('helloWorld', function() {
  return {
    scope: {
      color: '='
    },
    ....
    // the rest of the configurations
  };
});

相应的HTML修改如下:

<body ng-controller="MainCtrl">
  <input type="text" ng-model="color" placeholder="Enter a color"/>
  <hello-world color="color"/>
</body>

与 @ 不同,这种方式让你能够给属性指定一个真实的scope数据模型,而不是简单的字符串。

这样你就可以传递简单的字符串、数组、甚至复杂的对象给隔离scope。

同时,还支持双向的绑定。每当父scope属性变化时,相对应的隔离scope中的属性也跟着改变,反之亦然。

和之前的一样,你也可以监视这个scope属性的变化。

选择三:使用 & 在父scope中执行函数

有时候从隔离scope中调用父scope中定义的函数是非常有必要的。为了能够访问外部scope中定义的函数,我们使用 &。

比如我们想要从指令内部调用 changColor() 方法。下面的代码告诉我们该怎么做:

app.directive('helloWorld',function(){
  return{
    scope:{
      color:'=',
      changeColor:'&',
    },
    ...
  }
});

相应的HTML代码如下:

<body ng-controller="MainCtrl">
    <input type="text" ng-model="color" placeholder="Enter a color"/>
    <hello-world color="color" change-color='change()'/>
</body>

这个 Plunker 例子对上面的概念做了很好的诠释。

2、父scope、子scope以及隔离scope的区别

下面的这个原则也许可以帮助你为你的指令选择正确的scope。

  • 父scope(scope: false) – 这是默认情况。如果你的指令不操作父scoe的属性,你就不需要一个新的scope。这种情况下是可以使用父scope的。

  • 子scope(scope: true) – 这会为指令创建一个新的scope,并且原型继承自父scope。如果你的指令scope中的属性和方法与其他的指令以及父scope都没有关系的时候,你应该创建一个新scope。在这种方式下,你同样拥有父scope中所定义的属性和方法。

  • 隔离scope(scope:{}) – 这就像一个沙箱!当你创建的指令是自包含的并且可重用的,你就需要使用这种scope。你在指令中会创建很多scope属性和方法,它们仅在指令内部使用,永远不会被外部的世界所知晓。如果是这样的话,隔离的scope是更好的选择。隔离的scope不会继承父scope。

三、Transclusion(嵌入)

Transclusion可以让我们的指令包含任意内容的方法。

如果你在指令定义中设置 transclude:true,一个新的嵌入的scope会被创建,它原型继承自父scope。

如果你想要你的指令使用隔离的scope,但是它所包含的内容能够在父scope中执行,transclusion也可以帮忙。

1、demo

假设我们注册一个如下的指令:

app.directive('outputText', function() {
  return {
    transclude: true,
    scope: {},
    template: '<div ng-transclude></div>'
  };
});

它使用如下:

<div output-text>
  <p>Hello {{name}}</p>
</div>

在这个例子中DOM内容

Hello {name}

被提取和放置到
内部。

表达式{name}所对应的属性是在父scope中被定义的,而非子scope。

你可以在这个 Plunker 例子中做一些实验。

2、transclude:’element’ 和 transclude:true的区别

有时候我我们要嵌入指令元素本身,而不仅仅是它的内容。在这种情况下,我们需要使用 transclude:’element’。

它和 transclude:true 不同,它将标记了 ng-transclude 指令的元素一起包含到了指令模板中。

使用transclusion,你的link函数会获得一个名叫 transclude 的链接函数,这个函数绑定了正确的指令scope,并且传入了另一个拥有被嵌入DOM元素拷贝的函数。

你可以在这个 transclude 函数中执行比如修改元素拷贝或者将它添加到DOM上等操作。 类似 ng-repeat 这样的指令使用这种方式来重复DOM元素。

仔细研究一下这个Plunker,它使用这种方式复制了DOM元素,并且改变了第二个实例的背景色。

同样需要注意的是,在使用 transclude:’element’的时候,指令所在的元素会被转换成HTML注释。

所以,如果你结合使用 transclude:’element’ 和 replace:false,那么指令模板本质上是被添加到了注释的innerHTML中——也就是说其实什么都没有发生!

相反,如果你选择使用 replace:true,指令模板会替换HTML注释,那么一切就会如果所愿的工作。

使用 replade:false 和 transclue:’element’有时候也是有用的,比如当你需要重复DOM元素但是并不想保留第一个元素实例(它会被转换成注释)的情况下。对这块还有疑惑的同学可以阅读stackoverflow上的这篇讨论,介绍的比较清晰。

四、controller 函数和 require

如果想让指令与指令交互,就要用到controller函数。比如有些情况下,你需要通过组合两个指令来实现一个UI组件。那么你可以通过如下的方式来给指令添加一个 controller 函数。

app.directive('outerDirective', function() {
  return {
    scope: {},
    restrict: 'AE',
    controller: function($scope, $compile, $http) {
      // $scope is the appropriate scope for the directive
      this.addChild = function(nestedDirective) { // this refers to the controller
        console.log('Got the message from nested directive:' + nestedDirective.message);
      };
    }
  };
});

这个代码为一个名叫 outerDirective的指令添加了controller。

当另一个指令 innerDirective 想要与指令outerDirective交互时,innterDirective 需要声明对 outerDirective 的 controller 实例的引用(require)。

可以通过如下的方式实现:

app.directive('innerDirective', function() {
  return {
    scope: {},
    restrict: 'AE',
    require: '^outerDirective',
    link: function(scope, elem, attrs, controllerInstance) {
      //the fourth argument is the controller instance you require
      scope.message = "Hi, Parent directive";
      controllerInstance.addChild(scope);
    }
  };
});

相应的HTML代码如下:

<outer-directive>
  <inner-directive></inner-directive>
</outer-directive>

require: ‘^outerDirective’ 告诉Angular在元素以及它的父元素outerDirective中搜索controller。

这样被找到的 controller 实例会作为第四个参数controllerInstance被传入到 link 函数中。

在我们的例子中,我们将嵌入的指令的scope发送给父亲指令。

如果你想尝试这个代码的话,请在开启浏览器控制台的情况下打开这个Plunker

同时,这篇Angular官方文档上的最后部分给了一个非常好的关于指令交互的例子,是非常值得一读的。

五、一个记事本的应用

这一部分,我们使用Angular指令创建一个简单的记事本应用。我们会使用HTML5的 localStorage 来存储笔记。

我们会创建一个展现记事本的指令。

用户可以查看他/她创建过的笔记记录。当他点击 add new 按钮的时候,记事本会进入可编辑状态,并且允许创建新的笔记。当点击 back 按钮的时候,新的笔记会被自动保存。笔记的保存使用了一个名叫 noteFactory 的工厂类,它使用了 localStorage。

你可以从GitHub上下到这个Demo的源代码。

六、总结

一个很重要的点需要注意的是,任何使用jQuery能做的事情,我们都能用Angular指令来做到,并且使用更少的代码。

所以,在使用jQuery之前,请考虑一下我们能否在不进行DOM操作的情况下以更好的方式来完成任务。试着使用Angular来最小化jQuery的使用吧。

angular应用中常见问题记录

1、问题:Template for directive ‘rabbitFooter’ must have exactly one root element

控制台报错:angular.js?bust=1463908350577:13550 Error: [$compile:tplrt] Template for directive ‘rabbitFooter’ must have exactly one root element. ../../../template/tplIndex/rabbitFooter.html

指令rabbitFooter对应的模板:

<!--s footer-->
<div class="footerbox">
    <div class="footercon">
        ...
    </div>
    <div class="copybox">
        ...
    </div>
    <!--
    <div class="authpics">
        ...
    </div>-->
</div>
<!--e footer-->

解决方案:保证模板的所有代码要有一个最外层的DIV,最外层注释也不要有。

<div class="footerbox">
    <div class="footercon">
        ...
    </div>
    <div class="copybox">
        ...
    </div>
    <!--
    <div class="authpics">
        ...
    </div>
    -->
</div>

React/React Native的ES5 ES6写法对照表

摘抄:React/React Native 的ES5 ES6写法对照表

###一、模块

#####1、引用

在ES5里,如果使用CommonJS标准,引入React包基本通过require进行,代码类似这样:

//ES5
var React = require("react-native");
var {
    Image,
    Text,
    PropTypes
} = React;  //引用不同的React Native组件

在ES6里,import写法更为标准

//ES6
import React, {
    Image, 
    Text,
    PropTypes
} from 'react-native';

#####2、导出单个类

在ES5里,要导出一个类给别的模块用,一般通过module.exports来导出

//ES5
var MyComponent = React.createClass({
    ...
});
module.exports = MyComponent;

在ES6里,通常用export default来实现相同的功能:

//ES6
export default class MyComponent extends React.Component{
    ...
}

引用的时候也类似:

//ES5
var MyComponent = require('./MyComponent.js');

//ES6
import MyComponent from './MyComponent.js';

###二、组件

#####1、定义组件

在ES5里,通常通过React.createClass来定义一个组件类,像这样:

//ES5
var Photo = React.createClass({
    render: function() {
        return (
            <Image source={this.props.source} />
        );
    },
});

在ES6里,我们通过定义一个继承自React.Component的class来定义一个组件类,像这样:

//ES6
class Photo extends React.Component {
    render() {
        return (
            <Image source={this.props.source} />
        );
    }
}    

#####2、给组件定义方法

从上面的例子里可以看到,给组件定义方法不再用 名字: function()的写法,而是直接用名字(),在方法的最后也不能有逗号了。

//ES5

var Photo = React.createClass({
    componentWillMount: function(){

    },
    render: function() {
        return (
            <Image source={this.props.source} />
        );
    },
});

//ES6

class Photo extends React.Component {
componentWillMount() {

}
render() {
    return (
        <Image source={this.props.source} />
    );
}

}

#####3、定义组件的属性类型和默认属性

在ES5里,属性类型和默认属性分别通过propTypes成员和getDefaultProps方法来实现

//ES5 
var Video = React.createClass({
    getDefaultProps: function() {
        return {
            autoPlay: false,
            maxLoops: 10,
        };
    },
    propTypes: {
        autoPlay: React.PropTypes.bool.isRequired,
        maxLoops: React.PropTypes.number.isRequired,
        posterFrameSrc: React.PropTypes.string.isRequired,
        videoSrc: React.PropTypes.string.isRequired,
    },
    render: function() {
        return (
            <View />
        );
    },
});

在ES6里,可以统一使用static成员来实现

//ES6
class Video extends React.Component {
    static defaultProps = {
        autoPlay: false,
        maxLoops: 10,
    };  // 注意这里有分号
    static propTypes = {
        autoPlay: React.PropTypes.bool.isRequired,
        maxLoops: React.PropTypes.number.isRequired,
        posterFrameSrc: React.PropTypes.string.isRequired,
        videoSrc: React.PropTypes.string.isRequired,
    };  // 注意这里有分号
    render() {
        return (
            <View />
        );
    } // 注意这里既没有分号也没有逗号
}

也有人这么写,虽然不推荐,但读到代码的时候你应当能明白它的意思:

//ES6
class Video extends React.Component {
    render() {
        return (
            <View />
        );
    }
}
Video.defaultProps = {
    autoPlay: false,
    maxLoops: 10,
};
Video.propTypes = {
    autoPlay: React.PropTypes.bool.isRequired,
    maxLoops: React.PropTypes.number.isRequired,
    posterFrameSrc: React.PropTypes.string.isRequired,
    videoSrc: React.PropTypes.string.isRequired,
};

注意: 对React开发者而言,static成员在IE10及之前版本不能被继承,而在IE11和其它浏览器上可以,这有时候会带来一些问题。React Native开发者可以不用担心这个问题。

#####4、初始化state

ES5下情况类似,

//ES5 
var Video = React.createClass({
    getInitialState: function() {
        return {
            loopsRemaining: this.props.maxLoops,
        };
    },
})

ES6下,有两种写法:

//ES6
class Video extends React.Component {
    state = {
        loopsRemaining: this.props.maxLoops,
    }
}

不过我们推荐更易理解的在构造函数中初始化(这样你还可以根据需要做一些计算):

//ES6
class Video extends React.Component {
    constructor(props){
        super(props);
        this.state = {
            loopsRemaining: this.props.maxLoops,
        };
    }
}

###三、把方法作为回调提供

#####1、写法

在ES5下,React.createClass会把所有的方法都bind一遍,这样可以提交到任意的地方作为回调函数,而this不会变化。但官方现在逐步认为这反而是不标准、不易理解的。

//ES5
var PostInfo = React.createClass({
    handleOptionsButtonClick: function(e) {
        // Here, 'this' refers to the component instance.
        this.setState({showOptionsModal: true});
    },
    render: function(){
        return (
            <TouchableHighlight onPress={this.handleOptionsButtonClick}>
                <Text>{this.props.label}</Text>
            </TouchableHighlight>
        )
    },
});

在ES6下,你需要通过bind来绑定this引用,或者使用箭头函数(它会绑定当前scope的this引用)来调用。

//ES6
class PostInfo extends React.Component
{
    handleOptionsButtonClick(e){
        this.setState({showOptionsModal: true});
    }
    render(){
        return (
            <TouchableHighlight 
                onPress={this.handleOptionsButtonClick.bind(this)}
                onPress={e=>this.handleOptionsButtonClick(e)}
                >
                <Text>{this.props.label}</Text>
            </TouchableHighlight>
        )
    },
}

#####2、箭头函数

箭头函数实际上是在这里定义了一个临时的函数,箭头函数的箭头=>之前是一个空括号、单个的参数名、或用括号括起的多个参数名,而箭头之后可以是一个表达式(作为函数的返回值),或者是用花括号括起的函数体(需要自行通过return来返回值,否则返回的是undefined)。

// 箭头函数的例子
()=>1
v=>v+1
(a,b)=>a+b
()=>{
    alert("foo");
}
e=>{
    if (e == 0){
        return 0;
    }
    return 1000/e;
}

需要注意的是,不论是bind还是箭头函数,每次被执行都返回的是一个新的函数引用,因此如果你还需要函数的引用去做一些别的事情(譬如卸载监听器),那么你必须自己保存这个引用。

//错误的做法

class PauseMenu extends React.Component{
    componentWillMount(){
        AppStateIOS.addEventListener('change', this.onAppPaused.bind(this));
    }
    componentDidUnmount(){
        AppStateIOS.removeEventListener('change', this.onAppPaused.bind(this));
    }
    onAppPaused(event){
    }
}

//正确的做法

class PauseMenu extends React.Component{
    constructor(props){
        super(props);
        this._onAppPaused = this.onAppPaused.bind(this);
    }
    componentWillMount(){
        AppStateIOS.addEventListener('change', this._onAppPaused);
    }
    componentDidUnmount(){
        AppStateIOS.removeEventListener('change', this._onAppPaused);
    }
    onAppPaused(event){
    }
}

从这个帖子中我们还学习到一种新的做法:

// 正确的做法

class PauseMenu extends React.Component{
    componentWillMount(){
        AppStateIOS.addEventListener('change', this.onAppPaused);
    }
    componentDidUnmount(){
        AppStateIOS.removeEventListener('change', this.onAppPaused);
    }
    onAppPaused = (event) => {
        //把方法直接作为一个arrow function的属性来定义,初始化的时候就绑定好了this指针
    }
}

###四、在es6中Mixins不再推荐使用

在ES5下,我们经常使用mixin来为我们的类添加一些新的方法,譬如PureRenderMixin

var PureRenderMixin = require('react-addons-pure-render-mixin');
React.createClass({
  mixins: [PureRenderMixin],

  render: function() {
    return <div className={this.props.className}>foo</div>;
  }
});

然而现在官方已经不再打算在ES6里继续推行Mixin。

对于库编写者而言,应当尽快放弃Mixin的编写方式,推荐一种新的编码方式。

//Enhance.js
import { Component } from "React";

export var Enhance = ComposedComponent => class extends Component {
    constructor() {
        this.state = { data: null };
    }
    componentDidMount() {
        this.setState({ data: 'Hello' });
    }
    render() {
        return <ComposedComponent {...this.props} data={this.state.data} />;
    }
};

//HigherOrderComponent.js
import { Enhance } from "./Enhance";

class MyComponent {
    render() {
        if (!this.data) return <div>Waiting...</div>;
        return <div>{this.data}</div>;
    }
}

export default Enhance(MyComponent); // Enhanced component

用一个“增强函数”,来为某个类增加一些方法,并且返回一个新类,这无疑能实现mixin所实现的大部分需求。

###五、es6+带来的其它好处

解构&属性延展

结合使用ES6+的解构和属性延展,我们给孩子传递一批属性更为方便。

这个例子把className以外的所有属性传递给了div标签:

class AutoloadingPostsGrid extends React.Component {
    render() {
        var {
            className,
            ...others,  // contains all properties of this.props except for className
        } = this.props;
        return (
            <div className={className}>
                <PostsGrid {...others} />
                <button onClick={this.handleLoadMoreClick}>Load more</button>
            </div>
        );
    }
}

下面这种写法,则是传递所有属性的同时,用override来覆盖属性中的className值:

<div {...this.props} className="override">
    …
</div>

这个例子则相反,如果属性中没有包含className,则提供默认的值,而如果属性中已经包含了,则使用属性中的值

<div className="base" {...this.props}>
    …
</div>    

github+hexo博客并备份

一、hexo

1、官方网址:https://hexo.io/

2、常用命令

npm install hexo -g #安装  
npm update hexo -g #升级  
hexo init #初始化

3、简写

hexo n "我的博客" == hexo new "我的博客" #新建文章
hexo p == hexo publish #草稿
hexo g == hexo generate #生成
hexo s == hexo server #启动服务预览
hexo d == hexo deploy #部署

4、布署

修改_config.yml

# Deployment
## Docs: http://hexo.io/docs/deployment.html
deploy:
      type: git
      repository: git@***.github.com:***/***.github.io.git
      branch: master

二、Github

1、摘抄

常用 Git 命令清单

Git远程操作详解

MacDown Screenshot

  • Workspace:工作区
  • Index / Stage:暂存区
  • Repository:仓库区(或本地仓库)
  • Remote:远程仓库

2、常用命令

a、新建本地仓库

# 在当前目录新建一个Git代码库
$ git init

# 新建一个目录,将其初始化为Git代码库
$ git init [project-name]

# 下载一个项目和它的整个代码历史
$ git clone [url]    

b、增加/删除文件

# 添加指定文件到暂存区
$ git add [file1] [file2] ...

# 添加指定目录到暂存区,包括子目录
$ git add [dir]

# 添加当前目录的所有文件到暂存区
$ git add .

# 删除工作区文件,并且将这次删除放入暂存区
$ git rm [file1] [file2] ...

# 改名文件,并且将这个改名放入暂存区
$ git mv [file-original] [file-renamed]

c、代码提交

# 提交暂存区到仓库区
$ git commit -m [message]

# 提交暂存区的指定文件到仓库区
$ git commit [file1] [file2] ... -m [message]

# 提交时显示所有diff信息
$ git commit -v

d、分支

# 列出所有本地分支
$ git branch

# 列出所有远程分支
$ git branch -r

# 列出所有本地分支和远程分支
$ git branch -a

# 新建一个分支,但依然停留在当前分支
$ git branch [branch-name]

# 新建一个分支,并切换到该分支
$ git checkout -b [branch]

# 新建一个分支,与指定的远程分支建立追踪关系
$ git branch --track [branch] [remote-branch]

# 切换到指定分支,并更新工作区
$ git checkout [branch-name]

# 切换到上一个分支
$ git checkout -

# 建立追踪关系,在现有分支与指定的远程分支之间
$ git branch --set-upstream [branch] [remote-branch]

# 合并指定分支到当前分支
$ git merge [branch]

# 删除分支
$ git branch -d [branch-name]

# 删除远程分支
$ git push origin --delete [branch-name]
$ git branch -dr [remote/branch]    

e、查看信息

# 显示有变更的文件
$ git status

# 显示当前分支的版本历史
$ git log

# 显示暂存区和工作区的差异
$ git diff

f、远程同步

# 下载远程仓库的所有变动
$ git fetch [remote]

# 显示所有远程仓库
$ git remote -v

# 显示某个远程仓库的信息
$ git remote show [remote]

# 上传本地指定分支到远程仓库
$ git push [remote] [branch]

# 强行推送当前分支到远程仓库,即使有冲突
$ git push [remote] --force

# 推送所有分支到远程仓库
$ git push [remote] --all

g、撤销

# 恢复暂存区的指定文件到工作区
$ git checkout [file]

# 恢复某个commit的指定文件到暂存区和工作区
$ git checkout [commit] [file]

# 恢复暂存区的所有文件到工作区
$ git checkout .

# 重置暂存区的指定文件,与上一次commit保持一致,但工作区不变
$ git reset [file]

# 重置暂存区与工作区,与上一次commit保持一致
$ git reset --hard

3、Git远程操作详解

a、git clone

#从远程主机克隆一个版本库
$ git clone <版本库的网址>    

b、git remote

#列出所有的主机
$ git remote
origin

#查看远程主机的网址
$ git remote -v
origin  git@github.com:jquery/jquery.git (fetch)
origin  git@github.com:jquery/jquery.git (push)

#克隆版本库的时候,所使用的远程主机自动被Git命名为origin
$ git remote show <主机名>

#git remote add命令用于添加远程主机。
$ git remote add <主机名> <网址>

#git remote rm命令用于删除远程主机
$ git remote rm <主机名>

#git remote rename命令用于远程主机的改名。
$ git remote rename <原主机名> <新主机名>

c、git fetch

git fetch 取回到本地的是远程主机版本库的更新

git fetch命令通常用来查看其他人的进程,因为它取回的代码对你本地的开发代码没有影响。

默认情况下,git fetch取回所有分支(branch)的更新。如果只想取回特定分支的更新,可以指定分支名。

$ git fetch <远程主机名> <分支名>

比如,取回origin主机的master分支。

$ git fetch origin master

d、git pull

git pull命令的作用是,取回远程主机某个分支的更新,再与本地的指定分支合并。它的完整格式稍稍有点复杂。

$ git pull <远程主机名> <远程分支名>:<本地分支名>

比如,取回origin主机的next分支,与本地的master分支合并,需要写成下面这样

$ git pull origin next:master

如果远程分支是与当前分支合并,则冒号后面的部分可以省略。

$ git pull origin next

上面命令表示,取回origin/next分支,再与当前分支合并。实质上,这等同于先做git fetch,再做git merge。

$ git fetch origin
$ git merge origin/next

在某些场合,Git会自动在本地分支与远程分支之间,建立一种追踪关系(tracking)。比如,在git clone的时候,所有本地分支默认与远程主机的同名分支,建立追踪关系,也就是说,本地的master分支自动”追踪”origin/master分支。

Git也允许手动建立追踪关系。

git branch --set-upstream master origin/next

上面命令指定master分支追踪origin/next分支。

e、git push

git push命令用于将本地分支的更新,推送到远程主机。它的格式与git pull命令相仿。

$ git push <远程主机名> <本地分支名>:<远程分支名>

如果省略远程分支名,则表示将本地分支推送与之存在”追踪关系”的远程分支(通常两者同名),如果该远程分支不存在,则会被新建。

$ git push origin master

上面命令表示,将本地的master分支推送到origin主机的master分支。如果后者不存在,则会被新建。

如果省略本地分支名,则表示删除指定的远程分支,因为这等同于推送一个空的本地分支到远程分支。

$ git push origin :master
# 等同于
$ git push origin --delete master

上面命令表示删除origin主机的master分支。

三、hexo多个终端同步

1、在其中一个终端操作,push本地文件夹Hexo中的必要文件到yourname.github.io的hexo分支上

//初始化本地仓库
git init  

//将本地与Github项目对接
git remote add origin git@github.com:yourname/yourname.github.io.git

//将必要的文件依次添加,有些文件夹如npm install产生的node_modules由于路径过长不好处理,所以这里没有用`git add .`命令了,而是依次添加必要文件,如下图所示
git add source scaffolds themes .npmignore _config.yml package.json
git commit -m "Blog Source Hexo"

//新建hexo分支
git branch hexo 

//切换到hexo分支上
git checkout hexo   

//push到Github项目的hexo分支上
git push origin hexo   

2、在另一终端完成clone和push更新

//将Github中hexo分支clone到本地
git clone -b hexo git@github.com:yourname/yourname.github.io.git  

//切换到刚刚clone的文件夹内
cd  yourname.github.io

//注意,这里一定要切换到刚刚clone的文件夹内执行,安装必要的所需组件,不用再init  
npm install    

//新建一个.md文件,并编辑完成自己的博客内容
hexo new post "new blog name"   

//经测试每次只要更新source中的文件到Github中即可,因为只是新建了一篇新博客
git add source 

git commit -m "XX"

//更新分支
git push origin hexo  

//push更新完分支之后将自己写的博客对接到自己搭的博客网站上,同时同步了Github中的master
hexo d -g

3、不同终端更新博客

//先pull完成本地与远端的融合
git pull origin hexo  

hexo new post " new blog name"

git add source

git commit -m "XX"

git push origin hexo

hexo d -g