Javascript知识点记录(二)

时间:2021-07-08 13:41:26

Javascript入门易,精通难,基本上是共识的一个观点。在这个篇幅里,主要对一些难点进行记录。

鸭子类型

Javascript属于动态类型语言的一种。对变量类型的宽容,给了很大的灵活性。由于无需类型检测,则无需考虑他是否被设计拥有该方法。

鸭子类型通俗说法:如果它走起路来像鸭子,叫起来也是鸭子,那么他就是鸭子

鸭子类型指导我们只关注对象的行为,而不关注对象本身。关注HAS-A 而不是 IS-A。

如下例子,不管什么动物,只要他会duckSinging,并且是方法,就可以加入。

var duck = {
        duckSinging: function () {
            console.log("鸭子叫");
        }
    };
    var chicken = {
        duckSinging: function () {
            console.log("鸭子叫");
        }
    };
    var joinChoir = function(animal){
        if(animal && animal.duckSinging instanceof Function)
        {
            //可以加入
        }
    }

多态

同一个操作用于不同的对象上,可以产生不同的解释和不同的结果。举例:动作都会叫,那么叫就是操作。猫叫和狗叫的结果不一样,这就是多态。

Sound是同一个操作,所以使用prototype来在原型链上进行定义。将Cat生成为方法,是为了给他的原型链添加方法。如Dog就不属于多态。

!(function () {
    var makeSound = function (animal) {
        animal.Sound();
    };
    var Cat = function(){};
    Cat.prototype.Sound = function () {
        console.log("喵");
    };
    var Dog = {}; //不属于多态
    Dog.Sound = function () {
        console.log("汪");
    }
    makeSound(Dog);
})()

一种动物能否发出叫声,取决于他有没有Sound方法,而不是取决于他是那种对象。不存在任何类型耦合,这就是鸭子类型的典型。

原型模式

原型模式是通过克隆来创建对象的。我们只需要调用克隆方法,就可以完成功能。使用Object.Create方法,来实现克隆。

!(function () {
    var Plance = function () {
        this.blood = 100
    };
    var plane = new Plance();
    plane.blood = 200;
    var clonePlane = Object.create(plane);
    console.log(clonePlane.blood);
})()

可以重写拷贝方法,来进行自定义

Object.create = Object.create || function(obj){  //Object.create || 意思是如果没有此方法
          var F = function () {};
            F.prototype = obj;
            return new F();
        };

继承

原型编程的基本规则

1.所有的数据都是对象

2.要得到一个对象,需要找到一个对象作为原型进行克隆。

3.对象会记住他的原型

4.如果对象无法响应某个请求,则会把这个请求委托给它自己的原型

javascript中绝大多数数据都是对象,除了undefined之外,都可以通过包装类的方式编程对象类型数据。

javascript中根对象是Object.prototype对象,他是一个空的对象。任何对象都是通过这个克隆而来的。

 var obj = function(){};
    var obj1 = {};
    console.log(Object.getPrototypeOf(obj) === Function.prototype);
    console.log(Object.getPrototypeOf(obj1) === Object.prototype);

Javascript中没有类的概念,那么new Person()这种,实际上是调用他的函数构造器,来进行初始化。函数既可以作为普通函数,也可以作为构造器。

!(function () {
   var obj = function(na){
       this.name = na;
   };
    var obj1 = new obj();
    var obj2 = new obj('a');
    console.log(obj1.name); //undefined
    console.log(obj2.name); //a
})()

请求可以在链条中往后传递,那么每个节点都必须知道它的下一个节点。Javascript给对象提供了一个名为_proto_的隐藏属性。

这个属性会默认指向它的构造器的原型对象。

var obj = function(na){};
   console.log(obj.__proto__ === Function.prototype); //true

当对象无法响应某个请求的时候,他会顺着原型链把请求传递下去,直到有可以处理的请求对象为止。

以下是最常用的原型继承

首先obj2找,然后obj2对象构造器原型obj1原型,obj1原型找obj原型

!(function () {
   var obj = function(){};
    obj.prototype = {name:'sven'};
    var obj1 = function () {};
    obj1.prototype = new obj();
    var obj2 = new obj1();
   console.log(obj2.name); //sven
})()

对象构造器创建与对象克隆

对象构造器创建,只会创建该对象实体,不会创建该对象对应的原型。而克隆则是将原型也一并拷贝过去。

!(function () {
   var obj1 = function(){
       this.name = 'sven'
   };
   var obj2 = obj1;
    var obj3 = Object.create(obj1);
    var obj4 = new obj1();
    console.log(typeof obj2.prototype); //object
    console.log(typeof obj3.prototype); //object
    console.log(typeof obj4.prototype); //undefined
})()

this

this总是指向一个对象,对象是在运行时基于函数执行环境动态绑定的。

this指向大致分为四种

1.作为对象调用时,this指向该对象。

 var obj = {
        a:1,
        getA:function()
        {console.log(this.a);}
    };
    obj.getA(); 

2.作为普通函数调用时,this指向windows对象。

 window.a = 1;
    var getA = function(){
        return this.a;
    };
    console.log(getA()); 

3.作为构造器调用时,this指向他本身。

!(function () {
   var myCla = function(){
       this.name = 'sven';
   };
    var obj = new myCla();
    console.log(obj.name); //sven
})()

  但是,如果在方法中,进行闭包返回,那么结果就是最终返回的这个对象。

!(function () {
   var myCla = function(){
       this.name = 'sven';
       return {
           name : "an"
       };
   };
    var obj = new myCla();
    console.log(obj.name); //an
})()

4.作为call或apply调用时,可以动态的改变传入函数this.

!(function () {
   var obj1 = {
       name:'sven',
       getName:function(){
           return this.name;
       }
   };
    var obj2 = { name : "an"};
    console.log(obj1.getName()); //sven
    console.log(obj1.getName.call(obj2)); //an
})()

需要注意一点,就是对象变换。当你把对象引用到一个变量后,就会变成普通函数调用方法。例如:var get = obj1.getName; get(); //undefind

使用Apply也可以做到借用其他对象的方法

!(function () {
    var A = function (name) {
        this.name = name;
    };
    var B = function(){
        A.apply(this,arguments);
    };
    B.prototype.getName=function(){
      return this.name;
    };
    var b = new B('save');
    console.log(b.getName());
})()

闭包

闭包两大知识点就是变量的作用域以及变量的生命周期

变量的作用域

作用域是指变量的有效范围。

在函数中声明变量的时候,该变量前面没有var,则变为全局变量,任何地方都可以访问到。

使用var关键字在函数内声明,即是局部变量。只要函数内部才能访问。函数外面是访问不到的。

var a = 1;
   var func = function(){
       var b =2;
       var func2 = function(){
           var c = 3;
           console.log(a);
           console.log(b);
       };
       console.log(c); //is not defined
   };

变量的生命周期

对于全局变量来说,声明周期是永久的,除非主动销毁这个全局变量。

对于函数内的局部变量来说,当退出函数时就会销毁。

使用闭包的话会一直存在在匿名函数的引用中,则不会销毁。因为,v返回了一个匿名函数的引用,可以访问到func()方法里面,局部变量a一直处在这个环境里。所以能被外界访问。

!(function () {
   var func = function(){
       var a =1;
       return function(){
            a++;
           console.log(a);
       };
   };
    var v = func();
    var b = func();
    var c = v;
    v();
    b();
    c();
})()

绑定事件

因为javascript事件都是异步触发的,当事件被触发的时候,循环就结束了,所以i会保持循环最后的数值。可以添加立即执行函数,来进行执行。

var nodes = document.getElementsByTagName('div');
    for (var i = 0; i < nodes.length; i++) {
        (function (i) {
            nodes[i].onclick = function () {
                alert(i);
            }
        })(i)
    }

闭包和面向对象

使用对象以方法的形式包含过程,闭包是在过程中以环境的形式包含数据。两个都可以达到一样的目的。

!(function () {
   var extent = function(){
     var value = 0;
       return {
           call:function(){
               value++;
               console.log(value);
           }
       };
   };
    var extent = extent();
    extent.call();extent.call();extent.call();
//上为闭包写法,下为面向对象写法
    var extent2 = {
        value:0,
        call:function(){
            this.value++;
            console.log(this.value);
        }
    };
    extent2.call();extent2.call();extent2.call();
})()

高级函数

currying(函数柯里化)

currying又称部分求值,当你传入函数的时候,不会立刻求值。会等到真正需要求值的时候,一次性求值。

//封装计算框架,其中算法使用参数传递
    var currying = function (fn) {
        var args = [];
        return function () {
            if (arguments.length === 0) {
                return fn.apply(this, args);
            } else {
                [].push.apply(args, arguments);
                return arguments.callee;
            }
        };
    };
    //定义实际算法
    var cost = (function () {
        var money = 0;
        return function () {
            for (var i = 0; i < arguments.length; i++) {
                money += arguments[i];
            }
            return money;
        };
    })();
    var cost = currying(cost);
    cost(100);cost(100);cost(100);
    console.log(cost());

函数节流

某些场合下,函数被非常频繁的调用,会造成巨大的性能影响。比如说,瀑布流,窗口大小等。

使用函数节流,可以按照我们需要的时间忽略掉一些请求。使用setTimeout方法。

 //函数节流方法,第一个参数,执行函数。第二个参数,延迟时间
    var throttle = function (fn, interval) {
        var _self = fn, timer, firstTime; //定义函数引用,定时器,是否首次调用
        return function () {
            var args = arguments, _me = this;
            if (firstTime) { //第一次调用不延迟
                _self.apply(_me, args); //修正方法,传递参数
                return firstTime = false; //不进行延迟
            }
            if (timer) { //判断计时器是否存在
                return false; //存在代表尚在延迟时间内,忽略
            }
            timer = setTimeout(function () { //延迟执行一次
                clearTimeout(timer); //取消方法
                timer = null;
                _self.apply(_me, args);
            }, interval || 500);
        };
    };
    window.onresize = throttle(function(){
        console.log(1);
    },500);

分时函数

页面渲染DOM时候,为了避免一次性加载过多的节点,添加的分时函数操作。

!(function () {
    //分时函数.参数1,创建节点数据。2.封装节点逻辑,3,每一批节点数量
    var timeChunk = function (ary, fn, count) {
        var obj, t;
        var len = ary.length;
        var start = function () {
            for (var i = 0; i < Math.min(count || 1, ary.length); i++) {
                var obj = ary.shift(); //删除一个数
                fn(obj);
            }
        };
        return function () {
            t = setInterval(function () {
                if (ary.length === 0) {
                    return clearInterval(t);
                }
                start();
            }, 200);
        };
    };
    //实际调用方法
    var ary = [];
    for (var i = 1; i <= 1000; i++) {
        ary.push(i);
    }
    var renderFirendList = timeChunk(ary, function (n) {
        var div = document.createElement('div');
        div.innerHTML = n;
        document.body.appendChild(div);
    }, 2);
    renderFirendList();
})()