美文网首页程序员
你不知道的JavaScript(六)|this和对象原型

你不知道的JavaScript(六)|this和对象原型

作者: xpwei | 来源:发表于2017-10-17 16:58 被阅读15次

    委托理论

    Task = {
        setID: function (ID) { this.id = ID; },
        outputID: function () { console.log(this.id); }
    };
    // 让XYZ 委托Task
    XYZ = Object.create(Task);
    XYZ.prepareTask = function (ID, Label) {
        this.setID(ID);
        this.label = Label;
    };
    XYZ.outputTaskDetails = function () {
        this.outputID();
        console.log(this.label);
    };
        // ABC = Object.create( Task );
        // ABC ... = ...
    

    在这段代码中,Task和XYZ并不是类(或者函数),它们是对象。XYZ通过Object.create(..)创建,它的[[Prototype]]委托了Task对象。相比于面向类(或者说面向对象),这里会把这种编码风格称为“对象关联”。我们真正关心的只是XYZ对象(和ABC对象)委托了Task对象。
    对象关联风格的代码还有一些不同之处。
    1、在上面的代码中,id和label数据成员都是直接存储在XYZ上(而不是Task)。通常来说,在[[Prototype]]委托中组好把状态保存在委托者(XYZ、ABC)而不是委托目标(Task)上。
    2、在类设计模式中,我们故意让父类(Task)和子类(XYZ)中都有outputTask方法,这样就可以利用重写(多态)的优势。在委托行为中则恰好相反:我们会尽量避免在[[Prototype]]链的不同级别中适用男相同的命名,否则就需要使用笨拙并且脆弱的语法来消除引用歧义。
    3、this.setID(ID);XYZ中的方法首先会寻找XYZ自身是否有setID(..),但是XYZ中并没有这个方法名,因此会通过[[Prototype]]委托关联到Task继续寻找,这时就可以找到setID(..)方法。此外,由于调用位置触发了this的隐式绑定规则,因此虽然setID(..)方法在Task中,运行时this仍然会绑定到XYZ,这正是我们想要的。在之后的代码中我们还会看到this.outputID(),原理相同。
    换句话说,我们和XYZ进行交互时可以使用Task中的通用方法,因为XYZ委托了Task。
    委托行为意味着某些对象(XYZ)在找不到属性或者方法引用时会把这个请求委托给另一个对象(Task)。
    在API接口的设计中,委托最好在内部实现,不要直接暴露出去。在之前的例子中我们并没有开发者通过API直接调用XYZ.setID()。相反,我们把委托隐藏在了API的内部,XYZ.prepareTask(..)会委托Task.setID(..)。

    比较思维模式
    典型的(“原型”)面向对象风格:

    function Foo(who) {
        this.me = who;
    }
    Foo.prototype.identify = function () {
        return "I am " + this.me;
    };
    function Bar(who) {
        Foo.call(this, who);
    }
    Bar.prototype = Object.create(Foo.prototype);
    Bar.prototype.speak = function () {
        alert("Hello, " + this.identify() + ".");
    };
    var b1 = new Bar("b1");
    var b2 = new Bar("b2");
    b1.speak();
    b2.speak();
    

    以上代码,子类Bar继承了父类Foo,然后生成了b1和b2两个实例。b1委托了Bar.prototype,Bar.prototype委托了Foo.prototype。
    使用对象关联风格来编写功能完全相同的代码:

    Foo = {
        init: function (who) {
            this.me = who;
        },
        identify: function () {
            return "I am " + this.me;
        }
    };
    Bar = Object.create(Foo);
    Bar.speak = function () {
        alert("Hello, " + this.identify() + ".");
    };
    var b1 = Object.create(Bar);
    b1.init("b1");
    var b2 = Object.create(Bar);
    b2.init("b2");
    b1.speak();
    b2.speak();
    

    这段代码中我们同样利用[[Prototype]]把b1委托给Bar并把Bar委托给Foo,和上一段代码一模一样。我们仍然实现了三个对象之间的关联。
    但是非常重要的一点是,这段代码简洁了许多,我们只是把对象关联起来,并不需要那些既复杂又令人困惑的模仿类的行为(构造函数、原型以及new)。

    控件“类”

    // 父类
    function Widget(width, height) {
        this.width = width || 50;
        this.height = height || 50;
        this.$elem = null;
    }
    Widget.prototype.render = function ($where) {
        if (this.$elem) {
            this.$elem.css({
                width: this.width + "px",
                height: this.height + "px"
            }).appendTo($where);
        }
    };
    // 子类
    function Button(width, height, label) {
        // 调用“super”构造函数
        Widget.call(this, width, height);
        this.label = label || "Default";
        this.$elem = $("<button>").text(this.label);
    }
    // 让Button“继承”Widget
    Button.prototype = Object.create(Widget.prototype);
    // 重写render(..)
    Button.prototype.render = function ($where) {
        // “super”调用
        Widget.prototype.render.call(this, $where);
        this.$elem.click(this.onClick.bind(this));
    };
    Button.prototype.onClick = function (evt) {
        console.log("Button '" + this.label + "' clicked!");
    };
    $(document).ready(function () {
        var $body = $(document.body);
        var btn1 = new Button(125, 30, "Hello");
        var btn2 = new Button(150, 40, "World");
        btn1.render($body);
        btn2.render($body);
    });
    

    ES6的class语法糖

    class Widget {
        constructor(width, height) {
            this.width = width || 50;
            this.height = height || 50;
            this.$elem = null;
        }
        render($where) {
            if (this.$elem) {
                this.$elem.css({
                    width: this.width + "px",
                    height: this.height + "px"
                }).appendTo($where);
            }
        }
    }
    class Button extends Widget {
        constructor(width, height, label) {
            super(width, height);
            this.label = label || "Default";
            this.$elem = $("<button>").text(this.label);
        }
        render($where) {
            super($where);
            this.$elem.click(this.onClick.bind(this));
        }
        onClick(evt) {
            console.log("Button '" + this.label + "' clicked!");
        }
    }
    $(document).ready(function () {
        var $body = $(document.body);
        var btn1 = new Button(125, 30, "Hello");
        var btn2 = new Button(150, 40, "World");
        btn1.render($body);
        btn2.render($body);
    });
    

    毫无疑问,使用ES6的class之后,上一段代码中许多丑陋的语法都不见了,super(..)函数棒极了。
    尽管语法上得到了改进,但实际上这里并没有真正的类,class仍然是通过[[Prototype]]机制实现的。

    委托控件对象

    var Widget = {
        init: function (width, height) {
            this.width = width || 50;
            this.height = height || 50;
            this.$elem = null;
        },
        insert: function ($where) {
            if (this.$elem) {
                this.$elem.css({
                    width: this.width + "px",
                    height: this.height + "px"
                }).appendTo($where);
            }
        }
    };
    var Button = Object.create(Widget);
    Button.setup = function (width, height, label) {
        // 委托调用
        this.init(width, height);
        this.label = label || "Default";
        this.$elem = $("<button>").text(this.label);
    };
    Button.build = function ($where) {
        // 委托调用
        this.insert($where);
        this.$elem.click(this.onClick.bind(this));
    };
    Button.onClick = function (evt) {
        console.log("Button '" + this.label + "' clicked!");
    };
    $(document).ready(function () {
        var $body = $(document.body);
        var btn1 = Object.create(Button);
        btn1.setup(125, 30, "Hello");
        var btn2 = Object.create(Button);
        btn2.setup(150, 40, "World");
        btn1.build($body);
        btn2.build($body);
    });
    

    使用对象关联风格来编写代码时不需要把Widget和Button当做父类和子类。相反,Widget只是一个对象,包含一组通用的函数,任何类型的控件都可以委托,Button同样只是一个对。
    从设计模式的角度来说,我们并没有像类一样在两个对象中都定义相同的方法名render(..),相反,我们定义了两个更具描述性的方法名(insert(..)和build(..))。同理,初始化方法分别叫做init(..)和setup(..)。

    好想跟妈妈说说心里话,好想妈妈

    相关文章

      网友评论

        本文标题:你不知道的JavaScript(六)|this和对象原型

        本文链接:https://www.haomeiwen.com/subject/wpbruxtx.html