JavaScript中的设计模式

本文深入探讨了JavaScript中的原型、闭包和高阶函数,通过实例展示了它们在对象继承、异步处理、模块封装等方面的应用。文中提到,JavaScript的原型模式是基于对象的继承,而闭包则可以实现变量的持久化和封装,避免了全局变量的污染。同时,高阶函数如回调函数和函数作为返回值,增强了代码的灵活性,实现了AOP(面向切面编程)。文章通过实例解释了如何利用这些特性来优化代码,提高可维护性和复用性。
摘要由CSDN通过智能技术生成
在这里插入代码片
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>sketches_for_jsdesigners</title>
</head>
<body>
<script>
/**********************************************************************************/
/*
    var duck = {
        duckSinging:function () {
            console.log('嘎嘎嘎');
        }
    };
    var chicken = {
        duckSinging:function () {
            console.log('嘎嘎嘎');
        }
    };

    var choir = []; //合唱团

    var joinChoir = function (animal) {
        if(animal && typeof animal.duckSinging === 'function'){
            choir.push(animal);
            console.log('恭喜加入合唱团');
            console.log('合唱团已有成员数量:' + choir.length);
        }
    };

    joinChoir(duck); //恭喜加入合唱团
    joinChoir(chicken); //恭喜加入合唱团
*/
/************************************************************************************/


/************************************************************************************/
/*
    var makeSound = function (animal) {
        if(animal instanceof Duck){
            console.log('嘎嘎嘎');
        }else if(animal instanceof Chicken){
            console.log('咯咯咯');
        }
    };

    var Duck = function () {

    };

    var Chicken = function () {

    };

    makeSound(new Duck()); //嘎嘎嘎
    makeSound(new Chicken()); //咯咯咯
*/
/************************************************************************************/


/************************************************************************************/
//prototype非常有用,用于添加函数以供对象访问。
//可以这么理解,prototype是下指针,_prop_是上指针,克隆者的_prop_指向被克隆者的prototype.不太准确。
//添加普通属性,直接使用.属性名=值就行。
/*
    var makeSound = function (animal) {
        animal.sound();
    };

    var Duck = function () {}

    Duck.prototype.sound = function () {
        console.log('嘎嘎嘎');
    };

    var Chicken = function () {};

    Chicken.prototype.sound = function () {
        console.log('咯咯咯');
    };

    makeSound(new Duck());//嘎嘎嘎
    makeSound(new Chicken());//咯咯咯

    var Dog = function () {}

    Dog.prototype.sound = function () {
        console.log('汪汪汪');
    }

    makeSound(new Dog());//汪汪汪
*/
//类型检查是在表现出对象多态性之前的一个绕不开的话题,但JavaScript是一门不必进行类型检查的动态类型语言
//静态类型的面向对象语言通常被设计为可以向上转型,当给一个类变量赋值时,这个变量的类型既可以使用这个类本身,
//也可以使用这个类的超类。
/************************************************************************************/


/************************************************************************************/
/*
    //java代码
    public abstract class Abimal{
        abstract void makeSound();//抽象方法
    }

    public class Chicken extends Animal{
        public void makeSound(){
            System.out.println("咯咯咯");
        }
    }

    public class Duck extends Animal{
        public void makeSound(){
            System.out.println("嘎嘎嘎");
        }
    }

    Animal duck = new Duck();
    Animal chicken = new Chicken();

    //现在剩下的就是让AnimalSound类的makeSound方法接受Animal类型的参数,
    //而不是具体的类型

    public class AnimalSound{
        public void makeSound(Animal animal()){//接受超类参数
            animal.makeSound();
        }
    }

    public class Test{
        public static void main(String args[]){
            AnimalSound animalSound = new AnimalSound();
            Animal duck = new Duck();
            Animal chicken = new Chicken();
            animalSound.makeSound(duck);//嘎嘎嘎
            animalSound.makeSound(chicken);//咯咯咯
        }
    }
*/
/************************************************************************************/


/***********************************************************************************/
//假设我们要编写一个地图应用,选择不同的地图API.
/*
    var googleMap = {
        show:function () {
            console.log('开始渲染谷歌地图');
        }
    };

    var renderMap = function () {
        googleMap.show();
    };

    renderMap();//开始渲染谷歌地图
*/
//加入百度地图
/*
    var googleMap = {
        show:function(){
            console.log('开始渲染谷歌地图');
        }
    };

    var baiduMap = {
        show:function () {
            console.log('开始渲染百度地图');
        }
    };

    var renderMap = function (type) {
        if(type === 'google'){
            googleMap.show();
        }else if(type === 'baidu'){
            baiduMap.show();
        }
    };
    renderMap('google');//开始渲染谷歌地图
    renderMap('baidu');//开始渲染百度地图
 */
//还可以更好,实现真正的多态。
/*
    var googleMap = {
        show:function(){
            console.log('开始渲染谷歌地图');
        }
    };

    var baiduMap = {
        show:function () {
            console.log('开始渲染百度地图');
        }
    };

    var renderMap = function (map) {
        if(map.show instanceof Function){
            map.show();
        }
    };

    renderMap(googleMap);//开始渲染谷歌地图
    renderMap(baiduMap);//开始渲染百度地图

    var sosoMap = {
        show:function () {
            console.log('开始渲染搜搜地图');
        }
    };

    renderMap(sosoMap);//开始渲染搜搜地图

//在这个例子中,我们假设每个地图的API提供展示地图的方法名都是show,
//在实际开发中也许不会如此顺利,这时候可以借助适配器模式来解决问题。
*/
/***************************************************************************************/


/***************************************************************************************/
//JavaScript并没有提供私有、共有,只能模拟。
/*
    var myObject = (function () {
        var _name = 'sven'; //私有变量
        return {
            getName:function () { //公开方法
                return _name;
            }
        }
    })();

    console.log(myObject.getName()); //输出:sven
    console.log(myObject._name); //输出:undefined
*/
/****************************************************************************************/


/****************************************************************************************/
//使用原型模式,关键在于语言本身是否提供了clone方法。
//ES5提供了Object.create方法,可以用来克隆对象。
/*
    var Plane = function () {
        this.blood = 100;
        this.attackLevel = 1;
        this.defenseLevel = 1;
    };

    var plane = new Plane();
    plane.blood = 500;
    plane.attackLevel = 10;
    plane.defenseLevel = 7;

    var clonePlane = Object.create(plane);
    console.log(clonePlane);//输出:Object{blood:500,attackLevel:10,defenseLevel:7}

// 在不支持Object.create方法的浏览器中,可以使用以下代码:
    Object.create = Object.create || function (obj) {
        var F = function () {};
        F.prototype = obj;

        return new F();
    }
*/
/****************************************************************************************/


/****************************************************************************************/
//JavaScript中的根对象是Object.prototype对象。这是一个空对象。
//我们在JavaScript遇到的每个对象,都是从它克隆而来。
/*
    var obj1 = new Object();
    var obj2 = {};

    console.log(Object.getPrototypeOf(obj1) === Object.prototype);//输出:true
    console.log(Object.getPrototypeOf(obj2) === Object.prototype);//输出:true
    console.log(obj1.__proto__ === Object.prototype);//输出:true
*/

/****************************************************************************************/


/****************************************************************************************/

//JavaScript的核心思想是克隆,而不是继承,而克隆是浏览器引擎实现的。
/*
    function Person(name) {
        this.name = name;
    };

    Person.prototype.getName = function () {
        return this.name
    };

    var a = new Person('sevn')

    console.log(a.name);//输出:sevn
    console.log(a.getName());//输出:sevn
    console.log(Object.getPrototypeOf(a) === Person.prototype);//输出:true
//JavaScript中没有类,JavaScript的函数既可以作为普通函数,也可以作为构造器。
//Person就是这样的。用new运算符来创建对象,其实也是克隆Object.prototype对象。
*/
/****************************************************************************************/


/****************************************************************************************/
/*
    function Person(name) {
        this.name = name;
    };

    Person.prototype.getName = function () {
        return this.name;
    };

    var objectFactory = function () {
        console.log(arguments)

        var obj = new Object(),//从Object.prototype上克隆一个空的对象
            Constructor = [].shift.call(arguments);//取得外部传入的构造器,此例是Person     即Constructor === Person
        //[].shift.call(arguments)这句真不是小白能看懂的,截取了入参的第一个。
        console.log(Constructor === Person)
        console.log(arguments)

        obj.__proto__ = Constructor.prototype;//指向正确的原型。
        var ret = Constructor.apply(obj,arguments);//借用外部传入的构造器给obj设置属性

        return typeof ret === 'object' ? ret : obj;//确保构造器总是会返回一个对象
    };

    var a = objectFactory(Person,'sven');//这两句效果相同
    // var a = new Person('sven');

    // console.log(a.name);//输出:sven
    // console.log(a.getName());//输出:sven
    // console.log(Object.getPrototypeOf(a) === Person.prototype);//输出:true
*/
/****************************************************************************************/


/****************************************************************************************/
/*
    var a = new Object();
    console.log(a.__proto__ === Object.prototype);//输出:true
*/
//实际上,_prop_就是对象跟”对象构造器的原型“联系起来的纽带。上一个例子中手动修改其指向。
/****************************************************************************************/


/****************************************************************************************/
//如果对象无法响应某个请求,它会把这个请求委托给它的构造器的原型,这就是原型继承的精髓。
/*
    var obj = {name: 'sven'};

    var A = function () {}

    A.prototype = obj;
    var a = new A();

    console.log(a.name);//输出:sven
//这就是我们最常用的原型继承方式。执行这些代码,引擎做了以下事情:
//首先,尝试遍历对象a中的所有属性,但没有找到name这个属性。
//查找name属性的这个请求被委托给对象a的构造器的原型,它被a._proto_记录着并且指向A.prototype,而A.prototype被设置为对象obj。
//在对象obj中找到name属性,并且返回它的值。
*/

/****************************************************************************************/


/****************************************************************************************/
//当我们期望得到一个“类”继承自另外一个"类"的效果时,往往会用下面的代码来模拟实现:
/*
    var A = function () {};
    A.prototype = {name:'sven'};

    var B = function () {};
    B.prototype = new A();

    var b = new B();
    console.log(b.name);//输出:sven
*/
//看看这段代码中,引擎做了声明:
//首先,尝试遍历对象b中的所有属性,但没有找到name这个属性。
//查找name属性的请求被委托给对象b的构造器的原型,它被b._proto_记录着并且指向B.prototype,
//而B.prototype被设置为一个通过new A()创建出来的对象。
//在该对象中依然没有找到name属性,于是请求被继续委托给这个对象构造器的原型A.prototype。
//在A.prototype中找到了name属性,并返回它的值。

//重点!!!
//和把B.prototype直接指向一个字面量对象相比,通过B.prototype = new A()形成的原型链比之前多了一层。
//但二者之间没有本质上的区别,都是将对象构造器的原型指向另外一个对象,继承总是发生在对象和对象之间。
//最后还要留意一点,原型链并不是无限长的。现在我们尝试访问对象a的address属性。
//而对象b和它构造器的原型上都没有address属性,那么这个请求会被最终传递到哪里呢?
//实际上,当请求达到A.prototype,并且在A.prototye中也没有找到address属性的时候,
//请求会被传递给A.prototype的构造器原型Object.prototype,显然Object.prototype中也没有
//address属性,但Object.prototype的原型是null,说明这时候原型链的后面已经没有别的节点了。
//所以该次请求就到此打住,a.address返回undefined。
//a.address  //输出:undefined

/****************************************************************************************/


/****************************************************************************************/
//重点!!!
/*
* 原型模式的未来
* 设计模式在很多时候其实都体现了语言的不足之处。
* 设计模式是对语言不足的补充,如果要使用设计模式,不如去找一门更好的语言。
* 但是JavaScript在未来的很长一段时间内都是web的唯一选择。
* 未来的JavaScript也有可能通过其自身实现设计模式的功能,不需要使用者去拐弯抹角的实现。
* 比如Object.create就是原型模式的天然实现。使用Object.create来完成原型继承看起来更能体现原型模式的精髓。
*
* */

/****************************************************************************************/


/****************************************************************************************/
//ES6中新的语法class,其背后仍然是通过原型机制来创建对象的。
/*
    class Animal{
        constructor(name){
            this.name = name;
        }

        getNamne(){
            return this.name;
        }
    }

    class Dog extends Animal{
        constructor(name){
            super(name);
        }

        speak(){
            return "woof";
        }
    }

    var dog = new Dog("Scamp");
    console.log(dog.getNamne() + ' says ' + dog.speak());
*/
//原型模式是一种设计模式,也是一种编程泛型,它构成了JavaScript这门语言的根本。

/****************************************************************************************/


/****************************************************************************************/
//在JavaScript编程中,this关键字总是让初学者感到迷惑,Function.prototype.call和Function.prototype.apply
//这两个方法也有着广泛的运用。有必要先了解。

//跟别的语言大相径庭的是,JavaScript的this总是指向一个对象,
//而具体指向哪个对象是在运行时基于函数的执行环境动态绑定的,
//而非函数被声明时的环境。

//除去不常用的with和eval的情况,具体到实际应用中,this的指向大致可以分为以下4种:
//作为对象的方法调用。
//作为普通函数调用。
//构造器调用
//Function.prototype.call或Function.prototype.apply调用。下面分情况介绍。

/****************************************************************************************/


/****************************************************************************************/
//作为对象的方法调用
//当函数作为对象的方法被调用时,this指向该对象:
/*
    var obj = {
        a:1,
        getA:function () {
            alert(this === obj);//输出:true
            alert(this.a);//输出:1
        }
    };

    obj.getA();
*/

/****************************************************************************************/


/****************************************************************************************/
//作为普通函数调用
//当函数不作为对象的属性被调用时,也就是我们常说的普通函数方式,此时的this总是指向全局对象。
//在浏览器的JavaScript里,这个全局对象是window对象。
/*
    window.name = 'globalName';

    var getName = function () {
        return this.name;
    };

    console.log(getName());//输出:globalName
*/

/*
    window.name = 'globalName';

    var myObject = {
        name:'sven',
        getName:function () {
            return this.name;
        }
    };

    var getName = myObject.getName;
    console.log(getName());//globalName
*/

//有时候我们会遇到一些困扰,比如在div节点的事件函数内部,有一个局部的callback方法,
//callback被作为普通函数调用时,callback内部的this指向了window,但我们往往是想
//让它指向该div节点,见如下代码:

    // <div id="div1">我是一个div</div>

    // window.id = 'window';
    //
    // document.getElementById('div1').onclick = function () {
    //     alert(this.id);//输出:'div1'
    //
    //     var callback = function () {
    //         alert(this.id);//输出:'window'
    //     }
    //
    //     callback()
    // }


    //此时有一种简单的解决方案,可以用一个变量保存div节点的引用:
    // document.getElementById('div1').onclick = function () {
    //     var that = this;//保存div的引用
    //
    //     var callback = function () {
    //         alert(that.id);//输出:'div1'
    //     }
    //
    //     callback();
    // }


    //在ES5的strict模式下,以下情况的this已经被规定为不会指向全局对象,而是undefined:
    // function func() {
    //     "use strict";
    //     alert(this);//输出:undefined
    //     console.log(this);
    // }
    //
    // func();

/****************************************************************************************/


/****************************************************************************************/
//JavaScript中没有类,但是可以从构造器中创建对象,同时也提供了new运算符,使得构造器看起来更像一个类。
//除了宿主提供的一些内置函数,大部分JavaScript函数都可以当作构造器使用。构造器的外表跟普通函数一模一样,
//它们的区别在于被调用的方式。当用new运算符调用函数时,该函数总会返回一个对象,通常情况下,构造器里的this
//就指向返回的这个对象。

/*
    var MyClass = function () {
        this.name = "sven";
    };

    var obj = new MyClass();
    alert(obj.name); //输出:sven
*/

/*
    var MyClass = function () {
        this.name = 'sven';
        return{//显示地返回一个对象
            name:'anne'
        }
    };

    var obj = new MyClass();
    alert(obj.name);//输出:anne
*/

/*
    var MyClass = function () {
        this.name = 'sven'
        return 'anne';//返回string类型
    };

    var obj = new MyClass();
    alert(obj.name);//输出:sven
*/

/****************************************************************************************/


/****************************************************************************************/
// Function.prototype.call或Function.prototype.apply调用
/*
    var obj1 = {
        name:'sven',
        getName:function () {
            return this.name;
        }
    };

    var obj2 = {
        name:'anne'
    };

    console.log(obj1.getName());//输出:sven
    console.log(obj1.getName().call(obj2));//输出:anne
*/
//call和apply方法能很好地体现JavaScript的函数式语言特性

/****************************************************************************************/


/****************************************************************************************/
//丢失的this
//这是一个经常遇到的问题
/*
    var obj = {
        myName:'sven',
        getName:function () {
            return this.myName;
        }
    };

    console.log(obj.getName());//输出:sven

    var getName2 = obj.getName();
    console.log(getName2());//输出:undefined

    //当调用obj.getName时,getName方法是作为obj对象的属性被调用的,this指向对象obj。
    //当用另外一个变量getName2来引用obj.getName,并且调用getName2时,是普通函数调用方式,this指向全局window。
*/
/****************************************************************************************/


/****************************************************************************************/
//我们可以尝试利用apply把document当作this传入getId函数,帮助“修正”this,
//也就是强制让getElementById的this绑定为document
/*
    document.getElementById = (function (func) {
        console.log(func.name)
        return function () {
            return func.apply(document,arguments);
        }
    })(document.getElementById);

    var getId = document.getElementById;
    var div = getId('div1');

    console.log(div)//输出:div1
*/
/****************************************************************************************/


/****************************************************************************************/
//JavaScript的函数无论指定形参与否,arguments都是以一个可变数组对象的形式来记录函数的参数
//call和apply没有太大的区别。
//call的第一个参数指定函数体内this对象的指向,第二个参数是可变数组.
//apply的第一个参数指定函数体内this对象的指向,其余参数是传统形参的形式.
//call是包装在apply上面的一颗语法糖.


/****************************************************************************************/


/****************************************************************************************/
/*
    var add = function () {
        this.a = 1,
        this.b = 1,
        this.c = 1;
    }

    var sel =function () {
        this.a = 1,
        this.b = 1,
        this.c = 1;

        return {
           a: this.a,
           b:this.b
        }
    }

    var del = function () {
        console.log(this)
    }
    del()//作为普通函数调用,this指向window
    new del()//作为构造器调用,this指向del

    var obj1 = new add()
    var obj2 = new sel()

    console.log(obj1)
    console.log(obj2)
*/
/****************************************************************************************/


/****************************************************************************************/
//大部分高级浏览器都实现了内置的Function.prototype.bind,用来指定函数内部的this指向,模拟实现:
/*
    Function.prototype.bind =  function (context) {
        var self = this;//保存原函数   ---> func
        return function () {//返回一个新函数
            return self.apply(context,arguments);//执行新函数的时候,会把之前传入的context当作新函数体内的this
        }
    };

    var obj = {
        name: 'sven'
    };

    var func = function () {
        alert(this.name);
    }.bind(obj);

    func();
*/
//我们通过Function.prototype.bind来"包装"func函数,并且传入一个对象context当作参数,
//这个context对象就是我们想修正的this对象。
//在Function.prototype.bind的内部实现中,我们先把func函数的引用保存起来,然后返回一个
//新的函数。当我们在将来执行func函数时,实际上先执行的是这个刚刚返回的新函数。在新函数内部,
//self.apply(context,arguments)这句代码才是执行原来的func函数,并且指定context对象为
//func函数体内的this。
//这是一个简化版的Function.prototype.bind实现,通常我们还会把它实现得稍微复杂一点,
//使得可以往func函数中预先填入一些参数:

/*
    Function.prototype.bind = function () {
        var self = this,//保存原函数   ---> func
            context = [].shift.call(arguments),//需要绑定的this上下文    ---> obj
            args = [].slice.call(arguments);//剩余的参数转成数组   ---> [1,2]
        return function () {//返回一个新的函数
            return self.apply(context,[].concat.call(args,[].slice.call(arguments)));
            //执行新的函数的时候,会把之前传入的context当作新函数体内的this
            //并且组合两次分别传入的参数,作为新函数的参数
        }
    };

    var obj = {
        name:'sven'
    };

    var func = function (a,b,c,d) {
        console.log(this.name);
        console.log([a,b,c,d])
    }.bind(obj,1,2);

    func(3,4);
*/
//上面这个例子比较经典,需要好好琢磨。

/****************************************************************************************/


/****************************************************************************************/
//借用其它对象的方法,原书33页。借用构造函数。
/*
    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('sven');
    console.log(b.getName());
*/


//借用方法的第二种运用场景跟我们的关系更加密切 。
//函数的参数列表arguyments是一个类数组对象,虽然它也有“下标”,但它并非真正的数组,
//所以也不能像数组一样,进行排序操作或者往集合里添加一个新元素。这种情况下,我们常常
//会借用Array.prototype对象上的方法。比如想往arguments中添加一个新的元素,通常会
//借用Array.prototype.push。
//想把arguments转成真正的数组的时候,可以借用Array.prototype.slice方法,
//想截取arguments列表中的头一个元素时,又可以借用Array.prototype.shift方法。

/*
(function () {
    Array.prototype.push.call(arguments,3);
    console.log(arguments);
})(1,2);
*/

/****************************************************************************************/


/****************************************************************************************/

//由此可以推断,我们可以把“任意“对象传入Array.prototype.push.
/*
    var a = {};
    Array.prototype.push.call(a,'first');

    alert(a.length);//输出:1
    alert(a[0]);//输出 first
*/

/****************************************************************************************/


/****************************************************************************************/
//第三章 闭包和高阶函数
//
//本章主要挑选了闭包和高阶函数进行讲解。在JavaScript版本的设计模式中,许多模式都可以用闭包和高阶函数来实现。
//对于JavaScript程序员来说,闭包(closure)是一个难懂又必须征服的概念。闭包的形成与变量的作用域以及变量的
//生存周期密切相关。

//当在函数中声明一个变量的时候,如果该变量前面没有带上关键字var,这个变量就会成为全局变量,这当然是一种
//容易造成命名冲突的做法。
//另外一种情况是用var关键字在函数中声明变量,这时候的变量即是局部变量,只有在该函数内部才能访问到这个变量,
//在函数外面是访问不到的。
//在JavaScript中,函数可以用来创造函数作用域。此时的函数像一层半透明的玻璃,在函数里面可以看到外面的变量,
//而在函数外面则无法看到函数里面的变量。这是因为当在函数中搜索一个变量的时候,如果该函数内并没有声明这个变量,
//那么此处搜索的过程会随着代码的执行环境创建的作用域链往外层逐层搜索,一直搜索到全局对象为止。
//对于全局变量来说,全局变量的生存周期当然是永久的,除非我们主动销毁这个全局变量。
//而对于在函数内用var关键字声明的局部变量来说,当退出函数时,这些局部变量即失去了它们的价值,
//它们都会随着函数调用的结束而被销毁。

//然而闭包是特殊的局部变量,是“全局化”的局部变量,
/****************************************************************************************/
//闭包
/*
    var func = function () {
        var a = 1;
        return function(){
            a++;
            alert(a);
        }
    };

    // var f = func();
    //
    // f();//2
    // f();//3
    // f();//4
    // f();//5

    func()();//2
    func()();//2
    func()();//2
    func()();//2
*/
//当退出函数后,局部变量a并没有消失,而是似乎一直在某个地方存活着。这是因为当执行var f = func()时,f返回了
//一个匿名函数的引用,它可以访问到func()被调用时产生的环境,而局部变量a一直处在这个环境里。既然局部变量所在的
//环境还能被外界访问,这个局部变量就有了不被销毁的理由。在这里产生了一个闭包结构,局部变量的生命看起来被延续了。

//这个地方很玄乎的。如果没有var f = func(),而是func()();连续执行四次,并不会形成闭包。
//之所以形成闭包是因为f是全局变量,它是对匿名函数的引用,而匿名函数中又存在变量a,
//所以a就变成了一个"全局化“的局部变量,也就形成了闭包。

/****************************************************************************************/


/****************************************************************************************/
//下面来看闭包在异步触发事件中的应用
/*
    var nodes = document.getElementsByTagName('div');

    for (var i = 0, len = nodes.length; i < len; i++){
        nodes[i].onclick = function () {
            alert(i);
        }
    } ;
*/
//无论点击哪个div,最后弹出的结果都是5.这是因为div节点的onclick事件是被异步触发的,当事件被触发的时候,
//for循环早已结束,此时变量i的值已经是5,所以在div是onclick事件函数中顺着作用域链从内到外查找变量i时,
//查找到的值总是5。总之,记住异步触发事件不保证同步就是了。

/*
    var nodes = document.getElementsByTagName('div');
    for(var i = 0, len = nodes.length; i < len; i++){
        (function (i) {
            nodes[i].onclick = function(){
                console.log(i)
            }
        })(i)
    };
*/

//根据同样的道理,我们还可以编写下面的代码:
/*
    var Type = {};
    for (var i = 0, type; type = [ 'String','Array','Number' ][i++];){
        (function (type) {
            Type['is' + type ] = function (obj) {
                return Object.prototype.toString.call(obj) === '[object ' + type + ']';
            }
        })(type)
    };

    console.log(Type.isArray([]));
    console.log(Type.isString('str'));
    console.log(Type)

*/

/****************************************************************************************/



/****************************************************************************************/
//闭包的更多作用
//封装变量,闭包可以帮助把一些不需要暴露在全局的变量封装成“私有变量"
/*
    var mult = function () {
        var a = 1;
        for (var i = 0, l = arguments.length; i < l; i++){
            a = a * arguments[i];
        }
        return a;
    };
*/

//加入缓存机制
/*
    var cache = {}

    var mult = function () {
        var args = Array.prototype.join.call(arguments,',');
        if(cache[args]){
            return cache[args];
        }

        var a = 1;

        for (var i = 0, l = arguments.length; i < l; i++){
            a = a * arguments[i];
        }

        return cache[args] = a;
    };

    alert(mult(1,2,3));
    alert(mult(1,2,3));
*/

//把cache这个变量封装到mult函数中使用,减少页面中的全局变量。
/*
    var mult = (function () {
        var cache = {};
        return function () {
            var args = Array.prototype.join.call(arguments,',');
            if(args in cache){
                return cache[args];
            }
            var a = 1;
            for (var i = 0, l = arguments.length; i < l; i++){
                a = a * arguments[i];
            }
            return cache[args] = a;
        }
    })()
    alert(mult(1,2,3))
*/

//这种时候,闭包就产生了,cache就是"全局化的"局部变量。


//提炼函数是代码重构中的一种常见技巧。
//如果在一个大函数中有一些代码块能够独立出来,我们常常把这些代码块封装在独立的小函数里面。
/*
    var mult = (function () {
        var cache = {};
        var calculate = function () {
            var a = 1;
            for (var i = 0, l = arguments.length; i < l; i++){
                a = a * arguments[i];
            }
            return a;
        };

        return function () {
            var args = Array.prototype.join.call(arguments,',');
            if (args in cache){
                return cache[args];
            }
            return cache[args] = calculate.apply(null,arguments);
        }
    })()

    console.log(mult(1,2,3))

*/

/****************************************************************************************/


/****************************************************************************************/
//延续局部变量的寿命,其实和之前那个按钮有点相似,就是异步事件的“同步”问题,
//异步事件不会去等待其它代码执行完毕再执行,有可能同步的代码还没有准备好,异步代码已经执行完了。

/*
    var report = function (src) {
        var img = new Image();
        img.src = src;
    } ;
    report('http://xxx.com/getUserInfo');
*/

//report函数发起http请求有可能失败,当report函数的调用结束后,img局部变量随机被销毁,
//而此时或许还没有来得及发出http请求,所以此次请求就会丢失。
//这就是要解决异步事件提前结束或者迟滞的问题。

//现在把img变量用闭包封闭起来,便能解决请求丢失的问题:
/*
    var report = (function () {
        var imgs = [];
        return function (src) {
            var img = new Image();
            imgs.push(img);
            img.src = src;
        }
    })();
*/

//总结闭包变量就是“全局化”的局部变量
/****************************************************************************************/


/****************************************************************************************/
//闭包和面向对象设计
//过程与数据的结合是形容面向对象中的“对象”时经常使用的表达。
//对象以方法的形式包含了过程,而闭包则是在过程中以环境的形式包含了数据。
//通常用面向对象思想能实现的功能,用闭包也能实现。反之亦然。


//用闭包来实现一个完整的面向对象系统。
/*
    var extent = function () {
        var value = 0;
        return {
            call: function () {
                value ++;
                console.log(value);
            }
        }
    };

    var extent = extent();

    extent.call();//输出1
    extent.call();//输出2
    extent.call();//输出3
*/

//如果换成面向对象的写法,就是:
/*
    var extent = {
        value:0,
        call:function () {
            this.value ++;
            console.log(this.value);
        }
    };

    extent.call();//输出:1
    extent.call();//输出:2
    extent.call();//输出:3
*/

//或者:
/*
    var Extent = function () {
        this.value = 0;
    };

    Extent.prototype.call = function () {
        this.value ++;
        console.log(this.value);
    };

    var extent = new Extent();

    extent.call();
    extent.call();
    extent.call();
*/
//这种设计模式在实际使用中,经常使用。

/****************************************************************************************/


/****************************************************************************************/
//用闭包实现命令模式
/*
    var Tv = {
        open:function () {
            console.log('打开电视机');
        },
        close:function () {
            console.log('关上电视机');
        }
    };

    var OpenTvCommand = function (receiver) {
        this.receiver = receiver;
    };

    OpenTvCommand.prototype.execute = function () {
        this.receiver.open();//执行命令,打开电视机
    };

    OpenTvCommand.prototype.undo = function () {
        this.receiver.close();//撤销命令,关闭电视机
    };

    var setCommand = function (command) {
        document.getElementById('execute').onclick = function () {
            command.execute();//输出:打开电视机
        }
        document.getElementById('undo').onclick = function () {
            command.undo();//输出:关闭电视机
        }
    }

    setCommand(new OpenTvCommand(Tv));
*/

//命令模式的意图是把请求封装为对象,从而分离请求的发起者和请求的接受者(执行者)之间的耦合关系。
//在命令被执行之前,可以预先往命令对象中植入命令的接受者。

//但在JavaScript中,函数作为一等函数,本身就可以四处传递,用函数对象而不是普通对象来封装请求显得更加简单和自然。
//如果需要往函数对象中预先植入命令的接受者,那么闭包可以完成这个工作。在面向对象版本的命令模式中,
//预先植入的命令接受者被当成对象的属性保存起来;而在闭包版本的命令模式中,命令接受者会被封闭在闭包形成的环境中。
//代码如下:
/*
    var Tv = {
        open:function () {
            console.log('打开电视机');
        },
        close:function () {
            console.log('关上电视机');
        }
    };

    var createCommand = function (receiver) {

        var execute = function () {
            return receiver.open();//执行命令,打开电视机
        }

        var undo = function () {
            return receiver.close();//执行命令,关闭电视机
        }

        return{
            execute:execute,
            undo:undo
        }
    };

    var setCommand = function (command) {
        document.getElementById('execute').onclick = function () {
            command.execute();//输出:打开电视机
        }
        document.getElementById('undo').onclick = function () {
            command.undo();
        }
    };

    setCommand(createCommand(Tv));
*/

/****************************************************************************************/


/****************************************************************************************/
//闭包与内存管理
//闭包是一个非常强大的特性,但人们对其也有诸多的误解。
//一种耸人听闻的说法是闭包会造成内存泄漏,所以要尽量减少闭包的使用。

//局部变量本来应该在函数退出的时候被解除引用,
//但如果局部变量被封闭在闭包形成的环境中,那么这个局部变量就能一直生产下去。
//从这个意义上看,闭包的确会使一些数据无法被即时销毁。使用闭包的一部分原因是我们选择主动把一些变量封闭在闭包中,
//因为可能在以后还需要使用这些变量,把这些变量放在闭包中和放在全局作用域,
//对内存方面的影响是一致的,这里并不能说成是内存泄漏。
//如果在将来需要回收这些变量,我们可以手动把这些变量设为null。

//跟闭包和内存泄漏有关系的地方是,使用闭包的同时比较容易形成循环引用,如果闭包的作用域链中保存着一些DOM节点,
//这时候就有可能造成内存泄漏。但这本身并非闭包的问题,也并非JavaScript的问题。

/****************************************************************************************/


/****************************************************************************************/
//高阶函数
//高阶函数是指至少满足下列条件之一的函数。
//1、函数可以作为参数被传递;
//2、函数可以作为返回值输出;

//JavaScript语言中的函数显然满足高阶函数的条件,在实际开发中,
//无论是将函数当作参数传递,还是让函数的执行结果返回另外一个函数,这两种情形都有很多应用场景。


/****************************************************************************************/


/****************************************************************************************/
//函数作为参数传递
//把函数当作参数传递,这代表我们可以抽离出一部分容易变化的业务逻辑,
//把这部分业务逻辑放在函数参数中,这样一来可以分离业务代码中变化与不变的部分。
//其中一个重要应用场景就是常见的回调函数。

//回调函数
//在ajax异步请求的应用中,回调函数的使用非常频繁。
//当我们想在ajax请求返回之后做一些事情,但又并不知道请求返回的确切时间时,
//最常见的方案就是把callback函数当作参数传入发起ajax请求的方法中,
//待请求完成之后执行callback函数:
/*
    var getUserInfo = function (userId,callback) {
        $.ajax('http://xxx.com/getUserInfo?' + userId,function (data) {
            if(typeof callback === 'function'){
                callback(data)
            }
        });
    }

    getUserInfo(13157,function (data) {
        alert(data.userName);
    });
*/


//回调函数的应用不仅只在异步请求中,当一个函数不适合执行一些请求时,我们也可以把这些请求封装成一个函数,
//并把它作为参数传递给另外一个函数,“委托”给另外一个函数来执行。
/*
    var appendDiv = function () {
        for(var i = 0; i < 100; i++){
            var div = document.createElement('div');
            div.innerHTML = i;
            document.body.appendChild(div);
            div.style.display = 'none';
        }
    };

    appendDiv();
*/

//把div.style.display = 'none'的逻辑硬编码在appendDiv里显然是不合理的,appendDiv未免有点个性化,
//成为了一个难以复用的函数,并不是每个人创建了节点之后就希望它们立刻被隐藏。

//于是我们把div.style.display = 'none'这行代码抽出来,用回调函数的形式传入appendDiv方法:
/*
    var appendDiv = function (callback) {
        for(var i = 0; i < 100; i++){
            var div = document.createElement('div');
            div.innerHTML = i;
            document.body.appendChild(div);
            if(typeof callback === 'function'){
                callback(div);
            }
        }
    };

    appendDiv(function (node) {
        node.style.display = 'none';
    });
*/

//可以看到,隐藏节点的请求实际上是由客户发起的,但是客户并不知道节点什么时候会创建好,
//于是把隐藏节点的逻辑放在回调函数中,“委托:给appendDiv方法。appendDiv方法当然知道
//节点什么时候创建好,所以在节点创建好的时候,appendDiv会执行之前客户传入的回调函数。

/****************************************************************************************/


/****************************************************************************************/
//书上46页,接下来是我们的的老朋友,我认识回调函数的第一个例子。sort函数
//Array.prototype.sort接收一个函数当作参数,这个函数里面封装了数组元素的排序规则。
//从Array.prototype.sort的使用可以看到,我们的目的是对数组进行排序,这是不变的部分;
//而使用什么规则去排序,则是可变的部分。把可变的部分封装在函数参数里,动态传入Array.prototype.sort,使
//Array.prototype.sort方法成为了一个非常灵活的方法。

//从小到大排序
/*
    console.log([1,4,3].sort(function (a,b) {
        return a - b;
    }))

    console.log([1,4,3].sort((a,b) => a-b))
*/
//上面两种写法等效。

/****************************************************************************************/


/****************************************************************************************/
//书上46页
//函数作为返回值输出
//相比把函数当作参数传递,函数当作返回值输出的应用场景也许更多,也更能体现函数式编程的巧妙。
//让函数继续返回一个可执行的函数,意味着运算过程是可延续的。


//判断数据的类型
/*
    var isString = function (obj) {
        return Object.prototype.toString.call(obj) === '[object String]';
    };

    var isArray = function (obj) {
        return Object.prototype.toString.call(obj) === '[object Array]';
    };

    var isNumber = function (obj) {
        return Object.prototype.toString.call(obj) === '[object Number]';
    }
*/

//我们发现,这些函数的大部分实现都是相同的,不同的只是Object.prototype.toString.call(obj)返回的字符串。
//为了避免多余的代码,我们尝试把这些字符串作为参数提前植入isType函数
/*
    var isType = function (type) {
        return function (obj) {
            return Object.prototype.toString.call(obj) === '[object ' + type + ']';
        }
    };

    var isString = isType('String');
    var isArray = isType('Array');
    var isNumber = isType('Number');

    console.log(isArray([1,2,3]));
*/

//我们还可以用循环语句,来批量注册这些isType函数:
/*
    var Type = {};

    for(var i = 0, type; type = ['String','Array','Number'][i++];){
        (function (type) {
            Type['is' + type ] = function (obj) {
                return Object.prototype.toString.call(obj) === '[object ' + type + ']';
            }
        })(type)
    };

    console.log(Type.isArray([]));
    console.log(Type.isString('str'));
*/
/****************************************************************************************/


/****************************************************************************************/
//下面是一个单例模式的例子,在第三部分设计模式的学习中,我们将进行更深入的讲解。
//这里暂且只了解其代码实现:
/*
    var getSingle = function (fn) {
        var ret;
        return function () {
            return ret || (ret = fn.apply(this,arguments));
        };
    };

    var getScript = getSingle(function () {
        return document.createElement('script');
    });

    var script1 = getScript();
    var script2 = getScript();

    alert(script1 === script2)//输出:true
*/
//这个高阶函数的例子,既把函数当作参数传递,又让函数执行后返回了另外一个函数。

/****************************************************************************************/



/****************************************************************************************/
//书上48页,高阶函数实现AOP
//AOP(面向切面编程)的主要作用是把一些跟核心业务逻辑模块无关的功能抽离出来,
//这些跟业务逻辑无关的功能通常包括日志统计、安全控制、异常处理等。把这些功能抽离出来之后,
//再通过“动态织入”的方式掺入业务逻辑模块中。这样做的好处首先是可以保持业务逻辑模块的纯净和高内聚性,
//其次是可以很方便地复用统计等功能模块。

//在java语言中,可以通过反射和动态代理机制来实现AOP技术。而在JavaScript这种动态语言中,
//AOP的实现更加简单,这是JavaScript与生俱来的能力。

//通常,在JavaScript中实现AOP,都是指把一个函数“动态织入”到另外一个函数之中,具体的实现技术有很多,
//本节我们通过扩展Function.prototype来做到这一点。

    Function.prototype.before = function (beforefn) {
        var _self = this;//保存原函数的引用
        console.log(this)
        return function () {//返回包含了原函数和新函数的“代理”函数
            beforefn.apply(this,arguments);//执行新函数,修正this
            return _self.apply(this,arguments);//执行原函数
        }
    };

    Function.prototype.after = function (afterfn) {
        var _self = this;
        console.log(this)
        return function () {
            //这三句的顺序大有名堂,如果修改,必然破坏执行顺序。
            var ret = _self.apply(this,arguments);
            afterfn.apply(this,arguments);
            return ret;

            //改成下面这样就坏事了
            // afterfn.apply(this,arguments);
            // return _self.apply(this,arguments)
        }
    };

    var func = function () {
        console.log(2);
    };


    // func = func.before(function () {
    //     console.log(1);
    // }).after(function () {
    //     console.log(3);
    // })

//这里面涉及到函数的嵌套,下面将会详细解读。
//这里水很深,this也在不断的切换,this由func转为before返回的匿名转为after返回的匿名;
//效果相同,面向切面,名不虚传,无论是先给before还是先给after,执行顺序不变

    func = func.after(function () {
        console.log(3);
    }).before(function () {
        console.log(1);
    })

    console.log(func)
    func();

//这里水很深,this也在不断的切换,this由func转为after返回的匿名转为before返回的匿名;


/*
    Function.prototype.before = function (beforefn) {
        var _self = this;
        //console.log(this) //这里的this指向匿名函数**2**
        return function () {//**3**
            beforefn.apply(this,arguments);
            return _self.apply(this,arguments);
        }
    };

    Function.prototype.after = function (afterfn) {
        var _self = this;
        //console.log(this)   //这里的this指向匿名函数**1**
        return function () {//匿名函数**2**
            var ret = _self.apply(this,arguments);
            afterfn.apply(this,arguments);
            return ret;
        }
    };

    var func = function () {//匿名函数**1**
        console.log(2);
    };

    func = func.after(function () {
        console.log(3);
    }).before(function () {
        console.log(1);
    })

    //console.log(func) //这里的func是匿名函数**3**,这样就形成了一种函数嵌套,
    //在执行的时候又会像多米诺骨牌一样产生连锁反应,从后往前去回溯。“回溯”
    func();//这里先去找匿名函数**3**,执行beforefn.apply(this,arguments)也就是打印1 ,
    //然后执行return _self.apply(this,arguments),也就是去找匿名函数**2**,
    //执行var ret = _self.apply(this,arguments)也就是打印1,
    //执行afterfn.apply(this,arguments)也就是打印3

    //这样分析还不能体会到afterfn函数中return ret的妙处,再反向执行一下。

*/

/*
    Function.prototype.before = function (beforefn) {
        var _self = this;
        console.log(this)
        return function () {
            beforefn.apply(this,arguments);
            return _self.apply(this,arguments);
        }
    };

    Function.prototype.after = function (afterfn) {
        var _self = this;
        console.log(this)
        return function () {
            var ret = _self.apply(this,arguments);
            afterfn.apply(this,arguments);
            return ret;
        }
    };

    var func = function () {
        console.log(2);
    };

    func = func.before(function () {
        console.log(1);
    }).after(function () {
        console.log(3);
    })

    //console.log(func)
    func();
*/
/****************************************************************************************/



</script>
</body>
</html>




评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值