why-js-day6 行为委托

[[prototype]]机制就是指对象中的一个内部链接引用另一个对象,如果在第一个对象上没有找到需要的属性和方法,引擎就会在[[prototype]]关联的对象上继续查找,如果后者也没找到,则会继续查找后者的[[prototype]],以此类推,这一系列对象的链接称为原型链,JavaScript中的这个机制本质即使对象之间的关联关系

面向委托的设计

let Task={
    setID:function (ID) {
        this.id=ID;
    },
    outputID:function () {
        console.log(this.id);
    }
};
let XYZ=Object.create(Task);
XYZ.prepareTask=function (ID, label) {
    this.setID(ID);
    this.label=label;
};
XYZ.outputTaskDetails=function () {
    this.outputID();
    console.log(this.label);
};

互相委托是禁止的

调试,JavaScript规范并不会控制浏览器中的开发者工具对特定结构的表示方式

chrome

function Foo() {}
let a1=new Foo();
console.log(a1);//Foo {}

firefox

function Foo() {}
let a1=new Foo();
console.log(a1);//Object {}

chrome表示{}是一个空对象,由Foo构造

Firefox表示{}是一个空对象,由Object构造

chrome会动态跟踪实际执行构造过程的函数名

function Foo() {}
let a1=new Foo();
console.log(a1.constructor,a1.constructor.name);
function Foo() {}
let a1=new Foo();
Foo.prototype.constructor = function Gotcha(){};
console.log(a1.constructor,a1.constructor.name,a1);
function Foo() {}
let a1=Object.create(Foo);
console.log(a1);
Object.defineProperty(Foo,"constructor",{
    enumerable:false,
    value:function Gotcha() {}
});
console.log(a1);

如果不是用new来生成对象,chrome就无法跟踪对象内部的构造函数名称(,constructor.name)

(原型)面向对象风格

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 () {
    console.log("hello, "+this.identify()+".");
};
let b1=new Bar("b1");
let b2=new Bar("b2");
b1.speak();
b2.speak();

对象关联风格

let Foo={
    init:function (who) {
        this.me=who;
    },
    identify:function () {
        return "i am "+this.me;
    }
};
Bar=Object.create(Foo);
Bar.speak=function () {
    console.log("Hello, "+ this.identify()+".");
};
let b1=Object.create(Bar);
b1.init('b1');
let b2=Object.create(Bar);
b2.init('b2');
b1.speak();
b2.speak();

类与对象

纯JavaScript实现类风格代码

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) {
    Widget.call(this,width,height);
    this.label=label||"Default";
    this.$elem=$("<button>").text(this.label);
}
Button.prototype=Object.create(Widget.prototype);
Button.prototype.render=function ($where) {
    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 () {
    let $body=$(document.body);
    let btn1=new Button(125,30,"hello");
    let 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 () {
    let $body=$(document.body);
    let btn1=new Button(125,30,"hello");
    let btn2=new Button(150,40,"world");
    btn1.render($body);
    btn2.render($body);
});

对象关联风格委托实现,对象关联可以更好的支持关注分离原则,创建和初始化并不需要合并为一个步骤

let 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);
        }
    }
};
let 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 () {
    let $body=$(document.body);
    let btn1=new Button(125,30,"hello");
    let btn2=new Button(150,40,"world");
    btn1.render($body);
    btn2.render($body);
});

更简洁的设计

对象关联除了能让代码看起来更简洁外,还可以通过行为委托模式简化代码结构

function Controller() {
    this.errors = [];
}
Controller.prototype.showDialog = function(title,msg){
    console.log(title + msg);
};
Controller.prototype.success = function (msg) {
    this.showDialog("Success",msg);
};
Controller.prototype.failure = function (err) {
    this.errors.push(err);
    this.showDialog("Error",err);
};

function LoginController() {
    Controller.call(this);
}
LoginController.prototype = Object.create(Controller.prototype);
LoginController.prototype.getUser = function () {
    return document.getElementById("login_username").value;
};
LoginController.prototype.getPassword = function () {
    return document.getElementById("login_password").value;
};
LoginController.prototype.validateEntry = function (user,pw) {
    user = user || this.getUser();
    pw = pw || this.getPassword();

    if (!(user && pw)){
        return this.failure(
            "Please enter a username & password"
        );
    }else if (user.length < 5){
        return this.failure(
            "Password must be 5+ characters"
        );
    }
    return true;
};
LoginController.prototype.failure = function (err) {
    Controller.prototype.failure.call(
        this,
        "Login invalid: " + err
    );
};

function AuthController(login) {
    Controller.call(this);
    this.login = login;
}
AuthController.prototype = Object.create(Controller.prototype);
AuthController.prototype.server = function (url, data) {
    return $.ajax({
        url:url,
        data:data
    });
};
AuthController.prototype.checkAuth = function () {
    var user = this.login.getUser();
    var pw = this.login.getPassword();

    if (this.login.validateEntry(user,pw)){
        this.server("/check-auth",{
            user:user,
            pw:pw
        })
            .then(this.success.bind(this))
            .fail(this.failure.bind(this));
    }
};
AuthController.prototype.success = function () {
    Controller.prototype.success.call(this,"Authenticated!");
};
AuthController.prototype.failure = function (err) {
    Controller.prototype.failure.call(
        this,
        "Auth Failed: " + err
    );
};
let auth = new AuthController();
auth.checkAuth(new LoginController());

反类

let LoginController = {
    errors:[],
    getUser:function () {
        return document.getElementById("login_username").value;
    },
    getPassword:function () {
        return document.getElementById("login_password").value;
    },
    validateEntry:function (user, pw) {
        user = user || this.getUser();
        pw = pw || this.getPassword();
        if (!(user && pw)){
            return this.failure("Please enter a username & password!");
        }else if(user.length < 5){
            return this.failure("Password must be 5+ character!");
        }
        return true;
    },
    showDialog:function (title, msg) {

    },
    failure:function (err) {
        this.errors.push(err);
        this.showDialog("Error","Login invalid: " + err);
    }
};

let AuthController = Object.create(LoginController);
AuthController.errors = [];
AuthController.checkAuth = function () {
    let user = this.getUser();
    let pw = this.getPassword();
    if (this.validateEntry(user,pw)){
        this.server("/check-auth",{
            user:user,
            pw:pw
        })
            .then(this.accepted.bind(this))
            .fail(this.rejected.bind(this));
    }
};
AuthController.server = function (url, data) {
    return $.ajax({
        url:url,
        data:data
    });
};
AuthController.accepted = function () {
    this.showDialog("Success","Authenticated!");
};
AuthController.rejected = function (err) {
    this.failure("Auth Failed: " + err);
}

更好的语法

ES6中的class可以简洁的定义类方法,但要避免使用

class Foo {
    methodname(){}
}

对象关联中出现了很多function,也不简洁,ES6中提供了简洁方法声明(和class语法糖一样),委托可以用Object.setPrototypeOf(obj,pro)

let LoginController={
    errors:[],
    getUser(){
        
    },
    getPassword(){
        
    }
};

反词法,简洁方法有一个小但是重要的缺点

let Foo={
    bar(){},
    baz:function baz() {}
};

去掉语法糖

let Foo={
    bar:function(){},
    baz:function baz() {}
};

由于函数对象本身没有标识符,所以实际上是一个匿名函数表达式赋值给属性,匿名函数没有名称标识符

  1. 调用栈更难追踪
  2. 自我引用更难
  3. 代码更难理解

但在这里主要是无法避免第2点,第一点由于简洁方法会给对应的函数对象设置一个内部的name属性,所有可以追踪

let Foo={
    bar:function(x){
        if (x < 10) {
            return Foo.bar(x*2);
        }
        return x;
    },
    baz:function baz(x) {
        if (x < 10) {
            return baz(x*2);
        }
        return x;
    }
};

上述情况下,可以通过对象的属性来引用,但当多个对象通过代理共享函数,使用this绑定,最好还是使用函数的name标识符

内省

instanceof,实际上是检查的对象的[[prototype]]链上是否有Foo.prototype对象

ES6中的class

class解决的问题

  1. 不再引用杂乱的.prototype
  2. 直接继承,不需要Object.create(..)
  3. 可以通过super(...)实现相对多态
  4. class只能声明方法,不能声明属性
  5. 通过extends自然扩展子类型,甚至是内置的类型

class陷阱

class只是现有[[prototype]]机制的一种语法糖

class不会像传统面向对象语言一样在声明时静态复制所有行为,如果修改了父类的方法,则子类也会受到影响

class C {
    constructor(){
        this.num=Math.random();
    }
    rand(){
        console.log("Random: "+this.num);
    }
}
let c1=new C();
c1.rand();
C.prototype.rand=function () {
    console.log("Random: "+Math.round(this.num*1000));
};
let c2=new C();
c2.rand();
c1.rand();

class无法定义类成员属性(只能定义方法),只有使用.prototype

class C {
    constructor(){
        C.prototype.count++;
        console.log("Hello: "+this.count);
    }
}
C.prototype.count=0;
let c1=new C();
let c2=new C();
console.log(c1.count===2,c1.count===c2.count);

此外,class语法仍然面临意外屏蔽的问题

class C {
    constructor(id){
        this.id=id;
    }
    id(){
        console.log("Id: "+id);
    }
}
let c1=new C("c1");
c1.id();

super也存在一些问题

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
经导师精心指导并认可、获 98 分的毕业设计项目!【项目资源】:微信小程序。【项目说明】:聚焦计算机相关专业毕设及实战操练,可作课程设计与期末大作业,含全部源码,能直用于毕设,经严格调试,运行有保障!【项目服务】:有任何使用上的问题,欢迎随时与博主沟通,博主会及时解答。 经导师精心指导并认可、获 98 分的毕业设计项目!【项目资源】:微信小程序。【项目说明】:聚焦计算机相关专业毕设及实战操练,可作课程设计与期末大作业,含全部源码,能直用于毕设,经严格调试,运行有保障!【项目服务】:有任何使用上的问题,欢迎随时与博主沟通,博主会及时解答。 经导师精心指导并认可、获 98 分的毕业设计项目!【项目资源】:微信小程序。【项目说明】:聚焦计算机相关专业毕设及实战操练,可作课程设计与期末大作业,含全部源码,能直用于毕设,经严格调试,运行有保障!【项目服务】:有任何使用上的问题,欢迎随时与博主沟通,博主会及时解答。 经导师精心指导并认可、获 98 分的毕业设计项目!【项目资源】:微信小程序。【项目说明】:聚焦计算机相关专业毕设及实战操练,可作课程设计与期末大作业,含全部源码,能直用于毕设,经严格调试,运行有保障!【项目服务】:有任何使用上的问题,欢迎随时与博主沟通,博主会及时解答。
经导师精心指导并认可、获 98 分的毕业设计项目!【项目资源】:微信小程序。【项目说明】:聚焦计算机相关专业毕设及实战操练,可作课程设计与期末大作业,含全部源码,能直用于毕设,经严格调试,运行有保障!【项目服务】:有任何使用上的问题,欢迎随时与博主沟通,博主会及时解答。 经导师精心指导并认可、获 98 分的毕业设计项目!【项目资源】:微信小程序。【项目说明】:聚焦计算机相关专业毕设及实战操练,可作课程设计与期末大作业,含全部源码,能直用于毕设,经严格调试,运行有保障!【项目服务】:有任何使用上的问题,欢迎随时与博主沟通,博主会及时解答。 经导师精心指导并认可、获 98 分的毕业设计项目!【项目资源】:微信小程序。【项目说明】:聚焦计算机相关专业毕设及实战操练,可作课程设计与期末大作业,含全部源码,能直用于毕设,经严格调试,运行有保障!【项目服务】:有任何使用上的问题,欢迎随时与博主沟通,博主会及时解答。 经导师精心指导并认可、获 98 分的毕业设计项目!【项目资源】:微信小程序。【项目说明】:聚焦计算机相关专业毕设及实战操练,可作课程设计与期末大作业,含全部源码,能直用于毕设,经严格调试,运行有保障!【项目服务】:有任何使用上的问题,欢迎随时与博主沟通,博主会及时解答。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值