您的位置:奥门新浦京网址 > Wed前段 > H5直播起航,的黑魔法

H5直播起航,的黑魔法

发布时间:2019-12-15 01:51编辑:Wed前段浏览(121)

    H5直播起航

    2016/10/31 · HTML5 · 开发

    原文出处: 凹凸实验室   

    图片 1

    深入理解Javascript面向对象编程

    2015/12/23 · JavaScript · 1 评论 · 面向对象

    原文出处: 涂根华   

    一:理解构造函数原型(prototype)机制

    prototype是javascript实现与管理继承的一种机制,也是面向对象的设计思想.构造函数的原型存储着引用对象的一个指针,该指针指向与一个原型对象,对象内部存储着函数的原始属性和方法;我们可以借助prototype属性,可以访问原型内部的属性和方法。

    当构造函数被实列化后,所有的实例对象都可以访问构造函数的原型成员,如果在原型中声明一个成员,所有的实列方法都可以共享它,比如如下代码:

    JavaScript

    // 构造函数A 它的原型有一个getName方法 function A(name){ this.name = name; } A.prototype.getName = function(){ return this.name; } // 实列化2次后 该2个实列都有原型getName方法;如下代码 var instance1 = new A("longen1"); var instance2 = new A("longen2"); console.log(instance1.getName()); //longen1 console.log(instance2.getName()); // longen2

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    // 构造函数A 它的原型有一个getName方法
    function A(name){
        this.name = name;
    }
    A.prototype.getName = function(){
        return this.name;
    }
    // 实列化2次后 该2个实列都有原型getName方法;如下代码
    var instance1 = new A("longen1");
    var instance2 = new A("longen2");
    console.log(instance1.getName()); //longen1
    console.log(instance2.getName()); // longen2

    原型具有普通对象结构,可以将任何普通对象设置为原型对象; 一般情况下,对象都继承与Object,也可以理解Object是所有对象的超类,Object是没有原型的,而构造函数拥有原型,因此实列化的对象也是Object的实列,如下代码:

    JavaScript

    // 实列化对象是构造函数的实列 console.log(instance1 instanceof A); //true console.log(instance2 instanceof A); // true // 实列化对象也是Object的实列 console.log(instance1 instanceof Object); //true console.log(instance2 instanceof Object); //true //Object 对象是所有对象的超类,因此构造函数也是Object的实列 console.log(A instanceof Object); // true // 但是实列化对象 不是Function对象的实列 如下代码 console.log(instance1 instanceof Function); // false console.log(instance2 instanceof Function); // false // 但是Object与Function有关系 如下代码说明 console.log(Function instanceof Object); // true console.log(Object instanceof Function); // true

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    // 实列化对象是构造函数的实列
    console.log(instance1 instanceof A); //true
    console.log(instance2 instanceof A); // true
     
    // 实列化对象也是Object的实列
    console.log(instance1 instanceof Object); //true
    console.log(instance2 instanceof Object); //true
     
    //Object 对象是所有对象的超类,因此构造函数也是Object的实列
    console.log(A instanceof Object); // true
     
    // 但是实列化对象 不是Function对象的实列 如下代码
    console.log(instance1 instanceof Function); // false
    console.log(instance2 instanceof Function); // false
     
    // 但是Object与Function有关系 如下代码说明
    console.log(Function instanceof Object);  // true
    console.log(Object instanceof Function);  // true

    如上代码,Function是Object的实列,也可以是Object也是Function的实列;他们是2个不同的构造器,我们继续看如下代码:

    JavaScript

    var f = new Function(); var o = new Object(); console.log("------------"); console.log(f instanceof Function); //true console.log(o instanceof Function); // false console.log(f instanceof Object); // true console.log(o instanceof Object); // true

    1
    2
    3
    4
    5
    6
    7
    var f = new Function();
    var o = new Object();
    console.log("------------");
    console.log(f instanceof Function);  //true
    console.log(o instanceof Function);  // false
    console.log(f instanceof Object);    // true
    console.log(o instanceof Object);   // true

    我们明白,在原型上增加成员属性或者方法的话,它被所有的实列化对象所共享属性和方法,但是如果实列化对象有和原型相同的成员成员名字的话,那么它取到的成员是本实列化对象,如果本实列对象中没有的话,那么它会到原型中去查找该成员,如果原型找到就返回,否则的会返回undefined,如下代码测试

    JavaScript

    function B(){ this.name = "longen2"; } B.prototype.name = "AA"; B.prototype.getName = function(){ return this.name; }; var b1 = new B(); // 在本实列查找,找到就返回,否则到原型查找 console.log(b1.name); // longen2 // 在本实列没有找到该方法,就到原型去查找 console.log(b1.getName());//longen2 // 如果在本实列没有找到的话,到原型上查找也没有找到的话,就返回undefined console.log(b1.a); // undefined // 现在我使用delete运算符删除本地实列属性,那么取到的是就是原型属性了,如下代码: delete b1.name; console.log(b1.name); // AA

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    function B(){
        this.name = "longen2";
    }
    B.prototype.name = "AA";
    B.prototype.getName = function(){
        return this.name;
    };
     
    var b1 = new B();
    // 在本实列查找,找到就返回,否则到原型查找
    console.log(b1.name); // longen2
     
    // 在本实列没有找到该方法,就到原型去查找
    console.log(b1.getName());//longen2
     
    // 如果在本实列没有找到的话,到原型上查找也没有找到的话,就返回undefined
    console.log(b1.a); // undefined
     
    // 现在我使用delete运算符删除本地实列属性,那么取到的是就是原型属性了,如下代码:
    delete b1.name;
    console.log(b1.name); // AA

    二:理解原型域链的概念

    原型的优点是能够以对象结构为载体,创建大量的实列,这些实列能共享原型中的成员(属性和方法);同时也可以使用原型实现面向对象中的继承机制~ 如下代码:下面我们来看这个构造函数AA和构造函数BB,当BB.prototype = new AA(11);执行这个的时候,那么B就继承与A,B中的原型就有x的属性值为11

    JavaScript

    function AA(x){ this.x = x; } function BB(x) { this.x = x; } BB.prototype = new AA(11); console.log(BB.prototype.x); //11 // 我们再来理解原型继承和原型链的概念,代码如下,都有注释 function A(x) { this.x = x; } // 在A的原型上定义一个属性x = 0 A.prototype.x = 0; function B(x) { this.x = x; } B.prototype = new A(1);

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    function AA(x){
        this.x = x;
    }
    function BB(x) {
        this.x = x;
    }
    BB.prototype = new AA(11);
    console.log(BB.prototype.x); //11
     
    // 我们再来理解原型继承和原型链的概念,代码如下,都有注释
    function A(x) {
        this.x = x;
    }
    // 在A的原型上定义一个属性x = 0
    A.prototype.x = 0;
    function B(x) {
        this.x = x;
    }
    B.prototype = new A(1);

    实列化A new A(1)的时候 在A函数内this.x =1, B.prototype = new A(1);B.prototype 是A的实列 也就是B继承于A, 即B.prototype.x = 1;  如下代码:

    JavaScript

    console.log(B.prototype.x); // 1 // 定义C的构造函数 function C(x) { this.x = x; } C.prototype = new B(2);

    1
    2
    3
    4
    5
    6
    console.log(B.prototype.x); // 1
    // 定义C的构造函数
    function C(x) {
        this.x = x;
    }
    C.prototype = new B(2);

    C.prototype = new B(2); 也就是C.prototype 是B的实列,C继承于B;那么new B(2)的时候 在B的构造函数内 this.x = 2;那么 C的原型上会有一个属性x =2 即C.prototype.x = 2; 如下代码:

    JavaScript

    console.log(C.prototype.x); // 2

    1
    console.log(C.prototype.x); // 2

    下面是实列化 var d = new C(3); 实列化C的构造函数时候,那么在C的构造函数内this.x = 3; 因此如下打印实列化后的d.x = 3;如下代码:

    JavaScript

    var d = new C(3); console.log(d.x); // 3

    1
    2
    var d = new C(3);
    console.log(d.x); // 3

    删除d.x 再访问d.x的时候 本实列对象被删掉,只能从原型上去查找;由于C.prototype = new B(2); 也就是C继承于B,因此C的原型也有x = 2;即C.prototype.x = 2; 如下代码:

    JavaScript

    delete d.x; console.log(d.x); //2

    1
    2
    delete d.x;
    console.log(d.x);  //2

    删除C.prototype.x后,我们从上面代码知道,C是继承于B的,自身的原型被删掉后,会去查找父元素的原型链,因此在B的原型上找到x =1; 如下代码:

    JavaScript

    delete C.prototype.x; console.log(d.x); // 1

    1
    2
    delete C.prototype.x;
    console.log(d.x);  // 1

    当删除B的原型属性x后,由于B是继承于A的,因此会从父元素的原型链上查找A原型上是否有x的属性,如果有的话,就返回,否则看A是否有继承,没有继承的话,继续往Object上去查找,如果没有找到就返回undefined 因此当删除B的原型x后,delete B.prototype.x; 打印出A上的原型x=0; 如下代码:

    JavaScript

    delete B.prototype.x; console.log(d.x); // 0 // 继续删除A的原型x后 结果没有找到,就返回undefined了; delete A.prototype.x; console.log(d.x); // undefined

    1
    2
    3
    4
    5
    6
    delete B.prototype.x;
    console.log(d.x);  // 0
     
    // 继续删除A的原型x后 结果没有找到,就返回undefined了;
    delete A.prototype.x;
    console.log(d.x);  // undefined

    在javascript中,一切都是对象,Function和Object都是函数的实列;构造函数的父原型指向于Function原型,Function.prototype的父原型指向与Object的原型,Object的父原型也指向与Function原型,Object.prototype是所有原型的顶层;

    如下代码:

    JavaScript

    Function.prototype.a = function(){ console.log("我是父原型Function"); } Object.prototype.a = function(){ console.log("我是 父原型Object"); } function A(){ this.a = "a"; } A.prototype = { B: function(){ console.log("b"); } } // Function 和 Object都是函数的实列 如下: console.log(A instanceof Function); // true console.log(A instanceof Object); // true // A.prototype是一个对象,它是Object的实列,但不是Function的实列 console.log(A.prototype instanceof Function); // false console.log(A.prototype instanceof Object); // true // Function是Object的实列 同是Object也是Function的实列 console.log(Function instanceof Object); // true console.log(Object instanceof Function); // true /* * Function.prototype是Object的实列 但是Object.prototype不是Function的实列 * 说明Object.prototype是所有父原型的顶层 */ console.log(Function.prototype instanceof Object); //true console.log(Object.prototype instanceof Function); // false

    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
    Function.prototype.a = function(){
        console.log("我是父原型Function");
    }
    Object.prototype.a = function(){
        console.log("我是 父原型Object");
    }
    function A(){
        this.a = "a";
    }
    A.prototype = {
        B: function(){
            console.log("b");
        }
    }
    // Function 和 Object都是函数的实列 如下:
    console.log(A instanceof Function);  // true
    console.log(A instanceof Object); // true
     
    // A.prototype是一个对象,它是Object的实列,但不是Function的实列
    console.log(A.prototype instanceof Function); // false
    console.log(A.prototype instanceof Object); // true
     
    // Function是Object的实列 同是Object也是Function的实列
    console.log(Function instanceof Object);   // true
    console.log(Object instanceof Function); // true
     
    /*
    * Function.prototype是Object的实列 但是Object.prototype不是Function的实列
    * 说明Object.prototype是所有父原型的顶层
    */
    console.log(Function.prototype instanceof Object);  //true
    console.log(Object.prototype instanceof Function);  // false

    三:理解原型继承机制

    构造函数都有一个指针指向原型,Object.prototype是所有原型对象的顶层,比如如下代码:

    JavaScript

    var obj = {}; Object.prototype.name = "tugenhua"; console.log(obj.name); // tugenhua

    1
    2
    3
    var obj = {};
    Object.prototype.name = "tugenhua";
    console.log(obj.name); // tugenhua

    给Object.prototype 定义一个属性,通过字面量构建的对象的话,都会从父类那边获取Object.prototype的属性;

    从上面代码我们知道,原型继承的方法是:假如A需要继承于B,那么A.prototype(A的原型) = new B()(作为B的实列) 即可实现A继承于B; 因此我们下面可以初始化一个空的构造函数;然后把对象赋值给构造函数的原型,然后返回该构造函数的实列; 即可实现继承; 如下代码:

    JavaScript

    if(typeof Object.create !== 'function') { Object.create = function(o) { var F = new Function(); F.prototype = o; return new F(); } } var a = { name: 'longen', getName: function(){ return this.name; } }; var b = {}; b = Object.create(a); console.log(typeof b); //object console.log(b.name); // longen console.log(b.getName()); // longen

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    if(typeof Object.create !== 'function') {
        Object.create = function(o) {
            var F = new Function();
            F.prototype = o;
            return new F();
        }
    }
    var a = {
        name: 'longen',
        getName: function(){
            return this.name;
        }
    };
    var b = {};
    b = Object.create(a);
    console.log(typeof b); //object
    console.log(b.name);   // longen
    console.log(b.getName()); // longen

    如上代码:我们先检测Object是否已经有Object.create该方法;如果没有的话就创建一个; 该方法内创建一个空的构造器,把参数对象传递给构造函数的原型,最后返回该构造函数的实列,就实现了继承方式;如上测试代码:先定义一个a对象,有成员属性name=’longen’,还有一个getName()方法;最后返回该name属性; 然后定义一个b空对象,使用Object.create(a);把a对象继承给b对象,因此b对象也有属性name和成员方法getName();

     理解原型查找原理:对象查找先在该构造函数内查找对应的属性,如果该对象没有该属性的话,

    那么javascript会试着从该原型上去查找,如果原型对象中也没有该属性的话,那么它们会从原型中的原型去查找,直到查找的Object.prototype也没有该属性的话,那么就会返回undefined;因此我们想要仅在该对象内查找的话,为了提高性能,我们可以使用hasOwnProperty()来判断该对象内有没有该属性,如果有的话,就执行代码(使用for-in循环查找):如下:

    JavaScript

    var obj = { "name":'tugenhua', "age":'28' }; // 使用for-in循环 for(var i in obj) { if(obj.hasOwnProperty(i)) { console.log(obj[i]); //tugenhua 28 } }

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    var obj = {
        "name":'tugenhua',
        "age":'28'
    };
    // 使用for-in循环
    for(var i in obj) {
        if(obj.hasOwnProperty(i)) {
            console.log(obj[i]); //tugenhua 28
        }
    }

    如上使用for-in循环查找对象里面的属性,但是我们需要明白的是:for-in循环查找对象的属性,它是不保证顺序的,for-in循环和for循环;最本质的区别是:for循环是有顺序的,for-in循环遍历对象是无序的,因此我们如果需要对象保证顺序的话,可以把对象转换为数组来,然后再使用for循环遍历即可;

    下面我们来谈谈原型继承的优点和缺点

    JavaScript

    // 先看下面的代码: // 定义构造函数A,定义特权属性和特权方法 function A(x) { this.x1 = x; this.getX1 = function(){ return this.x1; } } // 定义构造函数B,定义特权属性和特权方法 function B(x) { this.x2 = x; this.getX2 = function(){ return this.x1 + this.x2; } } B.prototype = new A(1);

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    // 先看下面的代码:
    // 定义构造函数A,定义特权属性和特权方法
    function A(x) {
        this.x1 = x;
        this.getX1 = function(){
            return this.x1;
        }
    }
    // 定义构造函数B,定义特权属性和特权方法
    function B(x) {
        this.x2 = x;
        this.getX2 = function(){
            return this.x1 + this.x2;
        }
    }
    B.prototype = new A(1);

    B.prototype = new A(1);这句代码执行的时候,B的原型继承于A,因此B.prototype也有A的属性和方法,即:B.prototype.x1 = 1; B.prototype.getX1 方法;但是B也有自己的特权属性x2和特权方法getX2; 如下代码:

    JavaScript

    function C(x) { this.x3 = x; this.getX3 = function(){ return this.x3 + this.x2; } } C.prototype = new B(2); C.prototype = new B(2);这句代码执行的时候,C的原型继承于B,因此C.prototype.x2 = 2; C.prototype.getX2方法且C也有自己的特权属性x3和特权方法getX3, var b = new B(2); var c = new C(3); console.log(b.x1); // 1 console.log(c.x1); // 1 console.log(c.getX3()); // 5 console.log(c.getX2()); // 3 var b = new B(2);

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    function C(x) {
        this.x3 = x;
        this.getX3 = function(){
            return this.x3 + this.x2;
        }
    }
    C.prototype = new B(2);
    C.prototype = new B(2);这句代码执行的时候,C的原型继承于B,因此C.prototype.x2 = 2; C.prototype.getX2方法且C也有自己的特权属性x3和特权方法getX3,
    var b = new B(2);
    var c = new C(3);
    console.log(b.x1);  // 1
    console.log(c.x1);  // 1
    console.log(c.getX3()); // 5
    console.log(c.getX2()); // 3
    var b = new B(2);

    实列化B的时候 b.x1 首先会在构造函数内查找x1属性,没有找到,由于B的原型继承于A,因此A有x1属性,因此B.prototype.x1 = 1找到了;var c = new C(3); 实列化C的时候,从上面的代码可以看到C继承于B,B继承于A,因此在C函数中没有找到x1属性,会往原型继续查找,直到找到父元素A有x1属性,因此c.x1 = 1;c.getX3()方法; 返回this.x3+this.x2 this.x3 = 3;this.x2 是B的属性,因此this.x2 = 2;c.getX2(); 查找的方法也一样,不再解释

    prototype的缺点与优点如下:

    优点是:能够允许多个对象实列共享原型对象的成员及方法,

    缺点是:1. 每个构造函数只有一个原型,因此不直接支持多重继承;

    2. 不能很好地支持多参数或动态参数的父类。在原型继承阶段,用户还不能决定以

    什么参数来实列化构造函数。

    四:理解使用类继承(继承的更好的方案)

    类继承也叫做构造函数继承,在子类中执行父类的构造函数;实现原理是:可以将一个构造函数A的方法赋值给另一个构造函数B,然后调用该方法,使构造函数A在构造函数B内部被执行,这时候构造函数B就拥有了构造函数A中的属性和方法,这就是使用类继承实现B继承与A的基本原理;

    如下代码实现demo:

    JavaScript

    function A(x) { this.x = x; this.say = function(){ return this.x; } } function B(x,y) { this.m = A; // 把构造函数A作为一个普通函数引用给临时方法m this.m(x); // 执行构造函数A; delete this.m; // 清除临时方法this.m this.y = y; this.method = function(){ return this.y; } } var a = new A(1); var b = new B(2,3); console.log(a.say()); //输出1, 执行构造函数A中的say方法 console.log(b.say()); //输出2, 能执行该方法说明被继承了A中的方法 console.log(b.method()); // 输出3, 构造函数也拥有自己的方法

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    function A(x) {
        this.x = x;
        this.say = function(){
            return this.x;
        }
    }
    function B(x,y) {
        this.m = A; // 把构造函数A作为一个普通函数引用给临时方法m
        this.m(x);  // 执行构造函数A;
        delete this.m; // 清除临时方法this.m
        this.y = y;
        this.method = function(){
            return this.y;
        }
    }
    var a = new A(1);
    var b = new B(2,3);
    console.log(a.say()); //输出1, 执行构造函数A中的say方法
    console.log(b.say()); //输出2, 能执行该方法说明被继承了A中的方法
    console.log(b.method()); // 输出3, 构造函数也拥有自己的方法

    上面的代码实现了简单的类继承的基础,但是在复杂的编程中是不会使用上面的方法的,因为上面的代码不够严谨;代码的耦合性高;我们可以使用更好的方法如下:

    JavaScript

    function A(x) { this.x = x; } A.prototype.getX = function(){ return this.x; } // 实例化A var a = new A(1); console.log(a.x); // 1 console.log(a.getX()); // 输出1 // 现在我们来创建构造函数B,让其B继承与A,如下代码: function B(x,y) { this.y = y; A.call(this,x); } B.prototype = new A(); // 原型继承 console.log(B.prototype.constructor); // 输出构造函数A,指针指向与构造函数A B.prototype.constructor = B; // 重新设置构造函数,使之指向B console.log(B.prototype.constructor); // 指向构造函数B B.prototype.getY = function(){ return this.y; } var b = new B(1,2); console.log(b.x); // 1 console.log(b.getX()); // 1 console.log(b.getY()); // 2 // 下面是演示对构造函数getX进行重写的方法如下: B.prototype.getX = function(){ return this.x; } var b2 = new B(10,20); console.log(b2.getX()); // 输出10

    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
    function A(x) {
        this.x = x;
    }
    A.prototype.getX = function(){
        return this.x;
    }
    // 实例化A
    var a = new A(1);
    console.log(a.x); // 1
    console.log(a.getX()); // 输出1
    // 现在我们来创建构造函数B,让其B继承与A,如下代码:
    function B(x,y) {
        this.y = y;
        A.call(this,x);
    }
    B.prototype = new A();  // 原型继承
    console.log(B.prototype.constructor); // 输出构造函数A,指针指向与构造函数A
    B.prototype.constructor = B;          // 重新设置构造函数,使之指向B
    console.log(B.prototype.constructor); // 指向构造函数B
    B.prototype.getY = function(){
        return this.y;
    }
    var b = new B(1,2);
    console.log(b.x); // 1
    console.log(b.getX()); // 1
    console.log(b.getY()); // 2
     
    // 下面是演示对构造函数getX进行重写的方法如下:
    B.prototype.getX = function(){
        return this.x;
    }
    var b2 = new B(10,20);
    console.log(b2.getX());  // 输出10

    下面我们来分析上面的代码:

    在构造函数B内,使用A.call(this,x);这句代码的含义是:我们都知道使用call或者apply方法可以改变this指针指向,从而可以实现类的继承,因此在B构造函数内,把x的参数传递给A构造函数,并且继承于构造函数A中的属性和方法;

    使用这句代码:B.prototype = new A();  可以实现原型继承,也就是B可以继承A中的原型所有的方法;console.log(B.prototype.constructor); 打印出输出构造函数A,指针指向与构造函数A;我们明白的是,当定义构造函数时候,其原型对象默认是一个Object类型的一个实例,其构造器默认会被设置为构造函数本身,如果改动构造函数prototype属性值,使其指向于另一个对象的话,那么新对象就不会拥有原来的constructor的值,比如第一次打印console.log(B.prototype.constructor); 指向于被实例化后的构造函数A,重写设置B的constructor的属性值的时候,第二次打印就指向于本身B;因此B继承与构造A及其原型的所有属性和方法,当然我们也可以对构造函数B重写构造函数A中的方法,如上面最后几句代码是对构造函数A中的getX方法进行重写,来实现自己的业务~;

    五:建议使用封装类实现继承

    封装类实现继承的基本原理:先定义一个封装函数extend;该函数有2个参数,Sub代表子类,Sup代表超类;在函数内,先定义一个空函数F, 用来实现功能中转,先设置F的原型为超类的原型,然后把空函数的实例传递给子类的原型,使用一个空函数的好处是:避免直接实例化超类可能会带来系统性能问题,比如超类的实例很大的话,实例化会占用很多内存;

    如下代码:

    JavaScript

    function extend(Sub,Sup) { //Sub表示子类,Sup表示超类 // 首先定义一个空函数 var F = function(){}; // 设置空函数的原型为超类的原型 F.prototype = Sup.prototype; // 实例化空函数,并把超类原型引用传递给子类 Sub.prototype = new F(); // 重置子类原型的构造器为子类自身 Sub.prototype.constructor = Sub; // 在子类中保存超类的原型,避免子类与超类耦合 Sub.sup = Sup.prototype; if(Sup.prototype.constructor === Object.prototype.constructor) { // 检测超类原型的构造器是否为原型自身 Sup.prototype.constructor = Sup; } } 测试代码如下: // 下面我们定义2个类A和类B,我们目的是实现B继承于A function A(x) { this.x = x; this.getX = function(){ return this.x; } } A.prototype.add = function(){ return this.x + this.x; } A.prototype.mul = function(){ return this.x * this.x; } // 构造函数B function B(x){ A.call(this,x); // 继承构造函数A中的所有属性及方法 } extend(B,A); // B继承于A var b = new B(11); console.log(b.getX()); // 11 console.log(b.add()); // 22 console.log(b.mul()); // 121

    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
    function extend(Sub,Sup) {
        //Sub表示子类,Sup表示超类
        // 首先定义一个空函数
        var F = function(){};
     
        // 设置空函数的原型为超类的原型
        F.prototype = Sup.prototype;
     
    // 实例化空函数,并把超类原型引用传递给子类
        Sub.prototype = new F();
     
        // 重置子类原型的构造器为子类自身
        Sub.prototype.constructor = Sub;
     
        // 在子类中保存超类的原型,避免子类与超类耦合
        Sub.sup = Sup.prototype;
     
        if(Sup.prototype.constructor === Object.prototype.constructor) {
            // 检测超类原型的构造器是否为原型自身
            Sup.prototype.constructor = Sup;
        }
     
    }
    测试代码如下:
    // 下面我们定义2个类A和类B,我们目的是实现B继承于A
    function A(x) {
        this.x = x;
        this.getX = function(){
            return this.x;
        }
    }
    A.prototype.add = function(){
        return this.x + this.x;
    }
    A.prototype.mul = function(){
        return this.x * this.x;
    }
    // 构造函数B
    function B(x){
        A.call(this,x); // 继承构造函数A中的所有属性及方法
    }
    extend(B,A);  // B继承于A
    var b = new B(11);
    console.log(b.getX()); // 11
    console.log(b.add());  // 22
    console.log(b.mul());  // 121

    注意:在封装函数中,有这么一句代码:Sub.sup = Sup.prototype; 我们现在可以来理解下它的含义:

    比如在B继承与A后,我给B函数的原型再定义一个与A相同的原型相同的方法add();

    如下代码

    JavaScript

    extend(B,A); // B继承于A var b = new B(11); B.prototype.add = function(){ return this.x + "" + this.x; } console.log(b.add()); // 1111

    1
    2
    3
    4
    5
    6
    extend(B,A);  // B继承于A
    var b = new B(11);
    B.prototype.add = function(){
        return this.x + "" + this.x;
    }
    console.log(b.add()); // 1111

    那么B函数中的add方法会覆盖A函数中的add方法;因此为了不覆盖A类中的add()方法,且调用A函数中的add方法;可以如下编写代码:

    JavaScript

    B.prototype.add = function(){ //return this.x + "" + this.x; return B.sup.add.call(this); } console.log(b.add()); // 22

    1
    2
    3
    4
    5
    B.prototype.add = function(){
        //return this.x + "" + this.x;
        return B.sup.add.call(this);
    }
    console.log(b.add()); // 22

    B.sup.add.call(this); 中的B.sup就包含了构造函数A函数的指针,因此包含A函数的所有属性和方法;因此可以调用A函数中的add方法;

    如上是实现继承的几种方式,类继承和原型继承,但是这些继承无法继承DOM对象,也不支持继承系统静态对象,静态方法等;比如Date对象如下:

    JavaScript

    // 使用类继承Date对象 function D(){ Date.apply(this,arguments); // 调用Date对象,对其引用,实现继承 } var d = new D(); console.log(d.toLocaleString()); // [object object]

    1
    2
    3
    4
    5
    6
    // 使用类继承Date对象
    function D(){
        Date.apply(this,arguments); // 调用Date对象,对其引用,实现继承
    }
    var d = new D();
    console.log(d.toLocaleString()); // [object object]

    如上代码运行打印出object,我们可以看到使用类继承无法实现系统静态方法date对象的继承,因为他不是简单的函数结构,对声明,赋值和初始化都进行了封装,因此无法继承;

    下面我们再来看看使用原型继承date对象;

    JavaScript

    function D(){} D.prototype = new D(); var d = new D(); console.log(d.toLocaleString());//[object object]

    1
    2
    3
    4
    function D(){}
    D.prototype = new D();
    var d = new D();
    console.log(d.toLocaleString());//[object object]

    我们从代码中看到,使用原型继承也无法继承Date静态方法;但是我们可以如下封装代码继承:

    JavaScript

    function D(){ var d = new Date(); // 实例化Date对象 d.get = function(){ // 定义本地方法,间接调用Date对象的方法 console.log(d.toLocaleString()); } return d; } var d = new D(); d.get(); // 2015/12/21 上午12:08:38

    1
    2
    3
    4
    5
    6
    7
    8
    9
    function D(){
        var d = new Date();  // 实例化Date对象
        d.get = function(){ // 定义本地方法,间接调用Date对象的方法
            console.log(d.toLocaleString());
        }
        return d;
    }
    var d = new D();
    d.get(); // 2015/12/21 上午12:08:38

    六:理解使用复制继承

    复制继承的基本原理是:先设计一个空对象,然后使用for-in循环来遍历对象的成员,将该对象的成员一个一个复制给新的空对象里面;这样就实现了复制继承了;如下代码:

    JavaScript

    function A(x,y) { this.x = x; this.y = y; this.add = function(){ return this.x + this.y; } } A.prototype.mul = function(){ return this.x * this.y; } var a = new A(2,3); var obj = {}; for(var i in a) { obj[i] = a[i]; } console.log(obj); // object console.log(obj.x); // 2 console.log(obj.y); // 3 console.log(obj.add()); // 5 console.log(obj.mul()); // 6

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    function A(x,y) {
        this.x = x;
        this.y = y;
        this.add = function(){
            return this.x + this.y;
        }
    }
    A.prototype.mul = function(){
        return this.x * this.y;
    }
    var a = new A(2,3);
    var obj = {};
    for(var i in a) {
        obj[i] = a[i];
    }
    console.log(obj); // object
    console.log(obj.x); // 2
    console.log(obj.y); // 3
    console.log(obj.add()); // 5
    console.log(obj.mul()); // 6

    如上代码:先定义一个构造函数A,函数里面有2个属性x,y,还有一个add方法,该构造函数原型有一个mul方法,首先实列化下A后,再创建一个空对象obj,遍历对象一个个复制给空对象obj,从上面的打印效果来看,我们可以看到已经实现了复制继承了;对于复制继承,我们可以封装成如下方法来调用:

    JavaScript

    // 为Function扩展复制继承方法 Function.prototype.extend = function(o) { for(var i in o) { //把参数对象的成员复制给当前对象的构造函数原型对象 this.constructor.prototype[i] = o[i]; } } // 测试代码如下: var o = function(){}; o.extend(new A(1,2)); console.log(o.x); // 1 console.log(o.y); // 2 console.log(o.add()); // 3 console.log(o.mul()); // 2

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    // 为Function扩展复制继承方法
    Function.prototype.extend = function(o) {
        for(var i in o) {
            //把参数对象的成员复制给当前对象的构造函数原型对象
            this.constructor.prototype[i] = o[i];
        }
    }
    // 测试代码如下:
    var o = function(){};
    o.extend(new A(1,2));
    console.log(o.x);  // 1
    console.log(o.y);  // 2
    console.log(o.add()); // 3
    console.log(o.mul()); // 2

    上面封装的扩展继承方法中的this对象指向于当前实列化后的对象,而不是指向于构造函数本身,因此要使用原型扩展成员的话,就需要使用constructor属性来指向它的构造器,然后通过prototype属性指向构造函数的原型;

    复制继承有如下优点:

    1. 它不能继承系统核心对象的只读方法和属性

    2. 如果对象数据非常多的话,这样一个个复制的话,性能是非常低的;

    3. 只有对象被实列化后,才能给遍历对象的成员和属性,相对来说不够灵活;

    4. 复制继承只是简单的赋值,所以如果赋值的对象是引用类型的对象的话,可能会存在一些副作用;如上我们看到有如上一些缺点,下面我们可以使用clone(克隆的方式)来优化下:

    基本思路是:为Function扩展一个方法,该方法能够把参数对象赋值赋值一个空构造函数的原型对象,然后实列化构造函数并返回实列对象,这样该对象就拥有了该对象的所有成员;代码如下:

    JavaScript

    Function.prototype.clone = function(o){ function Temp(){}; Temp.prototype = o; return Temp(); } // 测试代码如下: Function.clone(new A(1,2)); console.log(o.x); // 1 console.log(o.y); // 2 console.log(o.add()); // 3 console.log(o.mul()); // 2

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    Function.prototype.clone = function(o){
        function Temp(){};
        Temp.prototype = o;
        return Temp();
    }
    // 测试代码如下:
    Function.clone(new A(1,2));
    console.log(o.x);  // 1
    console.log(o.y);  // 2
    console.log(o.add()); // 3
    console.log(o.mul()); // 2

    2 赞 19 收藏 1 评论

    图片 2

    setTimeout 的黑魔法

    2016/05/03 · JavaScript · 1 评论 · settimeout

    原文出处: 李三思   

    setTimeout,前端工程师必定会打交道的一个函数.它看上去非常的简单,朴实.有着一个很不平凡的名字–定时器.让年少的我天真的以为自己可以操纵未来.却不知朴实之中隐含着惊天大密.我还记得我第一次用这个函数的时候,我天真的以为它就是js实现多线程的工具.当时用它实现了一个坦克大战的小游戏,玩儿不亦乐乎.可是随着在前端这条路上越走越远,对它理解开始产生了变化.它似乎开始蒙上了面纱,时常有一些奇怪的表现让我捉摸不透.终于,我的耐心耗尽,下定决心,要撕开它的面具,一探究竟.

    要说setTimeout的渊源,就得从它的官方定义说起.w3c是这么定义的

    setTimeout() 方法用于在指定的毫秒数后调用函数或计算表达式。

    看到这样一个说明,我们明白了它就是一个定时器,我们设定的函数就是一个”闹钟”,时间到了它就会去执行.然而聪明的你不禁有这样一个疑问,如果是settimeout(fn,0)呢?按照定义的说明,它是否会立马执行?实践是检验真理的唯一标准,让我们来看看下面的实验

    JavaScript

    <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title></title> </head> <body> <script> alert(1); setTimeout("alert(2)", 0); alert(3); </script> </body> </html>

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title></title>
    </head>
    <body>
        
        <script>
            alert(1);
            setTimeout("alert(2)", 0);
            alert(3);
        </script>
    </body>
    </html>

    这是一个很简单的实验,如果settimeout(0)会立即执行,那么这里的执行结果就应该是1->2>3  . 然而实际的结果却是1->3->2. 这说明了settimeout(0)并不是立即执行.同时让我们对settimeout的行为感到很诡异.

    前言

    前不久抽空对目前比较火的视频直播,做了下研究与探索,了解其整体实现流程,以及探讨移动端HTML5直播可行性方案。

    发现目前 WEB 上主流的视频直播方案有 HLS 和 RTMP,移动 WEB 端目前以 HLS 为主(HLS存在延迟性问题,也可以借助 video.js 采用RTMP),PC端则以 RTMP 为主实时性较好,接下来将围绕这两种视频流协议来展开H5直播主题分享。

    js引擎是单线程执行的

    我们先把上面的问题放一放.从js语言的设计上来看看是否能找到蛛丝马迹.

    我们发现js语言设计的一个很重要的点是,js是没有多线程的.js引擎的执行是单线程执行.这个特性曾经困扰我很久,我想不明白既然js是单线程的,那么是谁来为定时器计时的?是谁来发送ajax请求的?我陷入了一个盲区.即将js等同于浏览器.我们习惯了在浏览器里面执行代码,却忽略了浏览器本身.js引擎是单线程的,可是浏览器却可以是多线程的,js引擎只是浏览器的一个线程而已.定时器计时,网络请求,浏览器渲染等等.都是由不同的线程去完成的. 口说无凭,咱们依然看一个例子

    JavaScript

    <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title></title> </head> <body> </body> <script> var isEnd = true; window.setTimeout(function () { isEnd = false;//1s后,改变isEnd的值 }, 1000); while (isEnd); alert('end'); </script> </html>

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title></title>
    </head>
    <body>
        
    </body>
    <script>
        var isEnd = true;
        window.setTimeout(function () {
            isEnd = false;//1s后,改变isEnd的值
        }, 1000);
        while (isEnd);
        alert('end');
    </script>
    </html>

    isEnd默认是true的,在while中是死循环的.最后的alert是不会执行的. 我添加了一个定时器,1秒后将isEnd改为false. 如果说js引擎是多线程的,那么在1秒后,alert就会被执行.然而实际情况是,页面会永远死循环下去.alert并没有执行.这很好的证明了,settimeout并不能作为多线程使用.js引擎执行是单线程的.

    一、视频流协议HLS与RTMP

    event loop

    从上面的实验中,我们更加疑惑了,settimeout到底做了什么事情呢?

    原来还是得从js语言的设计上寻找答案.

    图片 3

    js引擎单线程执行的,它是基于事件驱动的语言.它的执行顺序是遵循一个叫做事件队列的机制.从图中我们可以看出,浏览器有各种各样的线程,比如事件触发器,网络请求,定时器等等.线程的联系都是基于事件的.js引擎处理到与其他线程相关的代码,就会分发给其他线程,他们处理完之后,需要js引擎计算时就是在事件队列里面添加一个任务. 这个过程中,js并不会阻塞代码等待其他线程执行完毕,而且其他线程执行完毕后添加事件任务告诉js引擎执行相关操作.这就是js的异步编程模型.

    如此我们再回过头来看settimeout(0)就会恍然大悟.js代码执行到这里时,会开启一个定时器线程,然后继续执行下面的代码.该线程会在指定时间后往事件队列里面插入一个任务.由此可知settimeout(0)里面的操作会放在所有主线程任务之后. 这也就解释了为什么第一个实验结果是1->3-2 .

    由此可见官方对于settimeout的定义是有迷惑性的.应该给一个新的定义:

    在指定时间内, 将任务放入事件队列,等待js引擎空闲后被执行.

    1. HTTP Live Streaming

    HTTP Live Streaming(简称 HLS)是一个基于 HTTP 的视频流协议,由 Apple 公司实现,Mac OS 上的 QuickTime、Safari 以及 iOS 上的 Safari 都能很好的支持 HLS,高版本 Android 也增加了对 HLS 的支持。一些常见的客户端如:MPlayerX、VLC 也都支持 HLS 协议。

    HLS 协议基于 HTTP,而一个提供 HLS 的服务器需要做两件事:

    • 编码:以 H.263 格式对图像进行编码,以 MP3 或者 HE-AAC 对声音进行编码,最终打包到 MPEG-2 TS(Transport Stream)容器之中;
    • 分割:把编码好的 TS 文件等长切分成后缀为 ts 的小文件,并生成一个 .m3u8 的纯文本索引文件;

    浏览器使用的是 m3u8 文件。m3u8 跟音频列表格式 m3u 很像,可以简单的认为 m3u8 就是包含多个 ts 文件的播放列表。播放器按顺序逐个播放,全部放完再请求一下 m3u8 文件,获得包含最新 ts 文件的播放列表继续播,周而复始。整个直播过程就是依靠一个不断更新的 m3u8 和一堆小的 ts 文件组成,m3u8 必须动态更新,ts 可以走 CDN。一个典型的 m3u8 文件格式如下:

    #EXTM3U

    #EXT-X-STREAM-INF:PROGRAM-ID=1, BANDWIDTH=200000
    gear1/prog_index.m3u8

    #EXT-X-STREAM-INF:PROGRAM-ID=1, BANDWIDTH=311111
    gear2/prog_index.m3u8

    #EXT-X-STREAM-INF:PROGRAM-ID=1, BANDWIDTH=484444
    gear3/prog_index.m3u8

    #EXT-X-STREAM-INF:PROGRAM-ID=1, BANDWIDTH=737777
    gear4/prog_index.m3u8

    可以看到 HLS 协议本质还是一个个的 HTTP 请求 / 响应,所以适应性很好,不会受到防火墙影响。但它也有一个致命的弱点:延迟现象非常明显。如果每个 ts 按照 5 秒来切分,一个 m3u8 放 6 个 ts 索引,那么至少就会带来 30 秒的延迟。如果减少每个 ts 的长度,减少 m3u8 中的索引数,延时确实会减少,但会带来更频繁的缓冲,对服务端的请求压力也会成倍增加。所以只能根据实际情况找到一个折中的点。

    对于支持 HLS 的浏览器来说,直接这样写就能播放了:

    XHTML

    <video src="" height="300" width="400" preload="auto" autoplay="autoplay" loop="loop" webkit-playsinline="true"></video>

    1
    2
    <video src="http://devimages.apple.com/iphone/samples/bipbop/bipbopall.m3u8"
    height="300" width="400" preload="auto" autoplay="autoplay" loop="loop" webkit-playsinline="true"></video>

    注意:HLS 在 PC 端仅支持safari浏览器,类似chrome浏览器使用HTML5 video标签无法播放 m3u8 格式,可直接采用网上一些比较成熟的方案,如:sewise-player、MediaElement、videojs-contrib-hls、jwplayer。

    js引擎与GUI引擎是互斥的

    谈到这里,就不得不说浏览器的另外一个引擎—GUI渲染引擎. 在js中渲染操作也是异步的.比如dom操作的代码会在事件队列中生成一个任务,js执行到这个任务时就会去调用GUI引擎渲染.

    js语言设定js引擎与GUI引擎是互斥的,也就是说GUI引擎在渲染时会阻塞js引擎计算.原因很简单,如果在GUI渲染的时候,js改变了dom,那么就会造成渲染不同步. 我们需要深刻理解js引擎与GUI引擎的关系,因为这与我们平时开发息息相关,我们时长会遇到一些很奇葩的渲染问题.看这个例子

    JavaScript

    <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title></title> </head> <body> <table border=1> <tr><td><button id='do'>Do long calc - bad status!</button></td> <td><div id='status'>Not Calculating yet.</div></td> </tr> <tr><td><button id='do_ok'>Do long calc - good status!</button></td> <td><div id='status_ok'>Not Calculating yet.</div></td> </tr> </table> <script> function long_running(status_div) { var result = 0; for (var i = 0; i < 1000; i++) { for (var j = 0; j < 700; j++) { for (var k = 0; k < 300; k++) { result = result + i + j + k; } } } document.querySelector(status_div).innerHTML = 'calclation done' ; } document.querySelector('#do').onclick = function () { document.querySelector('#status').innerHTML = 'calculating....'; long_running('#status'); }; document.querySelector('#do_ok').onclick = function () { document.querySelector('#status_ok').innerHTML = 'calculating....'; window.setTimeout(function (){ long_running('#status_ok') }, 0); }; </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
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title></title>
    </head>
    <body>
        <table border=1>
            <tr><td><button id='do'>Do long calc - bad status!</button></td>
                <td><div id='status'>Not Calculating yet.</div></td>
            </tr>
            <tr><td><button id='do_ok'>Do long calc - good status!</button></td>
                <td><div id='status_ok'>Not Calculating yet.</div></td>
            </tr>
        </table>    
    <script>
     
    function long_running(status_div) {
     
        var result = 0;
        for (var i = 0; i < 1000; i++) {
            for (var j = 0; j < 700; j++) {
                for (var k = 0; k < 300; k++) {
                    result = result + i + j + k;
                }
            }
        }
        document.querySelector(status_div).innerHTML = 'calclation done' ;
    }
     
    document.querySelector('#do').onclick = function () {
        document.querySelector('#status').innerHTML = 'calculating....';
        long_running('#status');
    };
     
    document.querySelector('#do_ok').onclick = function () {
        document.querySelector('#status_ok').innerHTML = 'calculating....';
        window.setTimeout(function (){ long_running('#status_ok') }, 0);
    };
     
    </script>
    </body>
    </html>

    我们希望能看到计算的每一个过程,我们在程序开始,计算,结束时,都执行了一个dom操作,插入了代表当前状态的字符串,Not Calculating yet.和calculating….和calclation done.计算中是一个耗时的3重for循环. 在没有使用settimeout的时候,执行结果是由Not Calculating yet 直接跳到了calclation done.这显然不是我们希望的.而造成这样结果的原因正是js的事件循环单线程机制.dom操作是异步的,for循环计算是同步的.异步操作都会被延迟到同步计算之后执行.也就是代码的执行顺序变了.calculating….和calclation done的dom操作都被放到事件队列后面而且紧跟在一起,造成了丢帧.无法实时的反应.这个例子也告诉了我们,在需要实时反馈的操作,如渲染等,和其他相关同步的代码,要么一起同步,要么一起异步才能保证代码的执行顺序.在js中,就只能让同步代码也异步.即给for计算加上settimeout.

    2. Real Time Messaging Protocol

    Real Time Messaging Protocol(简称 RTMP)是 Macromedia 开发的一套视频直播协议,现在属于 Adobe。这套方案需要搭建专门的 RTMP 流媒体服务如 Adobe Media Server,并且在浏览器中只能使用 Flash 实现播放器。它的实时性非常好,延迟很小,但无法支持移动端 WEB 播放是它的硬伤。

    虽然无法在iOS的H5页面播放,但是对于iOS原生应用是可以自己写解码去解析的, RTMP 延迟低、实时性较好。

    浏览器端,HTML5 video标签无法播放 RTMP 协议的视频,可以通过 video.js 来实现。

    XHTML

    <link href="" rel="stylesheet">   <video id="example_video_1" class="video-js vjs-default-skin" controls preload="auto" width="640" height="264" loop="loop" webkit-playsinline> <source src="rtmp://10.14.221.17:1935/rtmplive/home" type='rtmp/flv'> </video>   <script src="; <script> videojs.options.flash.swf = 'video.swf'; videojs('example_video_1').ready(function() { this.play(); }); </script>

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    <link href="http://vjs.zencdn.net/5.8.8/video-js.css" rel="stylesheet">
     
    <video id="example_video_1" class="video-js vjs-default-skin" controls preload="auto" width="640" height="264" loop="loop" webkit-playsinline>
    <source src="rtmp://10.14.221.17:1935/rtmplive/home" type='rtmp/flv'>
    </video>
     
    <script src="http://vjs.zencdn.net/5.8.8/video.js"></script>
    <script>
    videojs.options.flash.swf = 'video.swf';
    videojs('example_video_1').ready(function() {
    this.play();
    });
    </script>

    settimeout(0)的作用

    不同浏览器的实现情况不同,HTML5定义的最小时间间隔是4毫秒. 使用settimeout(0)会使用浏览器支持的最小时间间隔.所以当我们需要把一些操作放到下一帧处理的时候,我们通常使用settimeout(0)来hack.

    3. 视频流协议HLS与RTMP对比

    协议 原理 延时 优点 使用场景
    HLS 短链接Http 集合一段时间数据生成ts切片文件更新m3u8文件 10s – 30s 跨平台 移动端为主
    RTMP 长链接Tcp 每个时刻的数据收到后立即发送 2s 延时低、实时性好 PC+直播+实时性要求高+互动性强

    requestAnimationFrame

    这个函数与settimeout很相似,但它是专门为动画而生的.settimeout经常被用来做动画.我们知道动画达到60帧,用户就无法感知画面间隔.每一帧大约16毫秒.而requestAnimationFrame的帧率刚好是这个频率.除此之外相比于settimeout,还有以下的一些优点:

    • requestAnimationFrame 会把每一帧中的所有DOM操作集中起来,在一次重绘或回流中就完成,并且重绘或回流的时间间隔紧紧跟随浏览器的刷新频率,一般来说,这个频率为每秒60帧,每帧大约16毫秒.
    • 在隐藏或不可见的元素中,requestAnimationFrame将不会进行重绘或回流,这当然就意味着更少的的cpu,gpu和内存使用量。
    • 但它优于setTimeout/setInterval的地方在于它是由浏览器专门为动画提供的API,在运行时浏览器会自动优化方法的调用,并且如果页面不是激活状态下的话,动画会自动暂停,有效节省了CPU开销。

    本文由奥门新浦京网址发布于Wed前段,转载请注明出处:H5直播起航,的黑魔法

    关键词: