JS笔记—— 对象 (原型对象)

    JavaScript对象是一个很有意思的数据类型,由于js没有类的概念,js对象就承担起JavaScript面向对象的重任。
    JavaScript 并没有类的概念,但是它有对象。
    ECMSscript将对象定义为无序属性的集合,其属性可以包含基本值、对象或者函数。
    JavaScript的对象的属性没有严格的顺序,每个属性和方法都需有一个名字和对应的值,看起来和JSON基本没有区别(实际上js对象和JSON可相互转换)。

对象基本概念

对象的创建非常的简单>>>

显示申明
var obj = new Object()
obj.name = "tom"
obj.age = 21
obj.sayName = function(){return this.name}

**对象字面量**
var obj = {name:"tom",
            age:21,
            sayName:function(){return this.name}
        }

    ECMA-262第五版定义了内部采用的特性时(attribute), 描述了属性(property)
注:property是用来描述对象的,attribute是用来描述property的元数据,此处的attribute区别于HTML的attribute,HTML标签的attribute是标签本身所具有的描述数据,和property描述的对象(DOM)不同,也就是这两个的描述参照并不是同一基准

属性

    为了支持JavaScript引擎工作,ECMAScript5规定了属性的类型:数据属性访问器属性

数据属性

    数据属性:数据属性包含一个数据值的位置,在这个位置可以读取和写入值。数据属性有四个描述其行为的特性。

[[Configurable]]:是否能够对属性进行其他更改,包括删除,修改等
[[Enumerable]]:是否能通过for-in循环返回属性
[[Writable]]:如字面意思,能否写入值(修改值)
[[Value]]:包含属性的数据值,代表属性值得存储位置

四个特性默认值为,true,true,true,undefined

    修改属性默认特性:Object.defineProperty(obj,propertyName,attrConfig)
    该方法接收三个参数:修改的对象,修改的属性值名称,该属性的特性描述符对象

例如:
var Car = {}
Object.defineProperty(Car,"price",{
    writeable:false,
    value:120000
})

注意:运用Object.defineProperty()产生的属性的特性默认为false,false,false,undefined
所以上面的例子中Car的price属性中,configurable默认为false,enumerable默认为false
直接通过对象创建的属性并无此限制

访问器属性

访问器属性就是我们在其他语言中常看到的getter和setter函数,访问器属性并不包含值,它也有四个特性

[[Configurable]]:是否能够对属性进行其他更改,包括删除,修改等
[[Enumerable]]:是否能通过for-in循环返回属性
[[Get]]:读取数据时调用的函数,默认为undefined
[[Set]]:写入数据时调用的函数,默认为undefined

例如:

var people = {
    name:"tom",
    _age:17,
    adult:false
}

Object.defineProperty(people,"age",{
    get:function(){
        return this._age
    },
    set:function(val){
        this._age = val
        if(val > 18){
            this.adult = true
        }
    }
})

定义多个属性
要想定义一个属性就调用一次definProperty方法会非常繁琐,ES5就存在方法defineProperties支持多属性定义,只是传入的参数有些许区别
例如:

Object.defineProperties("people",{
    name:{
        writable:false,
        value:"Amy"
    },
    _age:{
        get:function(){}
        set:function(){}
    }
})

读取属性的特性
使用ECMAScript5的Object.getOwnPropertyDescriptor(),可以取得给定属性的描述符
参数:包含待查属性的对象,待查属性的名称
例如:

var perple = {}
Object.defineProperty(perple,"name",{
    writable:false,
    value:"Jack"
})
var result = Object.getOwnPropertyDescriptor(people,"name")
console.log(result.value)
console.log(result.writable)

原型对象

    对象的创建往往是重复的,就好像创建一个学生对象并不能完成所有工作,如果牵扯到班级的概念,可能就要创建班级中所有学生的对象了,对象也有很多的重复性,比如一个班的人所在班级学校都是一样的,是共有的,为了简化代码,也就有了原型对象的概念。
注:对象创建中,每一个学生代表一个实例,这和其他OO(面向对象)语言的类具有相似的理念。

    我们创建的每一个函数都有一个prototype(原型)属性,这个属性是一个指针,指向一个对象,而这个对象的用途。就是可以包含那些由特定类型的实例共享的属性和方法。通过字面意思,prototype就是通过调用构造函数而创建的那个对象的原型对象。而原型对象也有一个指针constructor指向他所代表的构造函数。例如:Person.prototype.constructor == Person

    原型对象就像是一个班级的标签,实例就是班级的学生,不管这个班级来了多少个新学生,只要这个班级叫一年级,那么这个班级的学生就都是一年级学生。原型对象包含所有实例公共的属性为所有实例共享。
    创建了自定义的构造函数之后,其原型对象默认只会取得 constructor 属性;至于其他方法,则都是从 Object 继承而来的。当调用构造函数创建一个新实例后,该实例的内部将包含一个指针(内部属性),指向构造函数的原型对象。ECMA-262 第 5 版中管这个指针叫 [[Prototype]] 。虽然在脚本中没有标准的方式访问 [[Prototype]] ,但 Firefox、Safari 和 Chrome 在每个对象上都支持一个属性__proto__;而在其他实现中,这个属性对脚本则是完全不可见的。不过,要明确的真正重要的一点就是,这个连接存在于实例与构造函数的原型对象之间,而不是存在于实例与构造函数之间。

function Person(){}
    Person.prototype.name="Nicholis"
    Person.prototype.age=29
    Person.prototype.job="Software Engineer"
    Person.prototype.sayName=function(){
        console.log(this.name)
}

var person1 = new Person()
person1 .sayName() // Nicholis
var person2 = new Person()
person2 .sayName() // Nicholis
console.log(person1 .sayName == person2 .sayName) // true

对象实例关系图

需要注意的几点:

  1. 构造函数的prototype指针指向该特定类型对象(Person)的原型对象,同时该对象的constructor指正指向该构造函数。
  2. 实例的[[prototype]]并非构造函数的prototype指针,在主流浏览器中的实际实现是_proto_指针,指向构造函数的原型对象。
  3. 实例与构造函数并无直接关系

原型对象属性和实例属性

原型对象属性说不定也有和实例属性冲突的时候,比如运动会的时候大多数人在开幕式的任务就是走队列喊口号,但是领头的人喊的口号和班里其他人很有可能不同。
领头:一年三班
同学:团结一心
领头一年三班
同学:共创佳绩

在JS中,发生了冲突该如何选择,和现实中也是相差无几的。

function Class13(){}
Class13.prototype.sentence= "永争第一";
Class13.prototype.say = function(){
    alert(this.sentence);
};
var student = new Class13(); // 大多数同学
var leader= new Class13();  // 领头人
leader.sentence= "一年三班";
alert(leader.say ()); //"一年三班" —— 来自实例
alert(student .say ()); //"永争第一" ——原型

    以上代码不难看出,student实例没创建实例属性,便输出了原型对象属性,而设置了实例属性的leader输出的是实例属性,领头人在作为同学的基础上口号是“永争第一”,但是新的任务让他产生了新的属性,屏蔽了之前的设置。
    当为对象实例添加一个属性时,这个属性就会屏蔽原型对象中保存的同名属性;换句话说,添加这个属性只会阻止我们访问原型中的那个属性,但不会修改那个属性。即使将这个属性设置为 null ,也只会在实例中设置这个属性,而不会恢复其指向原型的连接。

更简单的原型语法

每一次都要书写xxx.prototype着实让人心烦,还好我们有更简便的方法——字面量

function Person(){}
Person.prototype = {
    name : "Nicholas",
    age : 29,
    job: "Software Engineer",
    sayName : function () {
        alert(this.name);
    }
};

但是用字面量的方法hi重写整个原型对象,导致的直接后果就是constructor指针不再是默认指向构造函数,而是Object
解决办法一:在字面量中定义constructor

function Person(){}
Person.prototype = {
    constructor : Person,
    name : "Nicholas",
    age : 29,
    job: "Software Engineer",
    sayName : function () {
        alert(this.name);
    }
};

问题:constructor默认数据属性enumerable会设置为true

解决办法二:通过Object.defineProperty()重新定义constructor

Object.defineProperty(Person.prototype, "constructor", {
    enumerable: false,
    value: Person
});

原型对象的问题

  1. 省略了为构造函数传递初始化参数这一环节,结果所有实例在默认情况下都将取得相同的属性值。
  2. 原型对象包含引用类型值的属性,会使得实例对该属性的操作产生连锁反应。
function Person(){
}
Person.prototype = {
    constructor: Person,
    name : "Nicholas",
    age : 29,
    job : "Software Engineer",
    friends : ["Shelby", "Court"],
    sayName : function () {
        alert(this.name);
    }
};
var person1 = new Person();
var person2 = new Person();
person1.friends.push("Van");
alert(person1.friends); //"Shelby,Court,Van"
alert(person2.friends); //"Shelby,Court,Van"
alert(person1.friends === person2.friends); //true

以上代码反映了引用类型在原型对象中的问题:当实例直接对引用类型进行方法操作时,将不会为实例创建同名实例属性,而是寻址,找到friends数组的地址,再进行push操作,这就导致了实例改变了原型对象的值,使得其他实例也会取得改变后的friends。


单纯的将引用类型进行赋值操作,那就不会有以上问题,因为这个过程中并没有寻址这个环节,而直接进行了实例属性申明和赋值。如下:

var person1 = new Person();
var person2 = new Person();
person1.friends=["hello"];
console.log(person1.friends); //"hello"
console.log(person2.friends); //"Shelby,Court,Van"
console.log(person1.friends === person2.friends); //false
未命名文件.png

相关方法

isPrototypeOf()
用途:判断实例与目标是否含有原型对象关系,即判断对象是否是某实例的原型对象

alert(Person.prototype.isPrototypeOf(person1)); //true
alert(Person.prototype.isPrototypeOf(person2)); //true

Object.getPrototypeOf()
用途:获取实例的原型对象

alert(Object.getPrototypeOf(person1) == Person.prototype); //true
alert(Object.getPrototypeOf(person1).name); //"Nicholas"

hasOwnProperty()
用途: 检测一个属性是存在于实例中。

function Person(){}
Person.prototype.name = "Tom";
var person1 = new Person();
var person2 = new Person();
person2.name = "Amy"
alert(person1.hasOwnProperty("name")); //false
alert(person2.hasOwnProperty("name")); //true
alert(person2.hasOwnProperty("nam")); //false

注:该方法结合操作符 in 可判断属性来自实例还是原型
in操作符会判断给定属性是否能通过对象访问,如果能,返回true,不能,返回false
结合hasOwnProperty()就很好判断属性来源了

function hasPrototypeProperty(object, name){
    return !object.hasOwnProperty(name) && (name in object);
}
alert(hasPrototypeProperty(person1, "name")); //true
alert(hasPrototypeProperty(person2, "name")); //false

Object.keys()
用途:要取得对象上所有可枚举的实例属性。接收一个对象作为参数,返回一个包含所有可枚举属性的字符串数组。

function Person(){
}
Person.prototype.name = "Nicholas";
Person.prototype.age = 29;
Person.prototype.job = "Software Engineer";
Person.prototype.sayName = function(){
    alert(this.name);
};
var keys = Object.keys(Person.prototype);
alert(keys); //"name,age,job,sayName"
var p1 = new Person();
p1.name = "Rob";
p1.age = 31;
var p1keys = Object.keys(p1);
alert(p1keys); //"name,age"

Object.getOwnPropertyNames()
用途:得到所有实例属性,无论它是否可枚举。

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