JavaScript对象进阶 Object

基本概念

JavaScript对象的特征
JavaScript的基本数据类型,一种复合值,可看做是属性的无序集合。
每个属性都是一个名/值对。
属性名是字符串,因此可以把对象看成是从字符串到值得映射。

对象除了可以保持自有的属性,还可以从一个称为原型的对象继承属性。
原型式继承(prototypal inheritance)是JavaScript的核心特征。

对象是动态的,可以增加或删除属性。

除了字符串、数值、true、false、null和undefined,其他值都是对象。

对象最常见的用法是对其属性进行创建、设置、查找、删除、检测和枚举等操作。

属性值是任意JavaScript值,或者是一个getter或setter函数。
每个属性还有一些与之相关的值,称为“属性特征(property abttribute)”。

  • 可写(writable attribute),表明是否可以设置属性的值。
  • 可枚举(enumerable attribute),表明是否可以通过for/in结构返回该属性。
  • 可配置(configurable attribute),表明是否可以删除或修改该属性。

每个对象还拥有三个相关的对象特性。

  • 对象的原型(prototype),指向另一个对象,该对象的属性会被当前对象继承。
  • 对象的类(class),一个标识对象类型的字符串。
  • 对象的扩展标记(extensible flag),指明了是否可以向该对象添加新属性。

对象分类

  • 内置:native object
  • 宿主:host object
  • 自定义:user-defined object
    属性分类
  • 自由:own property
  • 继承:inherited property

创建对象

字面量:

let empty = {}; //没有任何属性的对象
let point = { x: 0, y: 0 }; //具有属性的对象
let book = {
  //属性名中有空格,必须用字符串表示。
  "main title": "JavaScript",
  //属性名中有特殊字符,必须用字符串表示
  "sub-title": "The Definitive Gruide", 
  //属性名可以是保留字,但尽量避免。
  for: "all audiences", 
  //属性值可以是一个对象。  
  author: {
    firstname: "David",
    lastname: "Flanagan",
  },
};

对象字面量是一个表达式,每次运算都会创建一个新的对象,其中的属性值也会重新计算。

new操作符和Object创建对象

通过new来创建并初始化一个对象。
JavaScript核心语言中的原始类型都包含内置构造函数,如:Object(),Array(),Date()等。

var person = new Object();
    person.name = "lisi";
    person.age = 21;
    person.family = ["lida","lier","wangwu"];
    person.say = function(){
        alert(this.name);
    }

工厂模式:

工厂模式解决了重复实例化多个对象的问题,但没有解决对象识别的问题

function createPerson(name,age,family) {
    var o = new Object();
    o.name = name;
    o.age = age;
    o.family = family;
    o.say = function(){
        alert(this.name);
    }
    return o;
}

var person1 =  createPerson("lisi",21,["lida","lier","wangwu"]);   
//instanceof无法判断它是谁的实例,只能判断他是对象,构造函数都可以判断出
var person2 =  createPerson("wangwu",18,["lida","lier","lisi"]);
console.log(person1 instanceof Object);              
//true
console.log(person1 instanceof createPerson);
//false

构造函数模式

function Person(name,age,family) {
    this.name = name;
    this.age = age;
    this.family = family;
    this.say = function(){
        alert(this.name);
    }
}
var person1 = new Person("lisi",21,["lida","lier","wangwu"]);
var person2 = new Person("lisi",21,["lida","lier","lisi"]);
console.log(person1 instanceof Object); //true
console.log(person1 instanceof Person); //true
console.log(person2 instanceof Object); //true
console.log(person2 instanceof Person); //true
console.log(person1.constructor);      //constructor 属性返回对创建此对象的数组、函数的引用  [Function: Person]

构造函数模式与工厂模式的不同:
没有显示地创建对象
直接将属性和方法赋给了this对象
没有return

调用构造函数步骤:
创建了一个新的对象
将构造函数的作用域赋给新的对象(将this指向这个新对象)
执行构造函数代码
返回新对象

原型

每一个JavaScript对象(null除外)都和另一个对象相关联。

  • “另一个对象”就是原型对象
  • 每一个对象都从原型继承属性。

所有通过对象字面量创建的对象都具有同一个原型对象。

  • 通过Object.prototype获得原型对象的引用。

通过new和构造函数创建的对象的原型就是构造函数的prototype属性引用的对象。

  • 如:new Array()对象的原型就是Array.prototype。

极少对象没有原型。

  • 如:Object.prototype,它不继承任何属性。

每个对象都有一个__proto__属性 ,并且指向他的prototype原型对象。

所有内置构造函数都具有一个继承自Object.prototype的原型。

  • prototype原型对象里的constructor指向构造函数本身
  • 如:Array.prototype的属性继承自Object.prototype。
  • 通过层级的原型继承形成的链接,称为“原型链”(prototype chain)。原型链是有终点的,最后查找到Object.prototype,Object.prototype.proto === null,意味着查找结束
//obj和arr都继承自Object
let obj = { value: 100 };
Object.prototype.flag = "head";
console.log(obj.flag);//flag
let arr = [1, 2, 3, 4];
console.log(arr.flag);//flag

Object.creat(proto,[propertiesObject]):创建一个新对象,使用现有的对象来提供新创建的对象的__proto__。

  • proto:新创建对象的原型对象
  • propertiesObject:需要传入一个对象
// obj1继承了属性x和y。
let obj1 = Object.create({ x: 1, y: 2 });
//obj2不继承任何属性和方法。没有__proto__属性
let obj2 = Object.create(null);
//obj3是一个普通的空对象。
let obj3 = Object.create(Object.prototype);
function Person() {
}

Person.prototype.name = "lisi";
Person.prototype.age = 21;
Person.prototype.family = ["lida","lier","wangwu"];
Person.prototype.say = function(){
    alert(this.name);
};
console.log(Person.prototype);   //Object{name: 'lisi', age: 21, family: Array[3]}

var person1 = new Person();        //创建一个实例person1
console.log(person1.name);        //lisi

var person2 = new Person();        //创建实例person2
person2.name = "wangwu";
person2.family = ["lida","lier","lisi"];
console.log(person2);            //Person {name: "wangwu", family: Array[3]}
// console.log(person2.prototype.name);         //报错
console.log(person2.age);              //21

原型模式的好处是所有对象实例共享它的属性和方法(即所谓的共有属性),此外还可设置实例自己的属性(方法)(即所谓的私有属性),可以覆盖原型对象上的同名属性(方法)。

原型与构造函数混合使用

构造函数模式用于定义实例属性,原型模式用于定义方法和共享的属性

function Person(name,age,family){
    this.name = name;
    this.age = age;
    this.family = family;
}

Person.prototype = {
    constructor: Person,  //每个函数都有prototype属性,指向该函数原型对象,原型对象都有constructor属性,这是一个指向prototype属性所在函数的指针
    say: function(){
        alert(this.name);
    }
}

var person1 = new Person("lisi",21,["lida","lier","wangwu"]);
console.log(person1);
var person2 = new Person("wangwu",21,["lida","lier","lisi"]);
console.log(person2);

属性相关

关联数组的对象

在这里插入代码片let result = {
  data1: {
    name: "Language",
    value: "Chinese",
  },
  data2: {
    name: "Country",
    value: "China",
  },
  data3: {
    name: "Gender",
    value: "Male",
  },
};
for (let i = 1; i < 4; i++) {
  let data = result["data" + i];
  console.log(`${data.name}-->${data.value}`);
}
//Language-->Chinese
//Country-->China
//Gender-->Male

继承

继承是指一个对象直接使用另外一个对象的属性和方法

属性的继承是通过在一个类内执行另外一个类的构造函数,通过call指定this为当前执行环境,这样就可以得到另外一个类的所有属性。

function Person (name, age) {
    this.name = name
    this.age = age
}

// 方法定义在构造函数的原型上
Person.prototype.getName = function () { console.log(this.name)}

function Teacher (name, age, subject) {
    Person.call(this, name, age)
    this.subject = subject
}
//实例化
var teacher = new Teacher('jack', 25, Math)

方法的继承
类的方法都定义在prototype里,那其实我们只需要把Person.prototype的备份赋值给Teacher.prototype即可。Object.create简单说就是新建一个对象,使用现有的对象赋值给新建对象的__proto__。如果直接赋值,那会是引用关系,修改Teacher. prototype的同时也会修改Person.prototype。
在给Teacher类添加方法时,应该在修改prototype以后,否则会被覆盖掉,原因是赋值前后的属性值是不同的对象。prototype里有个属性constructor指向构造函数本身,但是因为我们是复制其他类的prototype,所以这个指向是不对的,需要更正一下。如果不修改,会导致我们类型判断出错

Teacher.prototype = Object.create(Person.prototype)

Teacher.prototype.constructor = Teacher

属性访问错误

  • 查询一个不存在的属性并不会报错,如果在对象o自身的属性或继承的属性中均未找到属性x,属性访问表达式o.x返回undefined
  • 但是,如果对象不存在,那么试图查询这个不存在的对象的属性就会报错
// let one = {};//没有属性,会返回undefined
 let one = { two: { three: 3 } };
console.log(one.two.three);
if (one) {
  if (one.two) {
    if (one.two.three) console.log(one.two.three);
  }
}
console.log(one && one.two && one.two.three);
console.log(one?.two?.three); // Null传导运算符
// “?.”运算符表示,如果返回了null或者undefined,则不再往下继续运算,直接返回undefined,而undefined || ‘默认名字’又构成了一个短路表达式,返回默认名字。

在这些场景下给对象o设置属性p会失败:

  • o中的属性p是只读的:不能给只读属性重新赋值(defineProperty()方法中有一个例外,可以对可配置的只读属性重新赋值)。
  • o中的属性p是继承属性,且它是只读的:不能通过同名自有属性覆盖只读的继承属性。
  • o中不存在自有属性p:o没有使用setter方法继承属性p,并且o的可扩展性(extensible attribute)是false。如果o中不存在p,而且没有setter方法可供调用,则p一定会添加至o中。但如果o不是可扩展的,那么在o中不能定义新属性。

删除属性

delete运算符可以删除对象的属性。

  • 它的操作数应当是一个属性访问表达式。
  • delete只是断开属性和宿主对象的联系,而不会去操作属性中的属性。
  • delete运算符只能删除自有属性,不能删除继承属性。
  • 要删除继承属性必须从定义这个属性的原型对象上删除它,而且这会影响到所有继承自这个原型的对象。
  • delete不能删除那些可配置性为false的属性。
  • 某些内置对象的属性是不可配置的,比如通过变量声明和函数声明创建的全局对象的属性。

检测属性

判断某个属性是否存在于某个对象中,可以通过in运算符、**hasOwnProperty()propertyIsEnumerable()**方法,甚至也可以仅通过属性查询。

枚举属性

for/in循环可以在循环体中遍历对象中所有可枚举的属性(包括自有属性和继承的属性),把属性名称赋值给循环变量。

  • 对象继承的内置方法不可枚举的,但在代码中给对象添加的属性都是可枚举的。
let o =Object.create({m:10,n:20});
o.x=1; o.y=2; o.z=3;
for (let p in o) {
  console.log(p, o[p]);
}
//x 1
//y 2
//z 3
//m 10
//n 20
  • Object.keys(),它返回一个数组,这个数组由对象中可枚举的自有属性的名称组成。
let o = Object.create({ m: 10, n: 20 });
o.x = 1; o.y = 2; o.z = 3;
console.log(Object.keys(o));
//["x","y","z"]
  • Object.getOwnPropertyNames(),它和Ojbect.keys()类似,只是它返回对象的所有自有属性的名称,而不仅仅是可枚举的属性。
let o = Object.create({ m: 10, n: 20 });
o.x = 1; o.y = 2; o.z = 3;
console.log(Object.getOwnPropertyNames(o));
//["x","y","z","m","n"]

属性getter和setter

对象属性是由名字、值和一组特性(attribute)构成的。

  • 在ES5中,属性值可以用一个或两个方法替代,这两个方法就是getter和setter。
  • 由getter和setter定义的属性称做“存取器属性”(accessor property),它不同于“数据属性”(dataproperty),数据属性只有一个简单的值。
  • 当程序查询存取器属性的值时,JavaScript调用getter方法(无参数)。 这个方法的返回值就是属性存取表达式的值。
  • 当程序设置一个存取器属性的值时,JavaScript调用setter方法,将赋值表达式右侧的值当做参数传入setter。
  • 从某种意义上讲,这个方法负责“设置”属性值。可以忽略setter方法的返回值。
let circle = {
  r: 10,
  get round() {
    return 2 * this.r * Math.PI;
  },
  set round(v) {
    this.r = v / 2 / Math.PI;
  },
  get area() {
    return Math.PI * this.r ** 2;
  },
};
console.log(circle.round, circle.area);//调用get方法
circle.round = 60;//调用的set方法
console.log(circle.r, circle.area);//调用get方法

let circle1 = Object.create(circle);
circle1.r = 20;
console.log(circle1.round);//调用get
circle1.round = 500;
console.log(circle1.r, circle1.area);

属性的特性

除了包含名字和值之外,属性还包含一些标识它们可写、可枚举和可配置的特性。

  • 可以通过这些API给原型对象添加方法,并将它们设置成不可枚举的,这让它们看起来更像内置方法。
  • 可以通过这些API给对象定义不能修改或删除的属性,借此“锁定”这个对象。

数据属性的4个特性

  • 值(value)、可写性(writable)、可枚举性(enumerable)和可配置性(configurable)。

存取器属性不具有值(value)特性和可写(writable)性。

  • 它们的可写性是由setter方法存在与否决定的。
  • 因此存取器属性的4个特性是读取(get)、写入(set)、可枚举(enumerable)性和可配置(configurable)性。

通过一个名为 “属性描述符”(property descriptor) 的对象实现属性特性的查询和设置操作。

  • 这个对象代表那4个特性。
  • 描述符对象的属性和它们所描述的属性特性是同名的。因此,数据属性的描述符对象的属性有value、writable、enumerable和configurable。
  • Object.getOwnPropertyDescriptor(obj, prop)可以获得某个对象特定属性的属性描述符。
  • 参数:obj:需要查找的目标对象 prop:目标对象内属性名称

要想设置属性的特性,或者想让新建属性具有某种特性,则需要调用Object.definePeoperty(),传入要修改的对象、要创建或修改的属性的名称以及属性描述符对象。

let o = {};
Object.defineProperty(o, "x", {
  value: 10,
  writable: true,
  enumerable: false,
  configurable: false,
});
console.log(o.x, Object.keys(o));

Object.defineProperty(o, "x", { enumerable: true });
  • 传入Object.defineProperty()的属性描述符对象不必包含所有4个特性。

同时修改或创建多个属性:

let p = Object.defineProperties(
  {},
  {
    x: { value: 1, writable: true, enumerable: true, configurable: true },
    y: { value: 1, writable: true, enumerable: true, configurable: true },
    r: {
      get: function () {
        return Math.hypot(this.x, this.y);
      },
      enumerable: true,
      configurable: true,
    },
  }
);

对象特性

原型属性

原型属性是在实例对象创建之初就设置好的。

  • 通过对象直接量创建的对象使用Object.prototype作为它们的原型。
  • 通过new创建的对象使用构造函数( constructor属性)的prototype属性作为它们的原型。
  • 通过Object.create()创建的对象使用第一个参数(也可以是null)作为它们的原型。

将对象作为参数传入Object.getPrototypeOf() 可以查询它的原型。

要想检测一个对象是否是另一个对象的原型(或处于原型链中),使用isPrototypeOf() 方法。

  • 和instanceof运算符非常类似。

可扩展性

javascript 对象中的可扩展性指的是:是否可以给对象添加新属性。所有的内置对象和自定义对象显示的都是可扩展的,对于宿主对象,则有javascript 引擎决定。

下面有几个函数是设置对象的可扩展性:

  • Object.isExtensible(Object); 检查对象是否可以扩展。
  • Object.preventExtensions(Object);设置对象不可扩展,也就是不能添加新的属性,但如果该对象的原型,添加了新的属性,那么该对象也将继承该属性。
  • Object.seal(Object);它除了可以设置对象的不可扩展,还可以设置对象的自有属性都设置为不可配置的,不能删除和配置。对于它已经有的可写属性依然可以设置。
  • Object.isSealed(Object); 检查对象是否封闭。
  • Object.freeze();更严格的锁定对象(冻结)。除了将对象设置为不可扩展,属性设置为不可配置,所有的自有属性设置为只读的,(如果对象存储器属性有setter方法,存储器属性不受影响,依然可以通过属性赋值给他们)。
  • Object.isFrozen() 来检测对象是否冻结。

序列化对象

对象序列化(serialization)是指将对象的状态转换为字符串,也可将字符串还原为对象。

  • ES5提供了内置函数JSON.stringify()JSON.parse() 用来序列化反序列化JavaScript对象。
  • JSON的全称是"JavaScript Object Notation"——JavaScript对象表示法
  • 注意事项:
  • 函数、RegExp、Error对象和undefined值不能序列化和还原。
  • JSON.stringify()只能序列化对象可枚举的自有属性。
  • 对于一个不能序列化的属性来说,在序列化后的输出字符串中会将这个属性省略掉。

Object构造函数的方法

  • Object.assign() 通过复制一个或多个对象来创建一个新的对象。
  • Object.create() 使用指定的原型对象和属性创建一个新对象。
  • Object.defineProperty() 给对象添加一个属性并指定该属性的配置。
  • Object.defineProperties() 给对象添加多个属性并分别指定它们的配置。
  • Object.entries() 返回给定对象自身可枚举属性的 [key, value] 数组。
  • Object.freeze() 冻结对象:其他代码不能删除或更改任何属性。
  • Object.getOwnPropertyDescriptor() 返回对象指定的属性配置。
  • Object.getOwnPropertyNames() 返回一个数组,它包含了指定对象所有的可枚举或不可枚举的属性名。
  • Object.getOwnPropertySymbols() 返回一个数组,它包含了指定对象自身所有的符号属性。
  • Object.getPrototypeOf() 返回指定对象的原型对象。
  • Object.is() 比较两个值是否相同。所有 NaN 值都相等(这与== 和 ===不同)。
  • Object.isExtensible() 判断对象是否可扩展。
  • Object.isFrozen() 判断对象是否已经冻结。
  • Object.isSealed() 判断对象是否已经密封。
  • Object.keys() 返回一个包含所有给定对象自身可枚举属性名称的数组。
  • Object.preventExtensions() 防止对象的任何扩展。
  • Object.seal() 防止其他代码删除对象的属性。
  • Object.setPrototypeOf() 设置对象的原型(即内部 [[Prototype]] 属性)。
  • Object.values() 返回给定对象自身可枚举值的数组。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值