美文网首页JavaScript 进阶营
JavaScript Tips - Vol.3 面向对象程序设计

JavaScript Tips - Vol.3 面向对象程序设计

作者: 张羽辰 | 来源:发表于2018-06-23 00:07 被阅读1次

    ECMA-262 把对象定义为:“无序属性的集合,其属性可以包含基本值、对象或者函数。” 相当于对象是一组没有特定顺序的值。

    我们可以将 ECMAScript 的对象想象成散列表,无非就是一组 key-value,v 可以是数据或者函数。所以需要注意的是,JavaScript 并没有类的概念,不论是使用闭包或者原型,我们只是在模拟类的一个基本功能:模板

    对象是一个引用类型创建的,该类型可以是 Object 也可以是其他自定义类型。

    ECMAScript 有两种属性:数据与访问器

    数据属性:

    • Configurable - 可 delete 从而重新定义属性
    • Enumerable - 能否 for-in
    • Writable - 能否修改?
    • Value - 表示属性的数据值,默认为 undefined

    要修改属性默认的特性,必须使用 ECMAScript 5 的 Object.defineProperty()方法。

    var people = {
        name: 'yuchen',
        age: 19,
        say: function () {
            console.log(this.name)
        }
    };
    
    people.say();
    
    Object.defineProperty(people, 'name', {
        writable: false,
        value: 'yuchen'
    });
    
    people.name = 'lele';
    people.say();
    
    for (var t in people) {
        console.log(t);
        console.log(people[t]);
    }
    

    这个例子演示了一个对象中的属性,这个属性可以是一个 permitive value 也可以是 reference,那么当然也可以是一个函数。

    访问器,为 getter setter 的提供者,只有 configurable、enumeration、get、set 四个属性。注意下面的 magic,如果

    var boy = {};
    
    Object.defineProperty(boy, 'name', {
        get: function () {
            return this._name; // this &  _name?
        },
        set: function (newVal) {
            this._name = newVal;
        }
    });
    

    上个例子中,我们使用了 this 与 _name。当你在思考 this 时,只需要抓住一点,谁调用了这个函数,谁就是 this。这一点在 Ruby 中有更好的描述,即 this 永远表示方法的 receiver。

    所以在上面这个例子中,boy 这个对象,其实他有两个属性,name_name,对于 boy 对象来说,他们没有任何分别,都是属性,并有自己的值,我们使用 _name 的目的是为了更好的描述 getter 与 setter。

    我经常在面试中问候选人一个很有意思的概念问题,function(函数) 与 method(方法) 的区别是什么?往往我们认为 method 是 function 概念上的子集,如果使用 OO 术语来说,method 是一个对象上的函数,对于 Ruby 大神来说,经常会用 receiver 这个词来描述,即 method 是一个有确定 receiver 的 function。

    经常,在 OOP 中,我们倾向使用方法来定义这个类的行为,但是在 MVC 的一些范式中,是将数据与可以被操作的行为分割开的。定义行为的最佳实践并没有唯一解。

    必先正名乎。

    术语的正确使用可以帮你养成正确的交流习惯。

    那么如何创建对象,最简单的方式也许就是工厂模式了,如果你对 Java 很熟悉,这一种方式对你就极为简单:

    function createPerson(name, age) {
      return {
        name: name,
        age: age
      };
    }
    
    var yuchen = createPerson('yuchen', 19);
    
    console.log(yuchen.name); // yuchen
    console.log(typeof yuchen); // object
    

    我们定义了一个返回新对象的函数,仅此而已,但是这样的一个对象非常简单,某些时候是满足我们的需求的。但是,typeof 关键字只能给我们 object 的值,我们没有解决这个 yuchen 是什么的问题。

    function People(name, age) {
        this.name = name;
        this.age = age;
        this.getName = function () {
            return this.name;
        }
    }
    
    var yuchen = new People("yuchen", 29);
    var lele = new People("lele", 28);
    console.log(yuchen.getName()); // yuchen
    console.log(yuchen.getName === lele.getName); // false
    console.log(typeof yuchen); // People
    
    var o = {};
    People.call(o, "another yuchen", 25); // this = o
    console.log(o.getName()); // another yuchen
    

    这个例子引入了一个新的概念,叫做构造函数。我们经常使用大写字母开始来表示这个函数是构造函数,并且使用 new 的关键字来创造一个对象。这样,我们能解决对象识别问题,typeof 关键字给了我们构造函数的名称,我们很开心。

    但是,很可惜的,在 JavaScript 的世界中,并没有构造函数这个严谨的定义。构造函数是什么,构造函数就是一个函数,你当然可以用任何合法的字符定义一个函数。只是我们约定俗成的将可以用作对象创建,并且首字母大写的函数成为构造函数。那么构造函数的秘密是什么呢?就是 this 与 new。

    function someOne(name) {
      this.name = name;
    }
    
    someOne('yuchen'); // this.someOne('yuchen') or window.someOne('yuchen')
    
    console.log(window.name); // yuchen
    console.log(this); // window
    

    这个例子说明了 this 与构造函数的关系,在浏览器的运行环境中,someOne function 是绑定在 window 对象中的,当调用 someOne 时,this 表示调用者,即 window,我们可以看到 name yuchen 被赋值给了 window 对象中的 name 属性中。

    那么,new 关键字的意义是什么呢?可以简单的理解为 new 创建了一个空对象,并且将其作为 this 调用某个函数,再将其返回,大约如同下面的代码。

    var o = {};
    People.call(o, "another yuchen", 25); // this = o
    console.log(o.getName()); // another yuchen
    

    事实上的实现更复杂一些,我鼓励你去自己找到答案,因为我知道我的描述是不正确的或者不严谨的 :)

    无论什么时候,只要你创建了一个函数,就会为其创建 prototype 属性,其指向函数的原型对象。原型对象的属性最初只包含 constructor,指原先对象的函数。例如 Person.prototype.constructor -> Person。当构造函数创建一个新实例后,该实例内部包含一个指针指向构造函数的原型。

    虽然 ECMA-262 管这个指针叫做 Prototype,在 Firefox、Safari、Chrome 中每个对象都有 proto 用于表示原型,但这不是标准。原型最初只包含 constructor 属性,该属性也是共享的,所以可以通过对象实例访问。

    function Person() {
    }
    
    Person.prototype.name = 'yuchen';
    Person.prototype.sayName = function () {
        console.log(this.name);
    };
    
    var yuchen = new Person();
    
    console.log(yuchen.hasOwnProperty("name")); // false
    
    yuchen.sayName(); // yuchen
    
    yuchen.name = 'zhang yuchen';
    yuchen.sayName(); // zhang yuchen
    
    console.log(yuchen.hasOwnProperty("name")); // true
    

    这段代码描述了 JavaScript 很重要的特性:属性查找。规则很简单,现在对象上找这个属性,如果没有,就在原型上找。当我们第二次设置了 zhang yuchen 作为属性时,name 这个属性在 yuchen 这个对象上已经存在了,所以属性被找到。对于执行方法?方法是属性,定义在原型上,所以也被找到了,再加一对小括号,执行!

    function Person(name) {
      this.name = name;
    }
    
    Person.prototype = {
        // constructor: Person,
        sayName: function () {
            console.log(this.name)
        }
    };
    
    var anotherYuchen = new Person('yuchen');
    console.log(typeof anotherYuchen); // object ?!
    console.log(anotherYuchen instanceof Object); // true
    console.log(anotherYuchen instanceof Person); // true
    console.log(anotherYuchen.constructor == Person); // false
    console.log(anotherYuchen.constructor == Object); // true
    

    prototype 上的 constructor 属性为我们解决了 typeof 问题,上面这个例子有点意外,为什么 typeof 返回的是 object ?

    原型对象是指针,修改了就会切断最初原型的联系,一旦修改了原型,原有的指针还是指向旧的原型。参考下面这个例子:

    function Person(name) {
      this.name = name;
    }
    
    Person.prototype.sayName = function () {
      console.log(this.name);
    };
    
    var yuchen = new Person('yuchen');
    yuchen.sayName(); // yuchen
    
    Person.prototype = {
      sayName: function() {
        console.log("I'm not: " + this.name);
      }
    }
                    
    yuchen.sayName(); // yuchen
    
    var cloneYuchen = new Person('yuchen');
    cloneYuchen.sayName();  // I'm not: yuchen
    

    当然,当你使用原型时,也要注意引用类型的情况,例如:

    function Person() {
    }
    
    Person.prototype = {
        constructor: Person,
        name: 'yuchen',
        friends: ['lele', 'jd'],
        sayName: function () {
            console.log(this.name);
        }
    };
    
    var p1 = new Person();
    var p2 = new Person();
    p1.friends.push('duomi');
    console.log(p2.friends); // lele jd duomi ???
    

    这个就很好解释了,因为都是一个对象引用!那么如果你将 p1.friends = [] 然后再做 push 呢?

    相关文章

      网友评论

        本文标题:JavaScript Tips - Vol.3 面向对象程序设计

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