一、对象和函数的原型

认识对象的原型

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

这个对象的作用:

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

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

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

获取的方式有两种:

  • 方式一:通过对象的__proto__属性可以获取到(但是这个是早期浏览器自己添加的,存在一定的兼容性问题)
  • 方式二:通过Object.getPrototypeOf()可以获取到
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
var obj = {
  name: "mohca",
    age: 18
}

// 获取对象的原型

// 非标准方式
console.log(obj.__proto__)

// 标准方式
console.log(Object.getPrototypeOf(obj))

认识函数的原型

将函数看成普通对象时,它具备__proto__(隐式原型)

1
2
3
4
5
6
7
var obj = {}

function foo() {}

console.log(obj.__proto__)
console.log(foo.__proto__)

将函数看成是函数时,它具备 prototype (显式原型)

1
2
3
4
5
6
var obj = {}

function foo() {}

console.log(obj.prototype)  // 对象上是没有的
console.log(foo.prototype)

new操作过程:

  1. 创建空对象 var obj = {}
  2. 将这个空对象复制给this this = obj
  3. 将函数的显示原型赋值给这个对象作为它的隐式原型 obj.__proto__ = Person.prototype
  4. 执行函数体中的代码
  5. 将这个对象默认返回

重写原型对象

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
function Person() { }

console.log(Person.prototype)

// 在原有的原型对象上添加新的属性
Person.prototype.message = "Hello Person"
Person.prototype.info = { name: "hahaha" }

// 直接赋值一个新的原型对象
Person.prototype = {
  message: "Hello Person",
  info: { name: "hahaha" },
  // constructor: Person
}

Object.defineProperty(Person.prototype, "constructor", {
    configurable: true,
    writable: true,
    enumerable: false,
    value: Person
})

console.log(111, Object.keys(Person.prototype))

// 新建实例对象
var p = new Person()
console.log(p.message)

二、原型链的查找顺序

  1. obj上查找
  2. obj.__proto__ 上查找
  3. obj.__proto__.proto__上查找,查找不到返回undefined
1
2
3
4
var obj = {
  name: "mocha",
  age: 18
}

三、原型链实现方法的继承

面向对象有三大特性:封装、继承、多态

  • 封装:将属性和方法封装到有个类中,称之为封装的过程
  • 继承:继承是面向对象中非常重要的,不仅仅可以减少重复代码的数量,也是多态的前提
  • 多态:不同的对象在执行时表现出不同的形态

方式一

父类的原型直接赋值给子类的原型

缺点:父类和子类共享一个原型对象,修改了任意一个,另外一个也会被修改

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
// 定义Person构造函数(类)
function Person(name, age) {
  this.name = name
  this.age = age
}

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

Person.prototype.eating = function () {
  console.log(this.name + " eating~")
}

// 定义学生类
function Student(name, age, sno, score) {
  this.name = name
  this.age = age
  this.sno = sno
  this.score = score
}

Student.prototype = Person.prototype

Student.prototype.studying = function () {
  console.log(this.name + " studying~")
}

var stu1 = new Student("mocha", 18, 111, 100)

stu1.running()  // mocha running~
stu1.studying()  // mocha studying~
console.log(stu1)  // Student {name: 'mocha', age: 18, sno: 111, score: 100}

image-20230630174113599

方式二

创建一个父类的实例对象,用这个实例对象作为子类的原型对象

缺点:原型上有重复多余的变量

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
// 定义Person构造函数(类)
function Person(name, age) {
  this.name = name
  this.age = age
}

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

Person.prototype.eating = function () {
  console.log(this.name + " eating~")
}

// 定义学生类
function Student(name, age, sno, score) {
  this.name = name
  this.age = age
  this.sno = sno
  this.score = score
}

var p = new Person("latte", 18)
Student.prototype = p

Student.prototype.studying = function () {
  console.log(this.name + " studying~")
}

var stu1 = new Student("mocha", 20, 111, 100)

stu1.running()  // mocha running~
stu1.studying()  // mocha studying~
console.log(stu1)  // Student {name: 'mocha', age: 20, sno: 111, score: 100}

image-20230630174038235

四、借用构造函数实现属性的继承

为了解决原型链继承中存在的问题,开发人员提供了一种新的技术:constructor stealing(借用构造函数或者称之为经典继承或者称之为伪造对象)

做法:在在子类型构造函数的内部调用父类型构造函数

  • 因为函数可以在任意的时刻被调用
  • 因此通过apply()和call()方法也可以在新创建的对象上执行构造函数
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
// 定义Person构造函数(类)
function Person(name, age) {
  this.name = name
  this.age = age
}

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

Person.prototype.eating = function () {
  console.log(this.name + " eating~")
}

// 定义学生类
function Student(name, age, sno, score) {
  Person.call(this, name, age)
  this.sno = sno
  this.score = score
}

var p = new Person()
Student.prototype = p


Student.prototype.studying = function () {
  console.log(this.name + " studying~")
}

var stu1 = new Student("mocha", 20, 111, 100)

stu1.running()  // mocha running~
stu1.studying()  // mocha studying~
console.log(stu1)  // Student {name: 'mocha', age: 20, sno: 111, score: 100}

image-20230630174743307

弊端:

  • 会调用两次父类构造函数
    • 一次在创建子类原型的时候
    • 另一次再子类构造函数内部(也就是每次创建子类实例的时候)
  • 所有的子类实例事实上会拥有两份父类的属性
    • 一份在当前的实例自己里面(person本身),另一份在子类对应的原型对象中(person.__proto__
    • 两份属性不会出现访问问题,因为默认一定是访问实例本身的那部分属性

五、寄生组合实现继承

方案一

创建新的对象,让对象原型指向父类原型,最后将对象原型赋值给子类原型

1
2
3
var obj = {}
Object.setPrototypeOf(obj.Person.prototype)
Student.prototype = obj

方案二

创建新的函数,让函数原型指向父类原型,最后将函数原型赋值给子类原型

1
2
3
function F() { }
F.prototype = Person.prototype
Student.prototype = new F()

方案三

使用Object.create()

1
2
var obj = Object.create(Person.prototype)
Student.prototype = obj

最终方案

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
function createObject(o) {
  function F() { }
  F.prototype = o
  return new F()
}

function inherit(Subtype, Supertype) {
  Subtype.prototype = createObject(Supertype.prototype)
  Object.defineProperty(Subtype.prototype, "constructor", {
    enumerable: false,
    configurable: true,
    writable: true,
    value: Subtype
  })
}

function Person(name, age, height) {
  this.name = name
  this.age = age
  this.height = height
}

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

Person.prototype.eating = function () {
  console.log(this.name + " eating~")
}

function Student(name, age, height, sno, score) {
  Person.call(this, name, age, height)
  this.sno = sno
  this.score = score
}

inherit(Student, Person)

Student.prototype.studying = function () {
  console.log(this.name + " studying~")
}

var stu = new Student("mocha", 18, 1.88, 1001, 100)
console.log(stu)
stu.running()
stu.eating()
stu.studying()

image-20230704181539585

六、Object是对象父类

Object是所有对象的父类

1
2
3
4
5
6
var info = {
  name: "mocha",
  age: 18
}

console.log(Object.getPrototypeOf(info).__proto__ === Object.prototype.__proto__)  // true

七、对象的其他方法补充

hasOwnProperty

判断对象是否有某一个属于自己的属性(不是在原型上的属性)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
function createObject(o) {
  function F() { }
  F.prototype = o
  return new F()
}

var obj = {
  name: "mocha",
  age: 18
}


var info = createObject(obj)
info.address = "广州"
info.intro = "广州很美丽"

console.log(info.hasOwnProperty("name"))  // false
console.log(info.hasOwnProperty("address"))  // true

in操作符

判断某个属性是否在某个对象或者对象的原型上

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
function createObject(o) {
  function F() { }
  F.prototype = o
  return new F()
}

var obj = {
  name: "mocha",
  age: 18
}


var info = createObject(obj)
info.address = "广州"
info.intro = "广州很美丽"

console.log("name" in info)  // true
console.log("address" in info)  // true

for in

遍历的不仅仅是自己身上的属性,也包括原型上的属性

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
function createObject(o) {
  function F() { }
  F.prototype = o
  return new F()
}

var obj = {
  name: "mocha",
  age: 18
}


var info = createObject(obj)
info.address = "广州"
info.intro = "广州很美丽"

for (var key in info) {
  console.log(key) 
}
// name
// age
// address
// intro

instanceof操作符

用于检测构造函数的prototype是否出现在某个实例对象的原型链上

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
function createObject(o) {
  function F() { }
  F.prototype = o
  return new F()
}

function inherit(Subtype, Supertype) {
  Subtype.prototype = createObject(Supertype.prototype)
  Object.defineProperty(Subtype.prototype, "constructor", {
    enumerable: false,
    configurable: true,
    writable: true,
    value: Subtype
  })
}

function Person() { }
function Student() { }

inherit(Student, Person)

var stu = new Student()

console.log(stu instanceof Student)  // true
console.log(stu instanceof Person)  // true
console.log(stu instanceof Object)  // true
console.log(stu instanceof Array)  // false

isPrototypeOf

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
function createObject(o) {
  function F() { }
  F.prototype = o
  return new F()
}

function inherit(Subtype, Supertype) {
  Subtype.prototype = createObject(Supertype.prototype)
  Object.defineProperty(Subtype.prototype, "constructor", {
    enumerable: false,
    configurable: true,
    writable: true,
    value: Subtype
  })
}

var obj = {
  name: "mocha",
  age: 18
}

var info = createObject(obj)
info.address = "广州"
info.intro = "广州很美丽"

function Person() { }
function Student() { }

inherit(Student, Person)

var stu = new Student()

console.log(Student.prototype.isPrototypeOf(stu))  // true
console.log(Person.prototype.isPrototypeOf(stu))  // true

/* 可以用于判断对象之间的继承 */
console.log(obj.isPrototypeOf(info))  // true