原型链,es5继承

73 阅读6分钟

原型

/**
 * 在JS中,每个对象都有一个特殊的内置属性[[prototype]]它指向另一个对象,即隐式原型
 */

1.对象的原型(隐式原型)

/**
 * 1,查看
 */

const ob = { name: "v", age: 1 }

console.log(ob.__proto__)
console.log(Object.getPrototypeOf(ob))
console.log(Object.getPrototypeOf(ob) === ob.__proto__)
/**
 * 2,作用
 * 访问对象的属性会触发[[get]]操作,先查找对象是否有这个属性,再沿着原型链查找,没找到就返回undefined
 */

const ob1 = { name: "a" }
console.log(ob1.age); // undefined

const ob2 = { name: "b" }
Object.prototype.age = 18
console.log(ob2.age); // 18

2.函数的显式原型

 /**
  * 所有函数都一个属性prototype(箭头函数除外),它指向另一个对象,即显式原型,利用这一特性实现继承
  */
 /**
  * 1,查看
  */

 const fn1 = function() {}
 const fn2 = () => {}

 console.log(fn1.prototype);
 console.log(fn2.prototype); // undefined
/**
 * 2,作用
 *
 * 通过new构造函数创造的对象实例,它的隐式原型就指向函数的显式原型这个对象
 */

function Person() {}

const p1 = new Person()
const p2 = new Person()

console.log(p1.__proto__ === p2.__proto__)
console.log(p1.__proto__ === Person.prototype)
console.log(p2.__proto__ === Person.prototype)
/**
 * 3.构造函数
 * 3.1,new发生了什么?
 * 
 * 创建一个空对象,this指向这个对象
 * 对象的隐式原型指向函数的显示原型并执行代码体
 * 根据返回值是否为对象决定返回值
 */
// 写法一
function myNew(fn, ...args) {
    const obj = {}
    Object.getPrototypeOf(obj) = fn.prototype
    const res = fn.apply(obj, args)
    return res instanceof Object ? res : obj
}

// 写法二
function myNew2(fn, ...args) {
    const obj = Object.create(fn.prototype)
    const res = fn.apply(obj, args)
    return res instanceof Object ? res : obj
}
 /**
  * 3.2,原型对象上添加属性方法
  * 
  * 每个函数都有name和length两个属性,name是函数的名称,length是形参个数
  * 
  * 结论:
  * 属性应该写在构造函数内部,∵每一个实例对象的属性都应该属于自己
  * 方法写在原型对象上,可以复用,避免多次定义多次调用浪费性能
  */

 function Person() {}

 Person.prototype.age = 18
 Person.prototype.getAge = function() { return this.age }

 const p1 = new Person()
 console.log(p1);
 console.log(p1.age);
 console.log(p1.getAge());
/**
 * 4,constructor
 * 每个原型对象上都有一个constructor,它默认指向了当前函数,形成循环引用
 * V8引擎GC采用标记清除算法,不会存在内存泄漏问题
 */
function Person() {}

console.log(Person.prototype.constructor === Person)
/**
 * 5,重写函数的原型对象带来的问题
 */
function Person(name, age) {
  this.name = name
  this.age = age
}

Person.prototype = {
  getAge: function () {
    return this.age * 2
  },
}

const p1 = new Person("a", 1)
console.log(p1.getAge()) // 2

/**
 * 探究此时的constructor
 *
 * 为什么?
 * 构造函数的显式原型是一个对象,原型对象上的constructor就指向当前函数
 * 对原型对象进行重写,此时的原型对象就等于一个全新的对象,这个对象本质上是通过new Object()创建的
 * ∴查找原型对象上的constructor属性时没找到,就沿着原型链找到了Object上的constructor即Object
 * 总结:万物皆对象
 *
 */
console.log(Person.prototype)
console.log(Person.prototype.constructor === Object) // true
/**
 * 自定义constructor
 */

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

Person.prototype = {
  getAge: function () {
    return this.age * 2
  },
}

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

3.函数的隐式原型

/**
 * 函数即是一个函数,也是一个特殊的对象
 */

function Person(name) {
  this.name = name
}
Person.age = 18
console.log(Person.age) // 18
console.log(Person.__proto__ === Function.prototype) // true

2.es5继承

/**
 * 继承
 *
 * 封装继承多态抽象
 */

2.1.原型链

/**
 * 原型链
 *
 * 实现继承的本质
 */

const pa = {
  name: "a",
  age: 1,
}
console.log(pa.__proto__) // Object.prototype

pa.__proto__ = {
  height: "120cm",
}

pa.__proto__.__proto__ = {
  weight: "120kg",
}

console.log(pa.height)
console.log(pa.weight)
/**
 * 原型链的尽头(顶层原型):Object.prototype,即万物皆对象,再找即为null
 *
 * 作用:
 * Object.prototype上有很多的属性和方法可以使用,如果valueOf()/toString()
 */

const pb = {
  name: "b",
  age: 2,
}

console.log(pb.toString()) // [object Object]
console.log(pb.valueOf() === pb) // true

2.2.实现继承

/**
 * 实现继承的方法
 * - 1.大聪明做法:引用赋值
 * - 2.原型链继承
 * - 3.借用构造函数继承(经典继承)
 * - 4.组合继承
 * - 5.原型式继承
 * - 6.寄生式继承
 * - 7.寄生式组合继承
 */
/**
 * 1,大聪明做法:将父类的原型对象赋值给子类的原型对象
 *
 * 存在的问题:
 * - 父类和子类共用一个原型对象,相互影响了,父类上是不该有study方法的
 * - name和age属性多次定义了
 */
function Person(name, age) {
  this.name = name
  this.age = age
}

Person.prototype.run = function () {
  console.log("run")
}

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

Student.prototype.study = function () {
  console.log("study")
}
const sa = new Student("a", 1)
sa.run()
sa.study()

const pa = new Person("a", 1)
pa.run()
pa.study()
/**
 * 2,原型链继承
 *
 * 存在的问题:
 * - name,age属性还是没有复用
 * - Person函数中的sex属性被继承下来了
 */
function Person(name, age) {
  this.name = name
  this.age = age
  this.sex = "man"
}

Person.prototype.run = function () {
  console.log("run")
}

function Student(name, age) {
  this.name = name
  this.age = age
}

const pa = new Person()
Student.prototype = pa
Student.prototype.study = function () {
  console.log("study")
}

const sa = new Student("a", 1)
sa.run()
sa.study()
console.log(sa.sex) // man
/**
 * 3,借用构造函数继承(经典继承)
 *
 * 解决了父类属性复用的问题
 */
function Person(name, age) {
  this.name = name
  this.age = age
}

function Student(name, age) {
  Person.apply(this, [name, age])
}

const pa = new Person("a", 1)
const sa = new Student("a", 1)
/**
 * 4,组合继承
 * 原型链继承 + 借用构造函数继承
 *
 * 缺点:
 * Person父类函数调用了两次,一个是属性复用,一次是创建pa实例对象
 */

function Person(name, age) {
  this.name = name
  this.age = age
}
Person.prototype.run = function () {
  console.log("run")
}

function Student(name, age) {
  Person.apply(this, [name, age])
}

const pa = new Person()
Student.prototype = pa
Object.defineProperty(Student.prototype, "constructor", {
  value: Student,
  writable: true,
  enumerable: false,
  configurable: false,
})
Student.prototype.study = function () {
  console.log("study")
}

const sa = new Student("a", 1)
sa.study()
sa.run()
/**
 * 5,原型式继承函数
 *
 * 本质:
 * - 创建一个空对象,对象的隐式=父类的显示原型,返回对象
 */

// 兼容性最好的
function create(obj) {
  function Fn() {}
  Fn.prototype = obj
  return new Fn()
}

// setPrototypeOf
// 不推荐使用,是一个很慢的操作
function create2(obj) {
  const newObj = {}
  Object.setPrototypeOf(newObj, obj)
  return newObj
}

// Object.create
// 和方法一本质一样
function create3(obj) {
  return Object.create(obj)
}

// __proto__
// 不考虑兼容性
function create4(obj) {
  const newObj = {}
  newObj.__proto__ = obj
  return newObj
}
/**
 * 6,寄生式继承函数
 */

function Person(name, age) {
  this.name = name
  this.age = age
}
Person.prototype.run = function () {
  console.log("run")
}

function create(obj) {
  function Fn() {}
  Fn.prototype = obj
  return new Fn()
}

function createStu(obj) {
  const f1 = create(obj)
  f1.study = function () {
    console.log("study")
  }
  return f1
}

const sa = createStu(Person.prototype)
const pa = new Person("a", 1)
console.log(sa)
console.log(pa)
/**
 * 7,寄生式组合继承
 */
function Person(name, age) {
  this.name = name
  this.age = age
}

Person.prototype.run = function () {
  console.log("run")
}

function Student(name, age) {
  Person.apply(this, [name, age])
}

function create(obj) {
  const Fn = function () {}
  Fn.prototype = obj
  return new Fn()
}

function inherit(sub, sup) {
  sub.prototype = create(sup.prototype)
  Object.defineProperty(sub, "constructor", {
    value: sub,
    writable: true,
    enumerable: false,
    configurable: false,
  })
}

inherit(Student, Person)

Student.prototype.study = function () {
  console.log("study")
}

const sa = new Student("a", 1)
sa.run()
sa.study()

3.es6类的继承

/**
 * 1,类的定义
 */

// 写法一
class Person1 {}
// 写法二
const Person2 = class {}
/**
 * 2,类与构造函数
 *
 * 本质是一样的
 */

class Person3 {}
const p3a = new Person3()
console.log(p3a.__proto__ === Person3.prototype) // true
console.log(Person3.prototype.constructor) // class Person3 {}
/**
 * 3,类的构造函数
 *
 * constructor(){}是每个类特有的构造函数,通过new 类调用
 */

/**
 * 4,类的实例方法
 *
 * 类中定义的方法是存放在原型对象上的
 */

class Person4 {
  constructor(name, age) {
    this.name = name
    this.age = age
  }
  run() {
    console.log(`${this.name} run`)
  }
  study() {
    console.log(`${this.name} study`)
  }
}

const p4a = new Person4("a", 1)
console.log(p4a)
p4a.run()
p4a.study()

console.log(p4a.run === Person4.prototype.run) // true
console.log(p4a.study === Person4.prototype.study) // true
/**
 * 5,类的静态方法(类方法)
 */

Function.prototype.money = 100
class Person5 {
  static getMoney() {
    console.log(this.money)
  }
}

Person5.getMoney()
/**
 * 6,类的继承
 */
class Person6 {}
class Student6 extends Person6 { }
/**
 * 7,类继承的体验
 *
 * super关键字:
 * 在子类的构造函数中使用this或返回默认对象前,使用super调用父类的构造函数
 * 使用位置:constructor,子类方法,静态方法
 */

class Person7 {
  constructor(name, age) {
    this.name = name
    this.age = age
  }
  run() {
    console.log(this.name + "run")
  }
}

const p7a = new Person7("p7a", 1)

class Student7 extends Person7 {
  constructor(name, age, sex) {
    super(name, age)
    this.sex = sex
  }

  study() {
    console.log(this.name + "study")
  }
}
const s7a = new Student7("s7a", 1, "man")
s7a.run()
s7a.study()
/**
 * 8,继承内置类
 */

class IceArr extends Array {
  lastItem() {
    console.log(this[this.length - 1])
  }
}

const ice = new IceArr(1, 2, 3)
ice.lastItem()