原型与面向对象

实例化和原型

  我们可以通过原型给函数附加属性,还可以在构造器函数内通过this参数初始化值。

    function Ninja() {
        this.swung = false;

        this.swingSword = function () {
            return !this.swung;
        }

    }

    Ninja.prototype.swingSword = function () {
        return this.swung;
    }

    var ninja = new Ninja();

我们这里有两个名称相同的方法,这里存在优先级,在构造器内才绑定操作优先级永远都高于在原型上的绑定操作优先级,因为构造器的this上下文指向的是实例本身,所以我们可以在构造器内对核心内容执行初始化操作。

做一个测试,上一段代码

function Ninja() {
        this.swung = false;
    }

    var ninja = new Ninja();

    Ninja.prototype.swingSword = function () {
        return this.swung;
    }

测试发现ninja是有swingSword这个方法,你开始并没有在原型上定义这个方法,就直接new,然后在后面定义prototype,原来的就有了,事实上,原型上的属性并没有复制到其他地方,而是附加到新创建的对象上了,并可以和对象自身的属性引用一切协调运行。如下是这一过程的简要概述。

1.在引用对象的一个属性时,首先检查该对象本身是否拥有该属性,如果有,则直接返回,如果没有。。。
2.则,再查看对象的原型,检查该原型上是否有所有的属性,如果有,则直接返回,如果没有。。。
3.则,该值是undefined

这种无缝的实时更新带给我们令人难以置信的威力和可扩展性,而一般语言都没有这种功能,利用该实时更新的特性,我们可以构建一个函数化的框架,用户可以使用该框架进行进一步扩展,即便是在对象被实例化之后。

function Ninja() {
        this.swung = true;
        this.swingSword = function () {
            return !this.swung;
        }
    }

    var ninja = new Ninja();

    Ninja.prototype.swingSword = function () {
        return this.swung;
    }

在本例中,我们重新定义了一个和原型同名的实例方法,回到代码清单,我们现在是在构造器执行之后才创建原型方法,在这种情况下,哪种方法的优先级最高。上述示例的测试表明,即便是在实例化对象之后,再在原型方法上添加方法,实例方法也会优先考虑原型上的方法.在对象自身对属性引用进行查询失败之后,原型查找是唯一的方式,由于对象直接拥有swingSword属性,原型版本的swingSword属性则不发挥任何作用,即便该版本的方法是最新创建的。
其关键点是,查询属性引用时,首先是查询对象本身,如果不存在,才在原型上进行查找


继承与原型链

如下代码清单,我们要为一个实例添加继承功能。

    function Person() {

    }
    Person.prototype.dance = function () {

    }

    function Ninja() {

    }

    Ninja.prototype = {dance: Person.prototype.dance}

    var ninja = new Ninja();

由于函数原型只是一个对象,所有有很多功能,可以通过复制的方法达到继承的目的。在上述代码中,首先定义一个Person,然后再定义一个Ninja,因为Ninja显示也是一个人,所以我们希望Ninja也能继承Person的属性。我们尝试将Person原型的dance属性方法复制到Ninja的原型上作为同名方法来实现继承功能。但测试结果表明,Ninja并没有成为一个Person。


我们真正想要的是一个原型链,创建这样一个原型链的最好方式是,使用一个对象的实例作为另外一个对象的原型:

SubClass.prototype = new SuperClass();

用原型实现继承

function Person() {

}
Person.prototype.dance = function () {

}

function Ninja() {

}

Ninja.prototype = new Person();

var ninja = new Ninja();

上述代码的唯一变化是将Person的一个实例作为Ninja的原型。

利用原型,我们可以扩展这门语言自身的功能,从而给这门语言引入新的特性或丢失的特性。下面展示了一种forEach()实现,使用该实现可以弥补旧版本浏览器的不足。

if (!Array.prototype.forEach) {

    Array.prototype.forEach = function (callback, context) {
        for (var i = 0; i < this.length; i++) {
            callback.call(context || null, this[i], i, this);
        }
    }
}

HTML DOM原型

在现代浏览器中,所有的DOM元素都继承于HTMLElement构造器,通过访问HTMLElement的原型,浏览器可以为我们提供扩展任意HTML节点的能力。

<div id="parent">
    <div id="a">I'am going to be removed</div>
    <div id="b">Me too!</div>
</div>

<script>
HTMLElement.prototype.remove = function () {
    if(this.parentNode) {
        this.parentNode.removeChild(this);
    }
}

var a = document.getElementById('a');
a.parentNode.removeChild(a);

document.getElementById('b').remove();


</script>

但是这样var elem = new HTMLElement();是不行的。

疑难陷阱

javaScript有几个和原型、实例化以及继承相关的陷阱。其中一些还能用,但另外一些需要我们静下心来研究。
给原生Object.prototype是很严重的错误,举个例子。


Object.prototype.keys = function () {
    var keys = [];
    for (var p in this) {
        keys.push(p);
    }
    return keys;
}

var obj = {a: 1, b: 2, c: 3};

obj.keys().length === 3

最后测试能否得到一个含有三个元素的数组,但是测试没有通过。问题出在了给Object添加的keys()方法上,我们给所有的对象添加了这个属性,并出现在计数中,这会影响到所有对象,很显然这是不可接受的,不能这样做。

如果别人的代码这样做的话,有一个解决办法可以保护自己的代码免受这些善意单倍误导的影响。

javaScript提供了一个名为hasOwnProperty()的方法,使用该方法可以确定一个属性是在对象实例上定义的,还是从原型里导入的。


Object.prototype.keys = function () {
    var keys = [];
    for (var i in this) {
        if (this.hasOwnProperty(i)) {
            keys.push(i);
        }
    }
    return keys;
}

var obj = {a: 1, b: 2, c: 3};

obj.keys().length === 3

我们重新定义了方法,忽略了非实例属性,这样测试就通过了。

子类化原生对象

当我们想对其他原生对象进行子类化时,情况却变得不那么明确。

function MyArray() {

}

MyArray.prototype = new Array();

var mine = new MyArray();

mine.push(1, 2, 3);

mine.length

对array进行子类化时,我们使用了一个自定义构造器MyArray(),除了在IE下不能正常使用,length属性相当特殊,密切关系到array的数字指标。在这种情况下,更好的策略是单独实现原生对象的各个功能,而不是扩展出子类,让我们来看看如下

function MyArray() {

}

MyArray.prototype.length = 0;

(function () {
    var methods = ['push', 'pop', 'shift', 'unshift', 'slice', 'splice', 'join'];

    for (var i = 0; i < methods.length; i++) (function (name) {
            MyArray.prototype[name] = function () {
                return Array.prototype[name].apply(this, arguments);
            }
    })(methods[i]);
})();

var mine = new MyArray();
mine.push(1, 2, 3);

我们创建了一个含有原型属性length的新类,复制了数组的功能。

编写类风格的代码

javaScript可以让我们通过原型实现继承。经典继承语法示例,

//通过subClass方法,创建一个Person类作为Object的一个子类,该方法最后实现
var person = Object.subClass({
    init: function (isDancing) {
        this.dancing = isDancing;
    },
    dance: function () {
        return this.dancing;
    }
})

//通过继承Person类,创建一个Ninja子类
var Ninja = Person.subClass({
    init: function () {
        this._super(false);  //需要调用父类构造器的方法,这里展示我们将这样做
    },
    dance: function () {
        return this._super();
    },
    swingSword: function () {
        return true;
    }
})

var person = new Person(true);

代码中。使得类作为一种结构,保持继承简单,并且允许调用超类方法,如果需要很长时间来理解这些代码的话,也不要沮丧。
下面我们研究是如何组织在一起的,

(function () {
    var initializing = false;
    var superPattern = /xyz/.test(function () {
        xyz;
    }) ? /\b_super\b/ : /.*/;

    Object.subClass = function (properties) {
        var _super = this.prototype;

        initializing = true;
        var proto = new this();
        initializing = false;

        for (var name in properties) {
            proto[name] = typeof properties[name] == 'function' &&
                          typeof _super[name] === 'function' &&
                          superPattern.test(properties[name]) ?
                (function (name, fn) {
                    return function () {
                        var tmp = this._super;

                        this._super = _super[name];

                        var ret = fn.apply(this, arguments);
                        this._super = tmp;

                        return ret;
                    }
                })(name, properties[name]) : properties[name];

        }

        function Class() {
            if(!initializing && this.init) {
                this.init.apply(this, arguments);
            }
        }

        Class.prototype = proto;
        Class.constructor = Class;
        Class.subClass = arguments.callee;

        return Class;
    }
})();

代码实现的一开始很深奥,而且还可能最让人困惑,在后续代码中,我们需要知道浏览器是否支持函数序列化。函数序列化就是简单接收一个函数,然后返回该函数的源码文本,稍后,我们可以使用这种方法检查一个函数在我们感兴趣的对象中是否存在引用。我们用下面这个方式进行序列化,在设置一个名为initializing的变量为false,我们使用如下表达式测试一个函数是否能够被序列化。

/xyz/.test(function() { xyz; })

该表达式创建一个包含xyz的函数,将该函数传递给正则表达式的test()方法,该正则表达式对字符串”xyz”进行测试,如果函数能够正常序列化(test()方法将接收一个字符串,然后将触发函数的toString()方法)最终结果将返回true。

此时,我们准备开始定于一个方法用于子类化父类,我们用如下代码进行实现,

Object.subClass = function (properties) {
    var _super = this.prototype;
}

给Object添加一个subClass()方法,该方法接收一个参数,该参数是我们期望添加到子类的属性集。重写函数时,我们需要通过名为_super的属性,将子类函数和父类函数的引用进行包装。但在完成该操作之前,我们需要检测即将被包装的子类函数。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值