js原型链和继承

127 阅读5分钟

js原型链和继承

原型链的理解

var obj = {
  name: 'Fhup',
  age: 18
}

console.log(obj.__proto__) // [Object: null prototype] {}

obj.__proto__ = {

}
// 原型对象属性里的原型
obj.__proto__.__proto__ = {
  // address: '宝鸡市'
}
obj.__proto__.__proto__.__proto__ = {
  address: '北京市'
}


// obj.address 触发[[get]]操作
// 1.在当前的对象中查找该属性
// 2.没有找到,会去原型(__proto__)对象上查找
// 2.还找不到时,去当前原型对象属性里的原型上继续查找
// 3.查找时形成的链接称为原型链(prototype chain)
console.log(obj.address)

函数与对象的原型差别

function foo() {
  
}
// 普通函数的原型测试
// 与对象的区别是: 函数是Object的子类,而对象等价于new Object()
console.log(foo.prototype.constructor) // [Function: foo]
console.log(foo.__proto__.constructor) // [Function: Function]
console.log(foo.prototype === foo.__proto__) // false
console.log('--------------------')


var obj = {

}
// 是对象时等价于 var obj = new Object()
// 内部将 obj.__proto__ = Object.prototype(顶层原型)
console.log(obj.__proto__) // [Object: null prototype] {}
console.log(obj.__proto__ === Object.prototype) // true

console.log('````````````````');
// Object的测试
console.log(Object.__proto__) //   {}

console.log(Object.__proto__.constructor) // [Function: Function]
console.log(Object.prototype.constructor) // [Function: Object]
console.log(Object.__proto__ === Object.prototype) // false
console.log(Object.__proto__.__proto__) // [Object: null prototype] {}

顶层原型是什么

var obj  = {
  
}
console.log(obj.__proto__) // [Object: null prototype] {}
console.log(obj.__proto__.__proto__) // 顶层上面的原型就是null

// 结论: [Object: null prototype] {} 就是obj顶层的原型

顶层原型来自哪里

// var obj = {
//   name: 'Fhup',
//   age: 18
// }
var obj  = new Object() // 都是创建对象 看图知道顶层原型

// 顶层原型 Object.prototype
console.log(obj.__proto__)
console.log(Object.prototype)
console.log(obj.__proto__ === Object.prototype) // true

// Object函数的prototype有constructor属性指向Object
console.log(Object)
console.log(Object.prototype.constructor)

// 获取Object原型的属性描述符
console.log(Object.getOwnPropertyDescriptors(Object.prototype)); 

04_Object的原型对象.png

Person构造函数原型

function Person() {

}

console.log(Person.prototype) // {}
console.log(Person.prototype.__proto__) // [Object: null prototype] {}
console.log(Object.getOwnPropertyDescriptors(Person.prototype));

继承-原型链的继承方案

// 父类: 公共属性和方法
function Person() {
  this.name = "Fhup"
  this.friends = []
}

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

// 子类: 特有属性和方法
function Student() {
  this.sno = 001
}

// Student.prototype = new Person()位置很重要
var p = new Person()
Student.prototype = p

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


// name/sno
var stu = new Student()

// console.log(stu.name)
// stu.eating()

// stu.studying()


// 原型链实现继承的弊端:
// 1.第一个弊端: 打印stu对象, 继承的属性是看不到的
// 默认的继承会看到该对象所有的属性
// console.log(stu.name)

// 2.第二个弊端: 创建出来两个stu的对象
var stu1 = new Student()
var stu2 = new Student()

// 直接修改对象上的属性,给自己对象上添加了一个新属性
stu1.name = "kobe"
console.log(stu2.name)

// 获取引用, 修改引用中的值,会相互影响(例如:push)
stu1.friends.push("kobe")

console.log(stu1.friends) // [ 'kobe' ]
console.log(stu2.friends) // [ 'kobe' ]

// 3.第三个弊端: 在前面实现类的过程中都没有传递参数
// 参数的处理要放在父类中,下面这种写法在子类中
var stu3 = new Student("lilei", 112)

继承-借用构造函数方案

// 父类: 公共属性和方法
function Person(name, age, friends) {
  // Person.call(this, name, age, friends) 这种 this 就是 Student
  this.name = name
  this.age = age
  this.friends = friends
}

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

// 子类: 特有属性和方法
function Student(name, age, friends, sno) {
  // *** 借用Person构造器的代码执行对Student()的参数进行初始化
  Person.call(this, name, age, friends)
  
  this.sno = sno
}

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

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


// 通过Person.call(this, name, age, friends)解决原型链继承方案的三个弊端
// var stu1 = new Student('Fhup1', 10, ['fhf1'], 3)
// var stu2 = new Student('Fhup2', 12, ['fhf2'], 9)

// stu1.friends.push("kobe")
// console.log(stu1.friends) // [ 'kobe' ]
// console.log(stu2.friends) // [ 'kobe' ]

// 借用构造函数的弊端
// 1.Person函数至少调用了二次 (new Person()和Person.call())
// 2.Student.prototype的原型对象上多出一些属性,属性多余.
// new Person()里面的值为undefined

继承-父类原型赋值给子类

// 父类: 公共属性和方法
function Person(name, age, friends) {
  // this = stu
  this.name = name
  this.age = age
  this.friends = friends
}

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

// 子类: 特有属性和方法
function Student(name, age, friends, sno) {
  Person.call(this, name, age, friends)
  this.sno = sno
}

/**
 * 直接将父类的原型赋值给子类, 作为子类的原型
 * 弊端: 
 * 1.每个子类都会给父类添加自己的方法,导致父类方法过于庞大
 * 2.每个子类还可以调用其他子类的方法
 */
Student.prototype = Person.prototype

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


var stu = new Student("why", 18, ["kobe"], 111)
console.log(stu)
stu.eating()

继承-原型式继承-对象

var obj = {
  name: 'Fhup',
  age: 18
}

// 原型式继承函数
function createObject(o) {
  var newobj = {}
  Object.setPrototypeOf(newobj, o)
  return newobj
}
function createObject2(o) {
  function Fn() {}
  Fn.prototype = o
  var newobj = new Fn()
  return newobj
}


// info对象的原型指向obj
// var info = createObject(obj)
// var info = createObject2(obj)

//创建一个新对象,使用现有的对象来提供新创建的对象的__proto__属性
var info = Object.create(obj)

console.log(info.__proto__)

继承-寄生式继承-对象

// 了解这种方案即可
var personObj = {
  running: function() {
    console.log("running")
  }
}

// 相当于工厂函数
function createStudent(name) {
  var stu = Object.create(personObj)
  stu.name = name
  stu.studying = function() {
    console.log("studying~")
  }
  return stu
}

var stuObj = createStudent("why")
var stuObj1 = createStudent("kobe")
var stuObj2 = createStudent("james")

// 弊端: 类型不明确

继承-寄生组合式继承(最终)

// 二个核心:
// 1.原型式
function createObject(o) { // o: 参入原型对象
  function Fn() {}
  Fn.prototype = o
  return new Fn()
}
// 2.寄生式 
function inheritPrototype(SubType, SuperType) {
  // SubType.prototype = Object.create(SuperType.prototype)
  SubType.prototype = createObject(SuperType.prototype) 
  Object.defineProperty(SubType.prototype, 'constructor', {
  writable: true,
  configurable: true,
  enumerable: false,
  value: SubType
})
}

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

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

function Student(name, age, sex, address) {
  Person.call(this, name, age, sex)
  this.address = address
}

// 这个函数让Student()继承Person()
inheritPrototype(Student, Person)

Student.prototype.say = function() {
  console.log(this.name + ' saying~');
}

var stu = new Student('Fhup', 18, '男', '北京市')
console.log(stu);
stu.say()
stu.running()

判断方法的补充

var obj = {
  name: 'Fhup'
}

var info = Object.create(obj, { // 参数2: info的默认值
  address: {
    value: '北京市',
    enumerable: true
  }
})

// console.log(info) // { address: '北京市' }
// console.log(info.__proto__) // { name: 'Fhup' }

// info对象是否有自己独有的属性
// console.log(info.hasOwnProperty('address')) // true
// console.log(info.hasOwnProperty('name')) // false

// in 操作符  在当前对象还是原型中返回的都是true
// console.log('address' in info) // true
// console.log('name' in info) // true

// for in
// for(var key in info) {
//   console.log(key)
// }

instanceof的判断

function Person() {

}
function Student() {

}

Student.prototype = Object.create(Person.prototype)
Student.prototype.constructor = Student

var stu = new Student()
console.log(stu) // Student {}

// instanceof 检测构造函数的prototype,是否出现在某个实例对象的原型链上
console.log(stu instanceof Student) // true
console.log(stu instanceof Person) // true
console.log(stu instanceof Object) // true

// 可以得出结论: Object是所有类的父类

isPrototypeOf的判断

function Person() {

}

var p = new Person()

console.log(p instanceof Person) // true

// isPrototypeOf针对某个对象,是否出现在某个实例对象的原型链上
// 判断Person的原型对象是否出现在p的原型链上
console.log(Person.prototype.isPrototypeOf(p)) // true


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

var info = Object.create(obj)
// 判断obj对象是否出现在info的原型链上
console.log(obj.isPrototypeOf(info))

对象-函数-原型之间的关系

var obj = {
  name: "why"
}

console.log(obj.__proto__) // [Object: null prototype] {}

// 对象里面有一个__proto__对象: 隐式原型对象

// Foo是一个函数时,它会有一个显示原型对象: Foo.prototype
// Foo.prototype来自哪里?
// 答案: 创建了一个函数, Foo.prototype = { constructor: Foo }

// Foo是一个对象, 那么它会有一个隐式原型对象: Foo.__proto__
// Foo.__proto__来自哪里?
// 答案: new Function()  Foo.__proto__ = Function.prototype
// Function.prototype = { constructor: Function }

// var Foo = new Function()
function Foo() {

}

console.log(Foo.prototype === Foo.__proto__) // false
console.log(Foo.prototype.constructor) // [Function: Foo]
console.log(Foo.__proto__.constructor) // [Function: Function]
// 特殊
console.log(Function.prototype === Function.__proto__) // true

var foo1 = new Foo()
var obj1 = new Object()

console.log(Object.getOwnPropertyDescriptors(Function.__proto__))

// 对图片的理解
/**
 * Foo() 的 prototype 指向 Foo.prototype   var f1 = new Foo()  f1的__proto__指向Foo.prototype
 * Foo() 的 __proto__ 指向 Function.prototype
 * Foo.prototype 的 __proto__ 指向 Object.prototype
 * Object() 的 prototype 指向 Object.prototype  var o1 = new Object()  o1的__proto__ 指向 Object.prototype
 * Object() 的 __proto__ 指向 Function.prototype
 * Function() 的 prototype 指向 Function.prototype
 * Function() 的 __proto__ 指向 Function.prototype
 * Function.prototype 的 __proto__ 指向 Object.prototype
 * Object.prototype 的 __proto__ 指向 null
 * 
 * 所有的constructor指向函数本身
 * 对象只有__proto__  函数有__proto__ 和 prototype
 */

04_对象-函数-原型之间的关系.PNG