js中的对象

139 阅读8分钟

对象

JavaScript中的对象其实就是一组数据和功能的集合。对象通过new操作符后跟对象类型来创建。对象是一种复合的数据类型,在对象中可以保存多个不数据类型的属性。

对象的分类:

  1. 内建对象:由ES标准中定义的对象,在任何的ES的实现中都可以使用。
  2. 宿主对象:由JS运行环境提供的对象,目前主要指由浏览器提供的对象。(如BOM,DOM)
  3. 自定义对象:由开发人员自己创建的对象。

创建对象:var obj = new Object();使用new关键词调用的函数是构造函数是专门用来创建对象的函数。

var person = {
  name:"孙悟空"
  age : 32,
  gender : 'male',
  bio : function() {
    alert(this.name[0] + ' ' + this.name[1] + ' is ' + this.age + ' years old. + ' and ' + this.interests[1] + '.');
  },
  greeting: function() {
    alert('Hi! I\'m ' + this.name[0] + '.');
  }
};

如上所示,一个对象由许多的成员组成,每一个成员都拥有一个名字(像上面的 name、age),和一个值(如 ['Bob', 'Smith']、32)。每一个名字/值对用逗号隔开,并且名和值之间由冒号分隔开。

对象成员的值可以是任意的,在上述的 person 对象里有字符串 (string),数字 (number),两个函数 (function)。前3个被称为对象的属性,后两个成员是函数,允许对象对资料做一些操作,被称为对象的方法 。

访问对象的属性和方法时,我们可以使用点表示法,也可以通过点表示法来设置属性的值。语法如下:

person.name
person.age="18"
person.bio()

对象的合并

E6专门为合并对象提供了Object.assign()方法,这个方法接收一个目标对象和一个或多个源对象作为参数 ,然后将每个源对象中可枚举和自有属性复制到目标对象,这个方法是使用源对象上的[[Get]]取得属性值,再使用目标对象上的[[Set]]设置属性值。

let dest,src,result;
desc={};
sre={id:'src'};
result=Object.assign(dest,src);
console.log(dest===result);//true
console.log(dest!==src);//true
console.log(dest);//{id:src}
console.log(src);//{id:src}

Object.assign()实际上是对每个源对象执行浅复制。如果多个源对象都有相同的属性,则使用最后一个复制的值。如果赋值期间出错,则操作会停止,但在停止前已经完成的修改会继续存在、

创建对象

工厂模式

工厂模式是一种广泛运用的设计模式。

function createPerson(name,age,job){
    let p=new Object();
    p.name=name;
    p.age=age;
    p.job=job;
    p.sayName=function(){
        conlose.log(this.name);
    }
    return p;   
}

let p1=createPerson("孙悟空",29,"弼马温");
let p2=createPerson("猪八戒",29,"天蓬元帅");

这里createPerson()函数接收3个参数,根据这几个参数构建一个包含Person信息的对象。可以使用不同参数多次调用函数,函数每次都会返回一个包含3个属性和1个方法的对象。这种工厂模式输入解决了创建多个类似对象的问题,但是没有解决对象标识问题(即新创建对象是什么类型)。

构造函数模式

创建一个构造函数,专门用来创建对象。构造函数就是一个普通函数,创建方式和普通函数没有区别,不同的是构造函数习惯上首字母大写。构造函数和普通函数的区别就是调用方式的不同,普通函数是直接调用,而构造函数使用new关键字调用。

使用同一个构造函数创建的对象,我们称为一个类,一个构造函数称为一个类,通过构造函数创建的对象,称为该类的实例,使用instanceof可以检查一个对象是否是一个类的实例。

function Person(name, age, gender) {
    this.name = name;
    this.age = age;
    this.gender = gender;
    this.sayName =function() {
        console.log(this.name);
    }
}
var p = Person();
console.log(p);
var p2 = new Person("孙悟空", 18, "男");
p2.sayName()
console.log(p2);

这个Person()构造函数代替了createPerson()工厂函数。实际上二者内部的代码基本上是一致的,只是构造函数有以下区别

  • 没有显式的创建对象
  • 属性和方法直接赋值给了this
  • 没有return

构造函数执行流程:

  1. 立刻创建一个新的对象
  2. 将新建的对象设置为函数中的this
  3. 执行函数中代码
  4. 将新建的对象作为返回值返回

在用构造函数进行实例化时,如果不想传参数,那么构造函数后面的括号可加可不加,只要有new操作符,就可以调用相应的构造函数:

function Person(name, age, gender) {
    this.name = name;
    this.age = age;
    this.gender = gender;
    this.sayName =function() {
        console.log(this.name);
    }
}
let p1=new Person();
let p2=new Person();

构造函数的问题:

构造函数的问题主要在于,其定义的方法会在每一个实例上都创建一遍。如上述例子的p1和p两个实例都有sayName()方法,但这两个方法不是同一个function实例,所以在p1和p2中会分别创建一次。

要解决这个问题,我们可以把sayName()函数定义在构造函数外面。但是这样是在全局作用域中定义的函数,会污染全局作用域。这会给代码的维护和书写带来不便。所以这个问题的最好的解决方法就是通过原型模式来解决。

原型模式

每个函数都会创建一个prototype属性,这个属性是一个对象,包含应该由特定引用类型的实例共享的属性和方法。实际上,这个对象就是通过调用构造函数创建的对象的原型。使用原型可以在它上面定义的属性和方法可以被对象实例共享,原来在构造函数中直接赋值给对象的值,可以直接赋值给它们的原型。这样就可以解决构造函数的问题。

function Person(name) {
    this.name = name;
}
Person.prototype.sayName = function () {
    console.log(this.name);
}

创建函数时,函数会获得一个prototype属性指向其原型对象。默认情况下,所有原型对象会获得一个constructor的属性指回与之关联的构造函数。

在自定义构造函数时,原型对象默认只会获得constructor属性,其他的所有方法都继承自Object。每次调用构造函数创建一个新实例时,这个实例内部的prototype就会指向这个构造函数的原型对象。

原型对象就相当于一个公共的区域,所有同一个类的实例都 可以访问到这个原型对象,我们可以将 对象中的共有内容统一设置到原型对象中。当我们访问对象的一个属性或者方法时,他会先在自身中寻找,如果有则直接使用,如果没有则会去原型对象中寻找。一般将方法放到原型对象中。

QQ图片20230715195920.jpg 原型对象也是对象,所以它也有原型。 当我们访问对象的一个属性或者方法时,他会先在自身中寻找,如果有则直接使用,如果没有则会去原型对象中寻找,如果没有则去原型对象的原型中寻找。直到找到Object对象的原型。 Object对象的原型没有原型,如果在Object中依然没有找到则返回undefined。

原型链

原型链是JavaScript的主要继承方式。通过原型链继承多个引用类型的属性和方法。

原型链:每个构造函数都有一个原型对象,原型对象有一个属性指回构造函数,而实例中有一个内部指针指向原型。如果原型是另一个类型的实例,那就意味着这个原型本身也有一个内部指针指向另一个原型,相应的另一个原型也有一个指针指向另一个构造函数。如此就在实例和原型之间构造了一条原型链。

function SuperType() {
    this.property = true;
}
SuperType.prototype.getSuperValue = function
    return this.property;
};
function SubType() {
    this.subporperty = false;
}
SubType.prototype = new SuperType();
SubType.prototype.getSubValue = function () 
    return this.subporperty;
}
let instance = new SubType();
console.log(instance.getSuperValue());//true
console.log(instance.getSubValue());//false

下图展示了实例与两个构造函数之间的原型关系

QQ图片20230715201110.jpg 这个例子中,SubType的原型是被替换成了一个SuperType的实例,这样一来SubType的实例不仅能从SuperType的实例中继承属性和方法,而且还与SuperType的原型连接上了。

在原型链中,对属性和方法的搜索会一直持续到原型链的末端。默认情况下,所有引用类型都继承自Object,这也是通过原型链实现的。所以上述例子的完整的原型链应如下图所示:

QQ图片20230715201811.jpg SubType继承SuperType,而SuperType继承Object。

原型与继承关系

原型与实例的关系可以通过两种方法来确定。一种是使用instanceof操作符,如果一个实例的原型链中出现过相应的构造函数,则返回结果为true。

console.log(instance instanceof Object);//true
console.log(instance instanceof SuperType);//true
console.log(instance instanceof SubType);//true

第二种是使用isPrototypeof()方法。只要原型链中包含这个原型,就会返回true。

console.log(Object.prototype.isPrototypeof(instance));//true
console.log(SuperTyoe.prototype.isPrototypeof(instance));//true
console.log(SubType.prototype.isPrototypeof(instance));//true

关于方法

子类有时候需要覆盖父类的方法,或者增加父类没有的方法。为此,这些方法必须在原型赋值之后再添加到原型上。在书写子类的方法时,如果父类中已存在则会覆盖父类的方法。