你不知道的JavaScript笔记之行为委托

委托理论

相比于面向类(或者说面向对象),这种风格可以被称之为“对象关联(OLOO,object linked to other objects)

对象关联风格的代码不同之处

  1. 应该把状态保存在委托者,而不是委托目标上。
  2. 避免[[prototype]]链的不同层级上使用相同的命名
  3. 触发this隐式绑定规则达到我们想要的调用效果。

委托行为意味着某些对象在找不到属性或者方法引用时会把这个请求委托给另一个对象

并且,委托最好是在内部实现,而不是直接暴露出去。

        Task = {
            setID : function(ID) { this.id = ID},
            outputID : function() { console.log(this.id);}
        }

        XYZ = Object.create(Task);

        XYZ.prepareTask = function(ID, Label) { 
            // 我们把委托隐藏在 API 的内部!!!
            // 也就是说,XYZ.prepareTask(..) 会委托 Task.setID(..) 
            this.setID(ID);
            this.label = Label;
        }

        XYZ.outputTaskDetails = function() {
            this.outputID();
            console.log(this.label);
        }

        ABC = Object.create(Task); // .....

比较“类”和“委托”的思维

类的风格

子类Bar继承了父类Foo,然后成成实例。实例委托了Bar.prototype,后者委托了Foo.prototype

        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并把Bar委托给了Foo,从而实现了三个对象之间的关联。但是这里更简洁,不需要那些复杂困惑的模仿类行为,比如构造函数、原型以及new

        Foo = {
            init : function(who) {
                this.me = who;
            },
            identity : function() {
                return "I am " + this.me;
            }
        };

        Bar = Object.create(Foo); // 开始第一层委托

        Bar.speak = function() { // 定义自己的功能
            alert("Hello" + this.identity() + "."); // 把委托隐藏在 API 内部
        }


        var b1 = Object.create(Bar); // 开始第二层委托
        b1.init("Saul");


        var b2 = Object.create(Bar);
        b2.init("Carrie");

        b1.speak();
        b2.speak();

控件举例

“类”的写法

类的写法要求我们现在父类上定义render()方法,然后在子类里面重写它。但又不是完全替换,而是添加一些按钮特定行为。如果在子类中引用父类的基础方法,就会出现丑陋的显式伪多态

        // 父类
        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 render方法
        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, 40, "Hello");
            var btn2 = new Button(144, 51, "World");

            btn1.render($body);
            btn2.render($body);

            console.log(btn1);
        })

ES6的calss语法糖写法

使用ES6之后,代码变得好看了很多。但是深入研究之下,其实并不完美。

        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.render($where); // 注意这里是 super.render()而不是 super(),书中有误
                this.$elem.click(this.onClick.bind(this));
            }

            onClick(evt) {
                console.log("Button  " + this.label + " is Clicked!");
            }

        }


        $(document).ready(function() {
            var $body = $(document.body);
            var btn1 = new Button(125, 40, "Hello");
            var btn2 = new Button(144, 51, "World");

            btn1.render($body);
            btn2.render($body);

            console.log(btn1);
        })

关联对象的写法

对象关联可以更好地支持关注分离————separation of concerns 的原则。

        // 注意,并不存在父类!!!只有对象,只有对象,只有对象!!!
        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); // 关联 Widget

        Button.setup = function(width, height, label) { // 包装 API ,在内部委托
            // 委托调用
            this.init(width, height); // 就在这个委托 Widget 对象的 init

            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 + " is Clicked!!! ");
        };

        $(document).ready(function() {

            var $body = $(document.body);

            var btn1 = Object.create(Button); // 注意这里新建实例也是通过关联对象
            btn1.setup(124, 76, 'Hello'); 

            var btn2 = Object.create(Button); // 注意这里新建实例也是通过关联对象
            btn2.setup(124, 66, 'World');

            btn1.build($body);
            btn2.build($body);
        })

更简洁的设计

最后一个案例,展示了如何利用对象关联来简化整体设计。
在这个场景中,我们有两个控制器对象:一个用来操作网页中的登录表单,另一个用来于服务器进行验证通信。

        var LoginController = { // 我们并不非得需要定义父类和两个子类来建模

            errors : [], // 错误信息
            getUsers : function() { // 取回用户名
                return document.getElementById('login_username').value;
            },
            getPassword : function() { // 取回密码
                return document.getElementById('login_password').value;
            },
            validateEntry : function(user,pw) { // 验证用户名和密码

                user = user || this.getUsers();
                pw = pw || this.getPassword();

                if(!(user && pw)) { // 验证失败
                    return this.failure("请输入用户名和密码");
                } 
                else if(pw.length < 5) {
                    return this.failure("密码必须5位以上!");
                }

                return true; // 执行到这里,说明通过了验证
            },
            showDialog : function(title, msg) { // 显示对话
                // 给用户显示标题和消息
            },
            failure : function(err) { // 显示错误
                this.errors.push(err); // 储存错误
                this.showDialog("错误", "登陆出现 " + err);
            }
        };

        // 让 AuthController 委托给 LoginController
        var AuthController = Object.create(LoginController);

        AuthController.errors = [];
        AuthController.checkAuth = function() {
            var user = this.getUsers();
            var pw = this.getPassword();

            if(this.validateEntry(user, pw)) { // 验证成功

                this.server('/check-auth', {
                    user : user,
                    pw : pw
                })
                .then(this.accepted().bind(this))   // then 监听响应
                .fail(this.rejected.bind(this)); // 这里使用了链式反应
            }
        };
        AuthController.server = function(url, data) { // 辅助函数来创建 Ajaxt 通信
            return $.ajax({
                url : url,
                data : data
            });
        };
        AuthController.accepted = function() {
            this.showDialog("成功", "通过验证")
        };
        AuthController.rejected = function(err) {
            this.failure("认证失败,错误" + err);
        };


        var controller1 = Object.create(AuthController);
        var controller2 = Object.create(AuthController);

[TOC]

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,902评论 5 468
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,037评论 2 377
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,978评论 0 332
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,867评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,763评论 5 360
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,104评论 1 277
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,565评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,236评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,379评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,313评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,363评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,034评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,637评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,719评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,952评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,371评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,948评论 2 341

推荐阅读更多精彩内容