js对象深入、原型与原型链

699 阅读6分钟

js对象深入、原型与原型链

面向对象的特性

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

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

JavaScript原型链

从一个对象上获取属性,如果在当前对象中没有获取到就会去它的原型上面获取

  • 所有的引用类型(数组,对象,函数,除了null以外,null也是引用类型),都有一个__proto__属性:(隐式原型),你可以通过 ES6 的 Object.getPrototypeOf() 来访问该属性,其属性值是一个普通对象 代码如下:
function Cat() {
    this.color = 'orange'
}

var cat = new Cat()
console.log(cat.__proto__)
console.log(Object.getPrototypeOf(cat) === cat.__proto__)  // true
  • 所有的函数,都有一个prototype属性:(显式原型),其属性值是一个普通对象
  • 所有的引用类型(数组,对象,函数,除了null以外),__proto__属性值指向它的构造函数的 prototype 的属性值

参考代码如下:

function Cat() {
    this.color = 'orange'
}

var cat = new Cat()

console.log(cat.__proto__ === Cat.prototype)   // true

有关构造函数的 prototype 和实例对象的 __proto__ 的关系,我们可以用张图来体现一下。

(构造函数的 prototype 和实例对象的 proto 关系图)

什么地方是原型链的尽头呢?

Object.prototype 的原型会是什么?

console.log(Object.prototype.__proto__)   // null

它就是 null,null 没有原型,所以 Object.prototype 就是原型链的最顶端。

可以说,JavaScript 中的所有对象都来自 Object,Object 位于原型链的最顶端,几乎所有 JavaScript 的实例对象都是基于 Object。

我们可以将图片更新一下:

(原型链)

基于原型链的继承其实随处可见,只是我们没有意识到。当你随手新建一个数组,是否想过它怎么会有 splice、indexOf 等方法,新建一个函数怎么可以直接使用 call 和 bind?其实数组都继承于 Array.prototype,函数都继承于 Function.prototype,它们分别包含了数组和函数的基本方法,尝试去控制台打印出 Array.prototype 和 Function.prototype,上面的疑问便可得到解答。

继承

原型链实现继承(简单粗暴,直接修改prototype)

代码如下:

//1.定义父类构造函数
function Person() {
    this.name = "why"
}
//2.父类原型上添加内容
Person.prototype.running = function () {
    console.log(this.name + "running~")
}
//3.定义子类构造函数
function Student() {
    this.sno = 111
}
//4.创建父类对象,并且作为子类的原型对象
var p = new Person()
Student.prototype = p
//5.在子类原型上添加内容
Student.prototype.studying = function () {
    console.log(this.name + "studying")
}

var stu = new Student()
console.log(stu.name)

原型链继承的弊端

  1. 我们通过直接打印对象是看不到这个属性的
  2. 这个属性会被多个对象共享,如果这个对象是一个引用类型,那么就会造成问题;
  3. 不能给Person传递参数,因为这个对象是一次性创建的(没办法定制化);

借用构造函数继承

可解决上述3个弊端 实现方式: 在子类型构造函数的内部调用父类型构造函数.

代码如下:

//1.定义父类构造函数
function Person(name) {
    this.name = name
}
//2.父类原型上添加内容
Person.prototype.running = function () {
    console.log(this.name + "running~")
}
//3.定义子类构造函数
function Student(name, sno) {
    Person.call(this, name)
    this.sno = sno
}

//4.创建父类对象,并且作为子类的原型对象(这里主要继承父类原型上的的方法)
var person = new Person()
Student.prototype = person

//5.在子类原型上添加内容
Student.prototype.studying = function () {
    console.log(this.name + "studying")
}

var stu = new Student('john', 'rrrr')
console.log(stu.name,stu.runinig())

弊端

  1. 会调用两次父类构造函数。
  • 一次在创建子类原型的时候
  • 一次在子类构造函数内部
  1. 所有的子类实例事实上会拥有两份父类的属性
  • 一份在当前的实例自己里面(也就是person本身的),另一份在子类对应的原型对象中(也就是 person.__proto__里面);

原型式继承函数(针对对象)

实现目的:新对象的原型指向旧的对象

方法一:

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

function createObject(o) {
    var newObj = {}
    Object.setPrototypeOf(newObj, o)
    return newObj
}

var info = createObject(obj)

方法二:

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

function createObject(o) {
    function Func(){}
    Func.prototype = o
    return new Func()
}

var info = createObject(obj)

方法三:


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

var info = Object.create(obj)

寄生式继承函数

寄生式继承的思路是结合原型类继承和工厂模式的一种方式;


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 stu1 = createStudent('why')

弊端

每次创建的学生都会有studying函数,内存浪费

寄生组合式继承(最终解决方案)

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

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



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

// 修改Student的隐式原型指向
Student.prototype = Object.create(Person.prototype)
// 增加student的构造值
Object.defineProperty(Student.prototype, 'constructor', {
    enumerable: false,
    configurable: true,
    writable: true,
    value: Student
})

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


var stu = new Student('john', 18, ['zs'],125,99)
console.log(stu)

优化后代码如下:

/**
 * 修改子类构造函数显示原型的__proto__指向
 * @param {子类构造函数} SubType 
 * @param {父类构造函数} SupType 
 */

function inheritPrototype(SubType, SupType) {
    // 修改SubType的隐式原型指向
    SubType.prototype = Object.create(SupType.prototype)
    // 增加SubType的构造值
    Object.defineProperty(SubType.prototype, 'constructor', {
        enumerable: false,
        configurable: true,
        writable: true,
        value: SubType
    })
}

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

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



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

inheritPrototype(Student,Person)

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


var stu = new Student('john', 18, ['zs'],125,99)
console.log(stu)

对象方法

hasOwnProperty

  • 对象是否有某一个属于自己的属性(不是在原型上的属性) 代码如下:
var obj = { name: "why", age: 18 }
var info = Object.create(obj, {
    address: {
        value: "北京市",
        enumerable: true
    }
})
console.log(info.hasOwnProperty("address")) //  true
console.log(info.hasOwnProperty("name"))    //  false

in/for in 操作符

  • 判断某个属性是否在某个对象或者对象的原型上
var obj = { name: "why", age: 18 }
var info = Object.create(obj, {
    address: {
        value: "北京市",
        enumerable: true
    }
})
console.log('address' in info) //  true
console.log('name' in info)    //  true

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

instanceof

  • 用于检测构造函数的pototype,是否出现在某个实例对象的原型链上(对象是否在函数
/**
 * 修改子类构造函数显示原型的__proto__指向
 * @param {子类构造函数} SubType 
 * @param {父类构造函数} SupType 
 */

function inheritPrototype(SubType, SupType) {
    // 修改SubType的隐式原型指向
    SubType.prototype = Object.create(SupType.prototype)
    // 增加SubType的构造值
    Object.defineProperty(SubType.prototype, 'constructor', {
        enumerable: false,
        configurable: true,
        writable: true,
        value: SubType
    })
}

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

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

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

inheritPrototype(Student,Person)

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


var stu = new Student('john', 18, ['zs'], 125, 99)
console.log(stu instanceof Student)  // true
console.log(stu instanceof Person)  // true
console.log(stu instanceof Object)  // true

isPrototypeOf

  • 用于检测某个对象,是否出现在某个实例对象的原型链上(对象是否在对象
function Person() { }
var p = new Person()
console.log(p instanceof Person)  // true
console.log(Person.prototype.isPrototypeOf(p))  // true

原型继承关系

究极蛇皮怪。。。

Function.prototype === Function.__proto__  // true
Object.__proto__ === Function.prototype  // true

image.png