js原型:
1、原型诞生的目的是什么呢?
js原型的产生是为了解决在js对象实例之间共享属性和方法,并把他们很好聚集在一起(原型对象上)。每个函数都会创建一个prototype属性,这个属性指向的就是原型对象。
2、理解原型
无论何时,只要创建一个函数,就会为这个函数添加一个属性prototype,这个属性指向一个对象——原型对象。原型对象默认只有一个属性constructor(不可枚举属性),这个属性指回构造函数,另外其他的属性和方法都继承自Object.prototype。
构造函数通过new关键字调用创建对象实例。对象实例会有一个[[prototype]]属性指向函数的原型对象,js访问的话就是通过obj.__proto__指向构造函数原型对象。
注意:对象实例和构造函数之间没有直接的联系。但是通过对象实例obj.constructor是指向构造函数的,这是因为对象实例本身是没有constructor这个属性的,但是沿着原型链查找会找到原型上constructor是指向构造函数的。
关系图如上。
Person === Person.prototype.constructor //true
person.__proto__ === Person.prototype //true
可以看出,构造函数和函数原型是循环引用的关系。
3、几个关于原型的方法:
isPrototypeOf(): 对象实例上的方法,继承自Object.prototype
Person.prototype.isPrototypeOf(person) //true
Object.getPrototypeOf():可以方便的获取一个对象的原型。
Object.getPrototypeOf(person)===Person.prototype //true
Object.setPrototypeOf()和Object.create():设置对象的原型
1.
Object.setPrototypeOf(obj, prototype)
- 功能: 用于设置一个现有对象的原型。
- 参数:
obj
: 目标对象,其原型将被修改。prototype
: 要设置为目标对象原型的新对象(或null
)。- 返回值: 返回被修改的对象
obj
。- 用法:
const obj = {}; const proto = { greet: () => console.log('Hello!') }; Object.setPrototypeOf(obj, proto); obj.greet(); // 输出: Hello!
- 特点:
- 修改的是现有对象的原型。
- 如果目标对象的原型已经被设置为某个值,
Object.setPrototypeOf()
会覆盖它。- 可能会影响性能,因为设置原型会破坏某些 JavaScript 引擎的优化。
2.
Object.create(proto, [propertiesObject])
- 功能: 创建一个新对象,并将该对象的原型设置为指定的对象。
- 参数:
proto
: 新对象的原型对象(或null
)。propertiesObject
(可选): 一个对象,其属性描述符用于定义新对象的自身属性。- 返回值: 返回一个新对象,其原型是
proto
。- 用法:
const proto = { greet: () => console.log('Hello!') }; const obj = Object.create(proto); obj.greet(); // 输出: Hello!
- 特点:
- 创建的是新对象,不会修改现有对象。
- 更适合用于继承或创建具有特定原型的对象。
- 性能通常优于
Object.setPrototypeOf()
,因为它是专门为创建对象而设计的。
4、原型对象属性和对象实例属性的优先级
如果对象实例上定义了和原型对象上同名的属性,那么对象实例的属性会覆盖掉原型对象上的属性。
hasOwnProperty():对象实例上继承自Object.prototype上的方法。区分属性是在对象原型上还是对象实例上。
const obj = Object.create({ inheritedProp: 'value' });
obj.ownProp = 'myValue';
console.log(obj.hasOwnProperty('ownProp')); // true
console.log(obj.hasOwnProperty('inheritedProp')); // false
in操作符:当你需要检查属性是否存在于对象中(无论是自身属性还是继承属性)时,使用 in
。
const obj = Object.create({ inheritedProp: 'value' });
obj.ownProp = 'myValue';
console.log('ownProp' in obj); // true
console.log('inheritedProp' in obj); // true
5、关于自定义原型特别需要注意的问题
A)重写原型会切断原型对象和构造函数之间的联系,需要手动重新建立。给原型对象增加constructor属性(且不可枚举),指回构造函数。
function Person(name,age){
this.name=name;
this.age=age;
}
Person.prototype={
name:'myName',
age:99,
job:'myjob',
sayName(){
console.log(this.name)
}
}
Object.defineProperty(Person.prototype,'constructor',{
value:Person
})
B)重写原型之前,使用构造方法创建出来的对象实例的__proto__指向的还是原来的原型对象。重写原型之后,再次创建出来的对象__proto__指向的才是新的原型对象。
function Person(name,age){
this.name=name;
this.age=age;
}
let person1=new Person('zhangsan',18)
Person.prototype={
name:'myName',
age:99,
job:'myjob',
sayName(){
console.log(this.name)
}
}
Object.defineProperty(Person.prototype,'constructor',{
value:Person
})
let person2=new Person('lisi',19)
console.log(person1)
console.log(person2)
根据需要,可以将先前的创建对象实例的原型重新指定一下。
6、原型存在的问题
共享引用值的问题,如下:
function Person(name,age){
this.name=name;
this.age=age;
}
Person.prototype.friends=['a','b','c','d']
let person1=new Person('zhangsan',18)
let person2=new Person('lisi',19)
person1.friends.splice(1,1)
console.log(person1.friends)
console.log(person2.friends)
person1的friends少了一个,person2也跟着少了一个,像这种问题其实也不算问题。把friends属性变成对象实例自有的属性即可。
原型链:
1、理解原型链
理解了原型,原型链就很好理解了。其实呢,就是在你访问对象实例的属性时,查找属性的一条链路。先上图:
当你从对象实例person上访问某个属性时,先查找自有属性,然后到函数原型Person.prototype上查找,最后再到函数原型Object.prototype上查找,如果还是找不到,就会返回undefined或者报错。
2、理解Function和Object的关系:
内容较多,且难以理解。我将再写篇文章进行详细介绍,敬请关注。