每个函数都会创建一个 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']
上述情况中,如果有意在多个实例之间共享数组的话没什么问题, 但是不是的话就会产生数据的错误。 一般来说, 不同的实例应该有属于自己的属性副本。 这也是通常实际开发中不单独使用原型模式的原因。
本文详细介绍了JavaScript中的原型模式,包括理解原型、Object的方法、原型层级、原型与in操作符的关系,以及原型模式的问题。每个函数都有prototype属性,用于共享属性和方法。通过原型对象,实例可以共享属性和方法,但这也可能导致不同实例间的数据错误。同时,文章提到了`Object.create()`和`in`操作符在处理原型关系时的作用。
533

被折叠的 条评论
为什么被折叠?



