Javascript老生常谈之面向对象


背景

作为一个前端新人,免不了加各种群,和其他小伙伴们一起学习(chui bi),互相帮助(bi can)。前几天一个小伙伴在群里发了道自己去面试的笔试题,我写了一下,一时间没能实现,今天又折腾了一下,虽然大致实现了,但不知道是否优雅,分享自己解法的同时也想请大佬指点一二。

题目

编写一个名字为Person的对象,要求

  • 有一个name属性,外部只能访问不能修改,初始化时赋值。

  • 内部维护一个叫things的局部变量,数组类型,用于存储购买的物品(something)清单。

  • 有一个buy(something)方法,用于购买物品(something)

  • 具有count属性,用于指示一共买了多少物品。

分析

看到这题的第一反应就是用构造函数/class来写。Person对象应该就是一个Person类。
两个属性一个方法,嗯,没什么问题。只有2个点需要考虑:

一、 这个things局部变量是个啥?是属性吗?

  • 应该不是,既然特意指出了,肯定有他的考虑,那么既然是局部变量,外部也是访问不到的,这个应该用闭包写。

二、 name属性外部只能访问不能修改.

  • 这个用闭包也能解决,但是这样记不能通过构造函数/class来实现了,背离了初衷,不行(不过我后面还是会给出这种写法)。那么(以我的水平)就只能用Proxy或者class,这2个都能拦截对象属性的读/写。2种我都试过,采用Proxy,原因下面会讲。

代码

class Person {
    constructor(name,count=0) {
        this.name = name;
        this.count = count;
        this.init()
    }
    //初始化函数
    init() {
        var things = []; 
        Person.prototype.buy = (something) => { //为了形成闭包,显式将buy方法写到Person的原型上
            things.push(something);
            this.count = things.length;
        }
    }
}
//Proxy handler对象,定义行为
var handler = {
    set(target,prop,value,receiver) {//拦截set行为
        if(prop == 'name') {
            throw Error('不可以哦') //这里自定义你的逻辑,也可以alert等。
        }
        Reflect.set(target,prop,value,receiver) //不要忘了对其余属性'放行'
    }
}

var p1 = new Proxy(new Person('张三'),handler)
console.log(p1.name) //张三
p1.name = '李四' //Error:不可以哦
p1.buy('猫粮');
p1.buy('猫砂');
console.log(p1.count) //2
//如果需要从'内部'修改p1的名字,则需要先对被代理对象进行定义
var _p1 = new Person('张三');
var p1 = new Proxy(_p1,handler)
console.log(p1.name); //张三
_p1.name = '李四';
console.log(p1.name) //李四

以上我对这道题的理解。至于为什么不用class的setter,是因为这样写后,在new Person的时候不能传name进去,因为一传进去就会被拦截。所以只能先初始化对name赋值,再进行proxy代理对name拦截。

另一种“野路子”写法

var Person = function(name,count=0) {
    var things = [],name = name
        result = {
            count = count;
        };
    result.__proto__ = {
        buy:function(something) {
            things.push(something);
            result.count = things.length;
        },
        getName:function() { // 通过getName函数来获取名字
            return name;
        }
    }
    return result;
}

var p2 = Person('王五'); 
p2.getName() //王五
p2.name = 123;
p2.getName() //王五
p2.buy('妙鲜包');
console.log(p2.count) // 1

两种方法见仁见智,第二种野路子不用Proxy代理,返回的是Object对象,而第一种正规军则返回Person对象,更符合题意。


结语

写到这不知道大家发现了没。 其实第一种方法也可以不需要proxy代理,再init函数中定义name = this.name,再在Person原型上写一个getName函数,这样2种方法的优点就结合到一起了Σ(っ °Д °;)っ
这2小时没有白花,这文章没有白写,又赚到了^ ^。
希望能给大家带来一点点收获,如果有不同的看法可以留言一起探讨()。

Thanks for reading


发表评论

电子邮件地址不会被公开。 必填项已用*标注

您可以使用这些HTML标签和属性: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>