javascript -- 创建对象 原型模式

本文详细介绍了JavaScript中的原型模式,包括理解原型、Object的方法、原型层级、原型与in操作符的关系,以及原型模式的问题。每个函数都有prototype属性,用于共享属性和方法。通过原型对象,实例可以共享属性和方法,但这也可能导致不同实例间的数据错误。同时,文章提到了`Object.create()`和`in`操作符在处理原型关系时的作用。

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

function Person() {}
Person.prototype.name = "Jin";
Person.prototype.age = 18;
Person.prototype.jon = "Software Engineer";
Person.prototype.sayNmae = function () {
	consoel.log(this.name)
}

let person1 = new Person(); 
let person2 = new Person();
person1.sayName(); //Jin
person2.sayName(); //Jin
console.log(person1.sayName == person2.sayName ); // true

  在上述例子送, 所有的属性以及 sayName() 方法都直接添加到了 Person 的 prototype 属性上,而构造函数体中什么也没有。这样子定义以后, 调用构造函数创建的新对象仍然拥有相应的属性和方法。与构造函数模式不同, 使用这种原型模式定义的属性和方法是由所有实例共享的。 因此 person1 和 person2 访问的都是相同的属性和相同的 sayName() 函数。

理解一下ECMAScript 中的原型

理解原型

   不论何时,只要创建一个函数, 就会按照特定的规则为这个函数创建一个 prototype 属性 (指向原型对象)。 默认情况下, 所有原型对象会自动获得一个名为 constructor 的属性, 指回与之关联的构造函数。例如, Person.prototype.constructor 就指向 Person。
  在我们自定义一个构造函数的时候, 原型对象默认只会获得 constructor 属性, 其他的所有方法都继承自 Object。 每次调用构造函数创建一个新的实例的时候, 这个实例内部的 [[Prototype]] 指针就被赋值为构造函数的原型对象。 脚本中没有访问这个 [[ Prototype ]] 特性的标准方式, 但是在 Firefox, Safari 和 Chrome 中会在每个对象上暴露__ proto__ 属性, 通过这个属性可以访问对象的原型。 主要理解的一点是, 实例与构造函数的原型之间有着直接的联系, 但实例和构造函数之间没有。
   举个🌰 :

1、创建一个构造函数

function Person() {} // 声明一个构造函数
let Person = function() {}  // 或者使用函数声明创建 

2、这个构造函数就有了一个与之管理的原型对象 Person.prototype

console.log(Person.prototype) 
// {
//	constructor: ƒ Person()
//	[[Prototype]]: Object
// }

3、这个原型对象有个constructor属性指回构造函数, Object原型的原型是null

console.log(Person.prototype.constructor == Person); //true

4、正常的原型链都会终止于 Object 的原型对象

console.log(Person.prototype.__proto__ === Object.prototype); //true
console.log(Person.prototype.__proto__.constructor === Object); // true
console.log(Person.prototype.__proto__.__proto__ === null); // true

5、 构造函数、原型对象、实例 是3个完全不同的对象

console.log(person1 !== Person); // true
console.log(person1 !== Person.prototype); // true
console.log(Person.prototype !== Person); // true

6、实例通过 __prototype__ 链接到原型对象, 实际上指向隐藏的特性[[Prototype]]
   构造函数通过 prototype 属性连接到原型对象
   实例与构造函数没有直接联系, 与原型对象有着直接联系
   
console.log(person1.__proto__ === Person.prototype); // true
conosle.log(person1.__proto__.constructor === Person); // true

   下面这张图可以很好的解释 构造函数、 实例、 原型对象三者之间的关系。
一张别人的图

Object的两个方法

1、Object.getPrototypeOf()取得一个对象的原型

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

2、Object.setPrototypeOf()向实例的私有特性 [[Prototype]] 写入一
个新值。

let biped = {
	numLegs: 2
};
let person = {
	name: 'Matt'
};
Object.setPrototypeOf(person, biped);
console.log(person.name); // Matt
console.log(person.numLegs); // 2
console.log(Object.getPrototypeOf(person) === biped); // true

  为避免使用 Object.setPrototypeOf() 可能造成的性能下降,可以通过 Object.create() 来创建一个新对象,同时为其指定原型:

let biped = {
	numLegs: 2
};
let person = Object.create(biped);
person.name = 'Matt';
console.log(person.name); // Matt
console.log(person.numLegs); // 2
console.log(Object.getPrototypeOf(person) === biped); // true
原型的层级

  读取实例的属性的时候,如果找不到该属性,就会去寻找该实例的原型上的属性,如果找不到,继续寻找原型的原型直到最顶层。

function Person() {}
Person.prototype.name = "Nicholas";
Person.prototype.age = 29;
Person.prototype.job = "Software Engineer";
Person.prototype.sayName = function() {
console.log(this.name);
};
let person1 = new Person();
let person2 = new Person();
person1.name = "Greg";
console.log(person1.name); // "Greg" ,来自实例
console.log(person2.name); // "Nicholas" 

  上述🌰中, person1 的 name 属性遮蔽了原型对象上的同名属性,所以返回了 Greg, 而 访问 person2 的 name属性的时候, 首先在这个实例上搜索这个属性,没有搜索到就去找个这个实例的原型对象上有没有, 这里有就返回了 Nicholas 值, 如果都没有则继续去访问原型的原型, 一直到最顶层 Object的原型, 返回null

原型和 in 操作符

  只要通过对象可以访问, in 操作符就返回 true ,而 hasOwnProperty() 只有属性存在于实例上时才返回 true 。因此,只要 in 操作符返回 true 且 hasOwnProperty() 返回 false ,就说明该属性是一个原型属性。
🌰

function Person() {}
Person.prototype.name = "Nicholas";
Person.prototype.age = 29;
Person.prototype.job = "Software Engineer";
Person.prototype.sayName = function() {
console.log(this.name);
};
let person1 = new Person();
let person2 = new Person();
console.log(person1.hasOwnProperty("name")); // false
console.log("name" in person1); // true
person1.name = "Greg";
console.log(person1.name); // "Greg",来自实例
console.log(person1.hasOwnProperty("name")); // true
console.log("name" in person1); // true
console.log(person2.name); // "Nicholas",来自原型
console.log(person2.hasOwnProperty("name")); // false
console.log("name" in person2); // true
delete person1.name;
console.log(person1.name); // "Nicholas",来自原型
console.log(person1.hasOwnProperty("name")); // false
console.log("name" in person1); // true

对象迭代

   在JavaScript有史以来大部分时间里, 迭代对象对象属性都是一个难题。 ECMAScript 2017 新增两个静态方法, 用于将对象内容转换为序列化的——更重要的是可迭代的——格式。它们分别是 Object.values()Object.entries(), 接受一个对象,返回它们内容的数组。
  前者返回对象值的数组, 后者返回键/值对的数组。

const obj = {
    name: 'JIN',
    age: 18,
    job: 'Software Enginner',
    sayName: function () {
        console.log(this.name);
    }
}

console.log(Object.values(obj));
// [ 'JIN', 18, 'Software Enginner', [Function: sayName] ]

console.log(Object.entries(obj));
// 
[
  [ 'name', 'JIN' ],
  [ 'age', 18 ],
  [ 'job', 'Software Enginner' ],
  [ 'sayName', [Function: sayName] ]
]

  两者都是执行对象的浅复制

原型语法
function Person() {}
	Person.prototype = {
	name: "Nicholas",
	age: 29,
	job: "Software Engineer",
	sayName() {
		console.log(this.name);	
	}
};

  在这个例子中, Person.prototype 被设置为等于一个通过对象字面量创建的新对象。最终结果是一样的,只有一个问题:这样重写之后, Person.prototype 的 constructor 属性就不指向 Person了。

我们可以这么写:

function Person() {}
Person.prototype = {
	constructor: Person,
	name: "Nicholas",
	age: 29,
	job: "Software Engineer",
	sayName() {
		console.log(this.name);
	}
};
原型模式的问题

1、弱化了向构造函数传递初始化参数的能力, 会导致所有实例默认都取得相同的属性值。
2、原型上所有的属性在实例之间是共享的。这可能会造成一些问题。
举个🌰

function Person() {}
Person.prototype = {
	constructor: Person,
	friends:['JIN', 'TONG']
}
let person1 = new Person;
let person2 = new Person;
person1.friends.push('Jenny');

console.log(person1.friends) //['JIN', 'TONG', 'Jenny']
console.log(person2.friends) //['JIN', 'TONG', 'Jenny']

上述情况中,如果有意在多个实例之间共享数组的话没什么问题, 但是不是的话就会产生数据的错误。 一般来说, 不同的实例应该有属于自己的属性副本。 这也是通常实际开发中不单独使用原型模式的原因。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值