什么是对象的原型?

对象的原型

JavaScript当中每个对象都有一个特殊的内置属性 [[prototype]],这个特殊的对象可以指向另外一个对象。

那么这个对象有什么用呢?

  1. 当我们通过引用对象的属性key来获取一个value时,它会触发 [[Get]]的操作;
  2. 这个操作会首先检查该属性是否有对应的属性,如果有的话就使用它
  3. 如果对象中没有改属性,那么会访问对象[[prototype]]内置属性指向的对象上的属性;

代码示例:

// 2.原型有什么用呢?
// 当我们从一个对象中获取某一个属性时, 它会触发` [[get]] `操作
// 1. 在当前对象中去查找对应的属性, 如果找到就直接使用
// 2. 如果没有找到, 那么会沿着它的原型去查找 `[[prototype]]`
//obj.age = 18
var obj = { name: "zayyo" } 
obj.__proto__.age = 18
console.log(obj.age)
复制代码

那么如果通过字面量直接创建一个对象,这个对象也会有这样的属性吗?如果有,应该如何获取这个属性呢?

答案是有的,只要是对象都会有这样的一个内置属性;

获取的方式有两种

  1. 通过对象的 proto 属性可以获取到(但是这个是早期浏览器自己添加的,存在一定的兼容性问题)
  2. 通过 Object.getPrototypeOf 方法可以获取到

代码示例:

// 我们每个对象中都有一个 [[prototype]], 这个属性可以称之为对象的原型(隐式原型)

var obj = { name: "zayyo" } // [[prototype]]
var info = {} // [[prototype]]

// 1.解释原型的概念和查看一下原型
// 早期的ECMA是没有规范如何去查看 [[prototype]]

// 给对象中提供了一个属性, 可以让我们查看一下这个原型对象(浏览器提供)
// __proto__
console.log(obj.__proto__) // {}
console.log(info.__proto__) // {}

// 原理
var obj = {name: "zayyo", __proto__: {} }

// // ES5之后提供的Object.getPrototypeOf
console.log(Object.getPrototypeOf(obj))

复制代码

既然字面量创建的对象会有原型,那么创建的函数也会有原型吗?

答案是肯定的,接下来我们就详细讲解一下函数的原型。

函数的原型 prototype

所有的函数都有一个prototype的属性

你可能会问题,是不是因为函数是一个对象,所以它有prototype的属性呢?

不是的,因为它是一个函数,才有了这个特殊的属性。而不是它是一个对象,所以有这个特殊的属性。

代码示例:

function foo() {
}
// 函数也有原型(隐式原型)
console.log(foo.__proto__) // 函数作为对象来说, 它也是有[[prototype]] 隐式原型
// 函数它因为是一个函数, 所以它还会多出来一个显示原型属性: prototype
console.log(foo.prototype)
复制代码

在对函数使用new操作符时,new会执行的步骤是和上面一样的,但是我们要==注意==到的是,new在执行 在内存中创建一个新的对象(空对象)并且这个对象内部的[[prototype]]属性会被赋值为该构造函数的prototype属性,这两步会对我们的函数产生巨大的影响,

  • 首先这意味着我们会有一个新的对象,
  • 第二我们通过Person构造函数创建出来的所有对象的[[prototype]]属性都指向Person.prototype

代码示例:

function Person() {

}

var p1 = new Person()
var p2 = new Person()

// 都是为true
// new出来的对象的隐性原型(__proto__)会指向构造出这个对象的类的显示原型(prototype)
console.log(p1.__proto__ === Person.prototype)
console.log(p2.__proto__ === Person.prototype)


// p1.name = "zayyo"
//给构造原型添加一个name属性赋值为kobe
// p1.__proto__.name = "kobe"
// 修改了构造原型的name属性的值,变为james
// Person.prototype.name = "james"
// 修改了构造原型的name属性的值,变为zayyo
p2.__proto__.name = "zayyo"

console.log(p1.name)

复制代码

constructor属性

事实上原型对象上面是有一个属性的:constructor,默认情况下原型上都会添加一个属性叫做constructor,这个constructor指向当前的函数对象

function foo () {

}

// 1.constructor属性
// foo.prototype这个对象中有一个constructor属性
//查看prototyp属性
console.log(foo.prototype)
//查看constructor属性
console.log(Object.getOwnPropertyDescriptors(foo.prototype))
// 定义constructor
Object.defineProperty(foo.prototype, "constructor", {
  enumerable: true,
  configurable: true,
  writable: true,
  value: "哈哈哈哈"
})

// console.log(foo.prototype)

// prototype.constructor = 构造函数本身
console.log(foo.prototype.constructor) // [Function: foo]
console.log(foo.prototype.constructor.name)

console.log(foo.prototype.constructor.prototype.constructor.prototype.constructor)

// 2.我们也可以添加自己的属性
foo.prototype.name = "zayyo"
foo.prototype.age = 18
foo.prototype.height = 18
foo.prototype.eating = function () {

}

var f1 = new foo()
console.log(f1.name, f1.age)


复制代码

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


// 3.直接修改整个prototype对象
foo.prototype = {
  // constructor: foo,
  name: "zayyo",
  age: 18,
  height: 1.88
}

var f1 = new foo()

console.log(f1.name, f1.age, f1.height)

// 真实开发中我们可以通过Object.defineProperty方式添加constructor
Object.defineProperty(foo.prototype, "constructor", {
  enumerable: false,
  configurable: true,
  writable: true,
  value: foo
})

复制代码

如果希望constructor指向Person,那么可以手动添加,上面的方式虽然可以, 但是也会造成constructor的[[Enumerable]]特性被设置了true。默认情况下, 原生的constructor属性是不可枚举的,如果希望解决这个问题, 就可以使用我们前面介绍的Object.defineProperty()函数了。我们在上一个构造函数的方式创建对象时,有一个弊端:会创建出重复的函数,比如running、eating这些函数。

那么有没有办法让所有的对象去共享这些函数呢?

有,将这些函数放到Person.prototype的对象上即可

代码示例:

// 变量属性要添加再构造类的函数里面,不可以添加剂再顶层原型里面,不然会造成数据被重复修改
function Person(name, age, height, address) {
  this.name = name
  this.age = age
  this.height = height
  this.address = address
}

// 而函数属性可以添加再顶层函数里面,不会造成重复修改,因为函数不会保存值,每次都会释放内存
Person.prototype.eating = function() {
  console.log(this.name + "在吃东西~")
}

Person.prototype.running = function() {
  console.log(this.name + "在跑步~")
}

var p1 = new Person("zayyo", 18, 1.88, "北京市")
var p2 = new Person("kobe", 20, 1.98, "洛杉矶市")

p1.eating()
p2.eating()
复制代码

猜你喜欢

转载自juejin.im/post/7229204793617530936