原型与原型链总结

重点

1.每个对象的__proto__都是指向它的构造函数的原型对象prototype的

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

2.构造函数是一个函数对象,是通过Function构造器产生的

console.log(Person.__proto__ === Function.prototype);//true

3.原型对象本身是一个普通对象,而普通对象的构造函数都是Object

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

4.所有的构造器都是函数对象,函数对象都是Function构造产生的

console.log(Object.__proto__ === Function.prototype);//true

5.Object的原型对象也有__proto__属性指向null,null是原型链的顶端

console.log(Object.prototype.__proto__);//null

总结

  • 一切对象都是继承自Object对象,Object 对象直接继承根源对象null
  • 一切的函数对象(包括 Object 对象),都是继承自 Function 对象
  • Object 对象直接继承自 Function 对象
  • Fuction对象的__proto__会指向自己的原型对象,最终还是继承自Object对象
    console.log(Function.__proto__ === Function.prototype);//true
    

原型模型

每个函数都会创建一个prototype属性,这个属性是一个对象,是通过调用构造函数创建的对象的原型。
使用原型对象的好处:在原型对象上定义的属性和方法可以被对象实例共享。

理解

创建一个函数,就会为这个函数创建一个prototype属性(指向原型对象)。默认情况下,所有原型对象默认有一个名为constructor的属性,指向与之关联的构造函数。
在自定义构造函数时,原型对象默认只会获得constructor属性,其他的所有方法都继承自Object。
调用构造函数创建实例时,这个实例的内部 [[prototype]] 指针就会被赋值为构造函数的原型对象,每个对象上暴露__proto__属性,通过这个属性可以访问对象的原型。
实例与构造函数原型对象之间有直接的关系,但实例与构造函数之间没有

  • 实例对象的隐式原型指向其构造函数的(显示)原型对象

构造函数可以是函数声明,也可以是函数表达式。声明之后,构造函数就有了一个与之关联的原型对象。

构造函数有一个prototype属性,引用其原型对象。而原型对象也有一个constructor属性,引用这个构造函数。

function Person() {}
let person1 = new Person();
let person2 = new Person();
console.log(Person.prototype.constructor === Person);//true

原型链都会终止于Object的原型对象。

//原型对象是一个对象,对象的构造函数都是Object(),所以函数原型对象(prototype)的__proto__都为Object.prototypea
console.log(Person.prototype.__proto__ === Object.prototype);//true
console.log(Person.prototype.__proto__.constructor === Object);//true

Object原型的原型为null

console.log(Object.prototype.__proto__);//null
console.log(Person.prototype.__proto__.__proto__ === null);//true
  • 实例通过__proto__链接到原型对象,它实际指向隐藏特性[[prototype]]
  • 构造函数通过prototype属性链接到原型对象
  • 实例与构造函数没有直接联系,与原型对象有直接联系
console.log(person1.__proto__ === Person.prototype);//true
console.log(person1.__proto__.constructor === Person);//true

//TODO
对象属性查询机制。

  • Object.prototype.isPrototypeof() //确定两个对象之间的关系 测试一个对象是否存在于另一个对象的原型链上
  • Object.getPrototypeOf() //返回参数的内部特性[[Prototype]]的值 返回指定对象的原型
  • Object.setPrototypeOf() //向实例的内部特性[[Prototype]]写入新值 设置一个指定的对象的原型到另一个对象或null

注意:为避免使用Object.setPrototypeOf()可能造成的性能下降(constructor相关),可以通过Object.create(proto, [propertiesObject])来创建一个新对象,同时为其指定原型。

原型层级

  • 在通过对象访问属性时,会按照这个属性的名称开始搜索。搜索开始于对象实例本身。如果在这个实例上发现了给定的名称,则返回该名称对应的值。如果没有找到这个属性,则搜索会沿着指针进入原型对象,然后再原型对象上找到属性后,再返回对应的值。
    • 注意:constructor属性只存在于原型对象,因此通过实例对象也可以访问到。
  • 如果在实例上添加了一个与原型对象同名的属性,这个属性会遮蔽原型对象上的同名属性。即使在实例上把这个属性设置为null,也不会恢复它和原型的联系。只有通过delete操作符删除实例上这个属性才可以。

Object.hasOwnProperty()用于确定某个属性是在实例上(返回true)还是在原型对象上。

注意:Object.getOwnPropertyDescriptor()只对实例属性有效。如果要取得原型属性的描述符,就必须直接在原型对象上调用。

原型和 in 操作符

有两种方式使用in操作符

  • 单独使用
  • 在for-in循环中使用

单独使用

可以通过对象访问指定属性时返回true,无论该属性在实例上还是在原型上。
如果要确定某个属性是否存在于原型上,可以同时使用hasOwnProperty和in操作符:

function hasPrototypeProperty(object, name) {
        return !object.hasOwnProperty && (name in object)
}

for-in 可枚举 实例属性和原型属性

在for-in循环中使用
可以通过对象访问且可以被枚举的属性都会返回,包括实例属性和原型属性
(遮蔽原型中不可枚举属性的实例属性也会在for-in循环中返回,因为默认情况下开发者定义的属性都是可枚举的。)

for…in语句以任意顺序迭代一个对象的除Symbol以外的可枚举属性,包括继承的可枚举属性.

function Person() {} 
Person.prototype.name = "Nicholas"; 
Person.prototype.age = 29; 
Person.prototype.job = "Software Engineer"; 
Person.prototype.sayName = function() { 
 console.log(this.name); 
}; 
let p1 = new Person(); 
p1.name = "Rob"; 
p1.age = 31; 
for(let i in p1){
    console.log(i);
}
// name age job sayName

Object.keys() 可枚举 实例属性

要获得对象上所有可枚举的实例属性,可以使用Object.keys()方法。这个方法接收一个对象作为参数,返回包含该对象所有可枚举属性名称的字符串数组。

function Person() {} 
Person.prototype.name = "Nicholas"; 
Person.prototype.age = 29; 
Person.prototype.job = "Software Engineer"; 
Person.prototype.sayName = function() { 
 console.log(this.name); 
}; 

let p1 = new Person(); 
p1.name = "Rob"; 
p1.age = 31; 
let p1keys = Object.keys(p1); 
console.log(p1keys); // "[name,age]"

Object.getOwnPropertyNames() 是否可枚举 实例属性 不包括Symbol值作为名称的属性

列出所有实例属性,无论是否可以枚举(包括不可枚举属性但不包括 Symbol 值作为名称的属性),都可以使用
Object.getOwnPropertyNames() //返回一个由指定对象的所有自身属性的属性名(包括不可枚举属性但不包括 Symbol 值作为名称的属性)组成的数组。

let keys1 = Object.getOwnPropertyNames(p1); 
console.log(keys1); // "[name, age]"
console.log(Object.getOwnPropertySymbols(p1));
// []
let keys2 = Object.getOwnPropertyNames(Person.prototype); 
console.log(keys2);// "[constructor,name,age,job,sayName]"

注意:返回的结果中constructor是不可枚举属性

Object.getOwnPropertyNames() 所有Symbol属性

Object.getOwnPropertySymbols() //返回一个给定对象自身的所有Symbol属性的数组

let k1 = Symbol('k1'),  k2 = Symbol('k2');
let o = { 
    [k1]: 'k1', 
    [k2]: 'k2' 
}; 
console.log(Object.getOwnPropertySymbols(o)); 
// [Symbol(k1), Symbol(k2)]
console.log(Object.getOwnPropertyNames(o)); 
// []
let k1 = Symbol('k1'),  k2 = Symbol('k2');
let o = { 
    [k1]: 'k1', 
    [k2]: 'k2',
    a: '1',
}; 
console.log(Object.getOwnPropertySymbols(o)); 
// [Symbol(k1), Symbol(k2)]
console.log(Object.getOwnPropertyNames(o)); 
// [a]

属性枚举顺序

枚举顺序不确定:

  • for-in循环
  • Object.keys()

枚举顺序确定:

  • Object.getOwnPropertyNames
  • Object.getOwnPropertySymbols
  • Object.assign()

对象迭代

  • Object.values() 返回对象值的数组
  • Object.entries() 返回键/值对的数组
    注意
    • 非字符串属性会被转换为字符串输出,另外,这两个方法执行对象的浅复制。
    • 符号属性会被忽略

补充注意点

  1. 直接通过一个包含所有属性和方法的对象字面量来重写原型,会导致Person.prototype的constructor属性就不再指向原来的构造函数Person,而是指向Object的构造函数。
function Person() {} 
Person.prototype = {
    name: "Nicholas", 
    age: 29, 
    job: "Software Engineer", 
    sayName() { 
        console.log(this.name); 
    } 
};
console.log(Person.prototype.constructor === Person);//false
console.log(Person.prototype.constructor === Object.prototype.constructor);//true
let friend = new Person(); 
console.log(friend.constructor == Person); // false 
console.log(friend.constructor == Object); // true
  1. 如果在重写对象原型对象时,专门设置了constructor属性,(设置成Person),保证这个属性仍然包含恰当的值。
function Person() { 
} 
Person.prototype = { 
    constructor: Person, 
    name: "Nicholas", 
    age: 29, 
    job: "Software Engineer", 
    sayName() { 
        console.log(this.name); 
    }
};
console.log(Person.prototype.constructor === Person);//true
let friend = new Person(); 
console.log(friend.constructor == Person); //true
//返回可枚举属性 实例属性和原型属性
for(let i in friend){
    console.log(i);
}
// constructor name age job sayName
  1. 注意:以这种方式恢复的consrtuctor属性会创建一个[ [ Enumerable ] ]为true的属性,而原生的constructor是不可枚举的。可以改为使用Object.defineProperty(obj, prop, descriptor)来定义constructor属性。
function Person() {} 
Person.prototype = { 
    name: "Nicholas", 
    age: 29, 
    job: "Software Engineer", 
    sayName() { 
        console.log(this.name); 
    } 
}; 
// 恢复 constructor 属性
Object.defineProperty(Person.prototype, "constructor", { 
    enumerable: false, 
    value: Person,
});
let friend = new Person(); 
//返回可枚举属性 实例属性和原型属性
for(let i in friend){
    console.log(i);
}
// name age job sayName

  1. 先创建一个Person实例friend,然后在Person.prototype上添加了名为sayHi()的方法。friend仍然可以访问这个方法。
    主要原因:实例和原型之间的链接就是简单的指针,而不是保存的副本。
let friend = new Person(); 
Person.prototype.sayHi = function() { 
    console.log("hi"); 
}; 
friend.sayHi(); // "hi",没问题!
  1. 虽然随时能给原型添加属性和方法,并会反映到对象实例上,但这跟重写整个原型是两回事
    实例的[ [ prototype ] ]指针是在调用构造函数时自动赋值的,这个指针即使把原型修改为不同的对象也不会变。重写整个原型会切断最初原型与构造函数的联系但实例引用的仍然是最初的原型
    实例只有指向原型的指针,没有指向构造函数的指针
    在这里插入图片描述

  1. 原型模式很重要,不仅体现在自定义类型上,而且还因为它也是实现所有原生引用类型的模式。所有原生引用类型的构造函数都在原型上定义了实例方法。比如:数组实例的sort()方法就是Array.prototype上定义的。
  2. 原型的问题:
    弱化了构造函数传递初始值参数的能力,所有实例默认取得相同的属性值。最主要的问题:源自共享特性,包含引用值的属性。一般来说,不同的实例应该有属于自己的属性副本。
function Person() {} 
Person.prototype = { 
    constructor: Person, 
    name: "Nicholas", 
    age: 29, 
    job: "Software Engineer", 
    friends: ["Shelby", "Court"],
    sayName() { 
        console.log(this.name); 
    } 
}; 
let person1 = new Person(); 
let person2 = new Person(); 
person1.friends.push("Van"); 
console.log(person1.friends); // "Shelby,Court,Van" 
console.log(person2.friends); // "Shelby,Court,Van" 
console.log(person1.friends === person2.friends); // true

测试题:

var A = function() {}
A.prototype.n = 1
var b = new A()
A.prototype = {
    n: 2,
    m: 3
}
var c = new A()
console.log(b.n, b.m, c.n, c.m)//1 undefined 2 3
  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值