JavaScript | 理解对象属性和创建对象的方式

JavaScript 的面向对象编程是比较难的部分,本文目前只针对es5来讲解js的对象,后续会在es6的语法基础上继续补充。欢迎指正

首先,创建自定义对象表示有两种方式

  • 创建一个Object的实例
var person = new Object();
    person.name = "nice";
    person.age = 2;
    person.address = "china";
    person.sayName = function () {
        console.log(this.name);
    }
  • 使用对象字面量创建
var person = {
        name:"hello",
        age:12,
        address:"shanghai",
        sayName:function () {
            console.log(this.name);
        }
    }

现在一般使用第二种方式来创建自定义对象,为啥,因为写起来简单啊老铁们。

上述都创建了一个person对象,其中,name,age,address叫做该对象的属性,sayName显然是该对象的一个方法,返回name

那么下面讲一讲es中的两种属性:数据属性和访问器属性

  • 数据属性
    1.数据属性包含一个数据值。比如上面的,name的数据值就是hello
    2.数据属性有4种特性:
    a. configurable 可配置性:能否通过delete删除属性从而重新定义
    该属性,能否修改属性的特性,能否将属性改为访问器属性,
    默认为true
    b. enumerable 可枚举性: 能否通过for in 循环返回属性,默认为
    true
    c. writable 可修改性。默认为true。
    d. value 该属性的数据值。默认为undefined

来几个例子给大家讲一讲这几个属性

  1. 数据属性configurable默认为true,如果通过Object.defineProperty将某个属性的configurable修改为false,则该属性不能被删除。且再次改回configurabe 为true时会抛出错误
var person = {
        name:"hello",
        age:12,
        address:"shanghai",
        sayName:function () {
            console.log(this.name);
        }
    }
    person.name = "world";
    person.sayName();
    // 修改对象的configurable属性   将person对象的name属性变为不可删除的
    Object.defineProperty(person,"name",{
        configurable:false
    })
    delete person.name;
    person.sayName();

执行结果会打印两遍world,说明将name属性修改为不可配置的时候,delete方法会失效

  1. 将enumerable修改为false之后,用Object.keys()方法可以列出可枚举属性
// 可枚举性
    console.log(Object.keys(person));
    Object.defineProperty(person,"name",{
        enumerable:false
    })
    console.log(Object.keys(person));
执行结果

person对象中的name属性已经不可枚举了

  1. writable 很好理解,修改为false之后就不能改变其数据值
person.sayName();
    Object.defineProperty(person,"name",{
        writable:false,//person的name属性变为不可更改的
    })
    person.name = "world";
    person.sayName();

试图给person.name赋值,不会生效,打印结果还是hello

  • 访问器属性

访问器属性不包含数据值。属性有4个特性,configurable,enumerable,get函数,set函数。一般在设置一个属性的值会导致另一个属性的值发生变化的场景下会使用访问器属性

举个栗子

// 访问器属性
    var book = {
        _year :2004,
        editon:1
    }

上述book对象,_year 和 editon 都是数据属性,但是_year这个特殊的命名方式告诉我们,_year是一个只能通过对象方法访问的属性,暗示我们 year 是一个访问器属性。

Object.defineProperty(book,"year",{
        get:function () {
            return this._year;
        },
        set:function (newValue) {
            if (newValue > 2004){
                this._year = newValue;
                this.editon = newValue - 2004 + this.editon;
            }
        }
    })
    book.year = 2008;
    console.log(book.editon);

最后的打印的book.edition的值为5 .上述代码通过set方法在访问器属性的值改变的同时也改变了edition这个数据属性的值

下面讲讲创建对象的N个方法,主要讲3个

构造函数模式

//构造函数来创建对象
    function Person(name,age,address) {
        this.name = name;
        this.age = age;
        this.address = address;
        this.sayName = function () {
            console.log(this.name);
        }
        // console.log(this);
    }
    var person1 = new Person("nice",12,"china");

使用构造函数模式创建实例对象,必须要使用new 操作符。构造函数一般为大写字母开头,区别于其他的函数。构造函数中的this指向新对象(即将生成的实例对象)

构造函数的问题就在于,每个方法要在每个实例上重新创建一遍。简而言之,不同实例对象的同名函数是不相等的。

/使用构造函数的同名函数不相等
    var person1 = new Person("abc",12,"china");
    var person2 = new Person("def",23,"english");

    console.log(person1.sayName == person2.sayName);

上述代码的打印结果为 false。

由于构造函数不同实例对象都有不同的方法(尽管方法同名,占据的空间不同),原型模式的诞生就解决了这个问题。

function Animal() {

    }

    Animal.prototype.name = "cat";
    Animal.prototype.age = 12;
    Animal.prototype.sayName = function () {
        console.log(this.name);
    }
var animal1 = new Animal();
var animal2 = new Animal();
console.log(animal1.sayName == animal2.sayName);

上述代码输出结果为 true,说明实例对象animal1和实例对象animal2已经共用了一个方法

我们先来理解一下什么叫原型对象
我们创建了一个函数,那么该函数就存在一个prototype属性,这个属性指向该函数的原型对象。这个原型对象也存在一个属性,叫做constructor,这个属性是一个指针,指回构造函数本身。
然后我们新建了一个实例对象animal1 ,该对象的prototype指针指向它的构造函数的原型对象。
给大家画了一张图:


构造函数和原型对象和实例对象

所以,实例对象其实和构造函数没有直接的关联,与之相关联的是原型对象。

然后我们来看看,js是如何处理实例对象与原型对象之间的关系的。

  • 1.生成实例对象,不给实例对象的属性赋值。
function Animal() {

    }

    Animal.prototype.name = "cat";
    Animal.prototype.age = 12;
    Animal.prototype.sayName = function () {
        console.log(this.name);
    }

    var animal1 = new Animal();
    animal1.sayName(); // 打印 cat
  • 2.给实例对象的属性赋值
var animal1 = new Animal();
    animal1.name = "rubbit";
    animal1.sayName(); // 打印 rubbit

此时,实例对象的name属性被赋值为rubbit,但是原型对象中name属性的值仍然是 cat

  • 3.原型对象中存在引用类型属性,多个实例对象共享了该引用类型属性
animal1.friends.push("fish","duck");
    console.log(animal1.friends); // ["dog","bird","fish","duck"]
    var animal2 = new Animal();
    console.log(animal2.friends); //["dog","bird","fish","duck"]

animal1实例对象对引用类型属性的改变,同时被animal2共享了。在某些时候,这不是我们想要的结果

组合构造函数和原型模式

上文已经说了,构造函数模式会造成空间的浪费,原型模式的属性被所有实例对象共享,都有各自的缺点。那么组合模式就解决了这两个问题。
构造函数用于定义实例属性,该属性为实例独有,接收传参,原型模式用于定义方法和实例共享的属性,取两者之长。

// 构造函数模式
    function Person(name,age,address) {
        this.name = name;
        this.age = age;
        this.address = address;
        this.friends= ["bob","nicy"];
    }

    // 原型模式
    Person.prototype = {
        constructor : Person,
        sayName:function () {
            console.log(this.name);
        }
    }

    var person1 = new Person("cat",12,"eng");
    var person2 = new Person("dog",13,"cha");

    person1.sayName(); //cat
    person2.sayName(); // dog

    console.log(person1.sayName === person2.sayName);  //true
    console.log(typeof person1); // Object
    person1.friends.push("van");
    console.log(person1.friends); // ["bob","nicy","van"]
    console.log(person2.friends); // ["bob","nicy"]

当然还有寄生构造函数模式,稳妥构造函数模式等不太常用的方式,有兴趣大家可以自己去了解一下。

好了,到这里,什么是对象,以及在不同情况下如果创建对象已经基本讲完了。简单地说,构造函数,原型和实例之间的关系就是:每一个构造函数都有一个原型对象,原型对象都包含一个指向构造函数的指针,实例对象都包含一个指向原型对象的内部指针。

好了,旁友们,下一篇我们讲继承。

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

推荐阅读更多精彩内容