diy1818

Good children, Constantly learning, Constantly growing.

javascript 面向对象编程

实物的抽象--对象

当实物抽象成"对象",实物之间的关系 就变成了"对象"之间的关系

对象是一个模型,封装了"属性"(property)和"方法"(method)

所谓"属性"就是对象的状态,所谓"方法"就是对象的行为

类和构造函数

面向对象就是要生成对象,生成对象就需要一个模板,用来表示某一类实物的共同特征,然后"对象"根据这个模板生成实例."类"就是对象 的模板,对象就是"类"的实例.

javascript没有"类",只有用构造函数(constructor)作为对象的模板.构造函数的最大特点就是函数体内使用了this关键字,代表了所要生成的对象实例.生成对象 的时候,必需用new命令,调用 构造函数.

用构造函数生成一个对象实例后, 构造函数内部的this代表被生成的实例对象.

基本用法

    //构造函数
    var con_obj = function(){
        this.speed = 100;
    };
    //实例化对象
    var v = new con_obj();
    console.log(v.speed);//100

更简单的原型语法

    function Person(){}
    Person.prototype={
        name:'anry',
        age:27,
        job:"web",
        sayName:function(){
            alert(this.name);
        }
    };

    var key = Object.keys(Person.prototype);
    alert(key);
    var p1 = new Person();
    p1.name = 'coco';
    p1.age= 23;
    var p1key = Object.keys(p1);
    alert(p1key);

注意:如果不使用new而直接调用构造函数,this代表的是全局对象,构造函数就变成了普通函数,并不会生成实例对象.

instanceof运算符

运算符用来确定一个对象是否为某个构造函数的实例.

    //构造函数
    var con_obj = function(){
        this.speed = 100;
    };
    //实例化对象
    var v = new con_obj();
    console.log(v instanceof con_obj);//true

instanceof运算符的左边放置对象,右边放置构造函数.在javascript中,只要是对象,就有对应的构造函数.因此,instanceof运算符可以用来判断值的类型.

    [] instanceof Array //true
    ({}) instanceof Object //true

注意:原始类型的值不是对象,所以不能用instanceof运算符来判断类型,但可以用 typeof 可判断.

instanceof运算符实质就是找出instanceof运算符左侧的实例对象的原型键上各个原型的constructor属性,然后确定这些属性中是否包含instanceof运算符右侧的构造函数.

this

javascript允许动态调用对象的属性.如果a对象和b对象都有一个m属性,javascript在调用m时,会动态切换,这时可能会属a也可以属b,这就要靠this来办到.

如果处在全局环境,this就指顶层对象 (window),如果不处在全局环境,this就指对属性求值时所在的对象.

全局环境

    this === window //true

构造函数

构造函数中的this,指的是实例对象

    var con_obj = function(p){
        this.p = p;
    }

    con_obj.prototype.m = function(){
        return this.p;
    }

    var test_o = new con_obj('test');
    test_o.p //test
    test_o.m() // test

上面代码定义了构造函数con_obj.由于this指向实例对象,所以在构造函数内部定义了this.p.就相当于定义的实例对象里有一个p属性,然后m方法就可以返回这个实例化的P属性.

普通函数

普通函数内部的this,指的是函数运行时所在的对象.

    function f(){
        console.log(this.m);
    }
    var m = 1;
    f(); //1

    //函数f是在全局环境中运行的,this指向的就是全局对象window,所以可以读取全局变量m的值.
    function f(){
        console.log(this.m);
    }

    var obj = {m:1};
    obj.f = f;
    obj.f() //1

    var obj2 = {m : 2};
    obj2.f = f;
    obj2.f() //2

    //当f在obj下运行时,this指向的是obj, 当在obj2下运行时,this指向的是obj2.

有时,某个方法m位于多层对象的内部,为了避免this指向全局对象,可以只将m函数所在的对象赋值给上层对像.如下

    var a = {
        b:{
            m:function(){
                console.log(this.p)
            },
            p:'test'
        }
    };

    var h = a.b;
    h.m();//test

结论

如果是在全局环境下运行,就代表全局对象window; 如果是在某个对象中运行,就代表该对象. 函数中使用多个this的方法如下:

    var o = {
        f1: function(){
            console.log(this);//object
            var that = this;
            var f2 = function(){
                console.log(that);//object
            }();
        }
    }

    o.f1();

    //上面代码定义了变量that,固定指向外层的this,
    然后在内层使用that,就不会发生this的改变.

call 方法

函数的call方法可以改变this指向的对象.然后再调用该函数.

    fun.call(contest,[arg1],[arg2],...);

    //call方法的第一个参数就是this所要指向的那个对象,
    后面的参数则是函数调用时所需要的参数,
    如果this所要指向的那个对象,
    设定为null或undefined,则等同指定全局对象.

    var n = 123;

    var o = { n : 456 };

    function a() {
        console.log(this.n);
    }

    a.call() // 123
    a.call(null) // 123
    a.call(undefined) // 123
    a.call(window) // 123
    a.call(o) // 456

apply方法

apply方法与call方法的作用类似,也是改变this指向的对象. 区别在于apply方法的第二个参数是一个数组,该数组的所有成员 依次作为参数,传入原函数.

        fun.apply(context,[arg1,arg2,...]);

        function f(x,y){ console.log(x+y); }
        f.call(null,1,1) // 2
        f.apply(null,[1,1]) // 2

        //上面的f函数本来接受两个参数,
        使用apply方法以后,就变成可以接受一个数组作为参数
    

利用apply这一点可以找出数组中最大元素的函数.

    //找出数组中最大元素
    var a = [10,2,3,4,15,16,9];
    Math.max.apply(null,a);
    // 16

    通过apply方法,利用构造函数Array将数组的空元素变成undefined。

    Array.apply(null, ["a",,"b"])
    // [ 'a', undefined, 'b' ]

    空元素与undefined的差别在于,数组的foreach方法会跳过空元素,
    但是不会跳过undefined。因此,遍历内部元素的时候,会得到不同的结果。

    var a = ["a",,"b"];

    function print(i) {
        console.log(i);
    }

    a.forEach(print)
    // a
    // b

    Array.apply(null,a).forEach(print)
    // a
    // undefined
    // b

    利用数组对象的slice方法,可以将一个类似数组的对象
    (比如arguments对象)转为真正的数组。但被处理的对象必须有length属性,
    以及相对应的数字键.

    Array.prototype.slice.apply({0:1,length:1})
    // [1]

    Array.prototype.slice.apply({0:1})
    // []

    Array.prototype.slice.apply({0:1,length:2})
    // [1, undefined]

    Array.prototype.slice.apply({length:1})
    // [undefined]

bind方法

bind 方法就是单纯了将函数体内的this绑定到某个对象,然后返回一个新函数, 它他call方法和apply方法更进一步的是,除了绑定this以外,还可以绑定原函数的参数.

    var o1 = new Object();
    o1.p = 123;
    o1.m = function (){
        console.log(this.p);
    };

    o1.m() // 123

    var o2 = new Object();
    o2.p = 456;
    o2.m = o1.m;

    o2.m() // 456

    o2.m = o1.m.bind(o1);
    o2.m() // 123

上面代码使用bind方法将o1.m方法绑定到o1以后,在o2对象上调用o1.m的时候,o1.m函数体内部的this.p就不再到o2对象去寻找p属性的值了。

如果bind方法的第一个参数是null或undefined,等于将this绑定到全局对象,函数运行时this指向全局对象(在浏览器中为window)。

    function add(x,y) { return x+y; }

    var plus5 = add.bind(null, 5);

    plus5(10) // 15

bind方法每运行一次就会返回一个新函数,这个会产生一些问题,如监听事件时,因click事件 绑定bind方法后,会生成一个匿名函数.这样会导致无法取消绑定.

        var o = {};
        o.p = 123;
        o.m = function(){
            console.log(this.p);
        };

        var  listener= o.m.bind(o);

        var ele = document.getElementById("mydiv1");

        ele.addEventListener('click', listener);
        //  ...
        ele.removeEventListener('click', listener);
    

对于那些不支持bind方法的老式浏览器,可以自行定义bind方法。

    if(!('bind' in Function.prototype)){
        Function.prototype.bind = function(){
            var fn = this;
            var context = arguments[0];
            var args = Array.prototype.slice.call(arguments, 1);
            return function(){
                return fn.apply(context, args);
            }
        }
    }


    除了用bind方法绑定函数运行时所在的对象,还可以使用jQuery的$.proxy方法,它与bind方法的作用基本相同。

    $("#button").on("click", $.proxy(o.f, o));
    上面代码表示,$.proxy方法将o.f方法绑定到o对象。
    

bind方法改写其它方法

    var push = Function.prototype.call.bind(Array.prototype.push);
    var pop = Function.prototype.call.bind(Array.prototype.pop);

    var a = [1 ,2 ,3];
    push(a, 4)
    a // [1, 2, 3, 4]
    //等同于
    a.push(4);

    pop(a)
    a // [1, 2, 3]