认识对象的原型

  • **JavaScript当中每个对象都有一个特殊的内置属性 [[prototype]],这个特殊的对象可以指向另外一个对象。 **
  • 那么这个对象有什么用呢?
    • 当我们通过引用对象的属性key来获取一个value时,它会触发 [[Get]]的操作;
    • 这个操作会首先检查该对象是否有对应的属性,如果有的话就使用它;
    • 如果对象中没有改属性,那么会访问对象[[prototype]]内置属性指向的对象上的属性;
  • 那么如果通过字面量直接创建一个对象,这个对象也会有这样的属性吗?如果有,应该如何获取这个属性呢?
    • 答案是有的,只要是对象都会有这样的一个内置属性;
  • 获取的方式有两种:
    • 方式一:通过对象的 proto 属性可以获取到(但是这个是早期浏览器自己添加的,存在一定的兼容性问题);
    • 方式二:通过 Object.getPrototypeOf 方法可以获取到;

普通对象的原型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
var obj = {
name: "why",
age: 18
}
console.log(obj)

var info = {}

// 获取对象的原型
console.log(obj.name, obj.age)
console.log(obj.__proto__)
console.log(Object.getPrototypeOf(obj))
console.log(obj.__proto__ === Object.getPrototypeOf(obj)) // true

// 疑问: 这个原型有什么用呢?
// 当我们通过[[get]]方式获取一个属性对应的value时
// 1> 它会优先在自己的对象中查找, 如果找到直接返回
// 2> 如果没有找到, 那么会在原型对象中查找
console.log(obj.name)

obj.__proto__.message = "Hello World"
console.log(obj.message)

image.png


函数的原型 prototype

函数对象的原型

1
2
3
4
5
6
7
8
9
10
11
12
13
var obj = {}
function foo () { }

// 1.将函数看成是一个普通的对象时, 它是具备__proto__(隐式原型)
// 作用: 查找key对应的value时, 会找到原型身上
console.log(obj.__proto__)
console.log(foo.__proto__)


// 2.将函数看成是一个函数时, 它是具备prototype(显式原型)
// 作用: 用来构建对象时, 给对象设置隐式原型的
console.log(foo.prototype)
// console.log(obj.prototype) 对象是没有prototype

new操作符

  • 我们前面讲过new关键字的步骤如下:
    • 1.在内存中创建一个新的对象(空对象);
    • 2.这个对象内部的[[prototype]]属性会被赋值为该构造函数的prototype属性;
  • 那么也就意味着我们通过Person构造函数创建出来的所有对象的[[prototype]]属性都指向Person.prototype:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function Foo() {
// 1.创建空的对象
// 2.将Foo的prototype原型(显式隐式)赋值给空的对象的__proto__(隐式原型)
}

console.log(Foo.prototype)

var f1 = new Foo()
var f2 = new Foo()
var f3 = new Foo()
var f4 = new Foo()
var f5 = new Foo()
console.log(f1.__proto__)
console.log(f1.__proto__ === Foo.prototype) // true
console.log(f3.__proto__ === f5.__proto__) // true

创建对象的内存表现

image.png

prototype添加属性

image.png

constructor属性

  • 事实上原型对象上面是有一个属性的:constructor
  • 默认情况下原型上都会添加一个属性叫做constructor,这个constructor指向当前的函数对象
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 非常重要的属性: constructor, 指向Person函数对象
function Person() {

}

// 1.对constructor在prototype上的验证
var PersonPrototype = Person.prototype
console.log(PersonPrototype)
console.log(PersonPrototype.constructor)
console.log(PersonPrototype.constructor === Person)

console.log(Person.name)
console.log(PersonPrototype.constructor.name)

// 2.实例对象p
var p = new Person()
console.log(p.__proto__.constructor)
console.log(p.__proto__.constructor.name)

image.png

重写原型对象

  • 如果我们需要在原型上添加过多的属性,通常我们会重写整个原型对象:image.png
  • 前面我们说过, 每创建一个函数, 就会同时创建它的prototype对象, 这个对象也会自动获取constructor属性;
    • 而我们这里相当于给prototype重新赋值了一个对象, 那么这个新对象的constructor属性, 会指向Object构造函数, 而不是 Person构造函数了

原型对象的constructor

  • 如果希望constructor指向Person,那么可以手动添加:
  • 上面的方式虽然可以, 但是也会造成constructor的[[Enumerable]]特性被设置了true.
    • 默认情况下, 原生的constructor属性是不可枚举的.
    • 如果希望解决这个问题, 就可以使用我们前面介绍的Object.defineProperty()函数了. image.png

JavaScript原型链

  • 在真正实现继承之前,我们先来理解一个非常重要的概念:原型链。
    • 我们知道,从一个对象上获取属性,如果在当前对象中没有获取到就会去它的原型上面获取: image.png

Object的原型

  • 那么什么地方是原型链的尽头呢?比如第三个对象是否也是有原型__proto__属性呢?image.png
  • 我们会发现它打印的是 [Object: null prototype] {}
    • 事实上这个原型就是我们最顶层的原型了
    • 从Object直接创建出来的对象的原型都是 [Object: null prototype] {}。
  • 那么我们可能会问题: [Object: null prototype] {} 原型有什么特殊吗?
    • 特殊一:该对象有原型属性,但是它的原型属性已经指向的是null,也就是已经是顶层原型了;
    • 特殊二:该对象上有很多默认的属性和方法;

对象的原型链:

1
2
3
4
5
// 1.{}的本质
var info = {}
// 相当于
var info = new Object()
console.log(info.__proto__ === Object.prototype)