【学习】你不知道的JavaScript + 忍者秘籍 + 红宝书 -- 原型、对象、继承、instanceof

本文从prototype/__proto__/[[prototype]]切入,理清实例,构造函数,原型之间的关系之后,列举创建对象的方法,分析继承的原理及方法

实例/构造函数/原型

首先,先祭出神图(第一列为实例,第二列为构造函数,第三列为原型)
在这里插入图片描述

补充说明:

  1. 每个函数都有prototype属性,该属性指向原型,原型也是一个对象。
  2. 每个对象(除了Object.prototype)都有[[prototype]]属性,指向了创建该对象的构造函数的原型。但是由于[[prototype]]属性是内部属性,我们并不能访问到,所以使用__proto__来访问。
  3. __proto__将对象连接起来组成了原型链。 对象可以通过__proto__来寻找不属于该对象的属性.
  4. 函数的prototype属性指向它的原型,函数原型的constructor属性指向该函数
  5. 实例的__proto__指向它的构造函数的原型
  6. 函数的__proto__指向Function的原型(每一个构造函数都是Function的实例)
  7. 函数的原型的__proto__都指向Object的原型,Object的原型是null(原型链的最顶端)
  8. 实例可以通过原型链找到本身不存在的属性和方法 (比如 f1 沿着 __proto__ 可以找到construtor 属性 : f1.constructor = Foo)

创建对象(红宝书4th)

  • 工厂模式
function createPeason(age,name,job){
  let o = new Object();
  o.name = name;
  o.age = age;
  o.job = job;
  o.sayName = function(){console.log(this.name)}
  return o;
}
const p1 = createPeason(18,"ss","IT")
//存在的问题: 没有指定原型 
  • 构造函数模式
function Person(age,name,job){
  this.name = name;
  this.age = age;
  this.job = job;
  this.sayName = function(){console.log(this.name)}
}
const p1 = new Person(18,"SS","IT")
//存在的问题: 定义的方法会在每个实例上都创建一遍

与工厂模式的区别:

  1. 没有显示的创建对象;
  2. 属性和方法直接赋值给了this;
  3. 没有return

回顾一下new的原理

  1. 在内存中创建一个新对象;
  2. 这个新对象内部的[[prototype]]属性被赋值为构造函数的prototype属性
  3. 构造函数内部的this被赋值为这个新对象(即this指向新对象),并执行构造内部代码(给新对象添加属性和方法)
  4. 如果构造函数返回非空对象,则返回该对象,扔掉刚创建的新对象,否则,返回刚创建的新对象。

注意: 构造函数和普通函数唯一的区别就是调用方式不同,任何函数只要使用new操作符调用就是构造函数,不使用new操作符调用就是普通函数。

//如果上面的Person采用如下方式调用,就是在window全局对象上添加属性和方法
Person(18,"SS","IT")
window.sayName() // SS
  • 原型模式
funciton Person(){};
Person.prototype.name = "SS";
Person.prototype.age = 18;
Person.prototype.job = "IT";
Person.prototype.sayName = function(){console.log(this.name)}
const p1 = new Person();
const p2 = new Person();
p1.sayName() // "SS"
p2.sayName() // "SS"
//属性和方法由所有的实例共享
p1.name = "SSS"
p1.sayName() // "SSS"
p2.sayName() // "SS"
//实例上的属性会遮蔽原型上的同名属性

继承(红宝书4th)

  • 原型链
function Super(){}
function Sub(){}
//继承Super
Sub.prototype = new Super();
const ins = new Sub();
console.log(ins instanceof Super) //true
// 问题: ins.constructor = Super

原型链的问题:

  1. 原型中包含的引用值会在实例之间共享
  2. 子类型在实例化时不能给父类型的构造函数传参
  • 盗用构造函数
    为了解决原型包含引用值导致的继承问题,出现了盗用构造函数方式的继承,原理是 : 在子类构造函数中调用父类构造函数。
function Super(){
  this.colors = ['red','yellow']
}
function Sub(){
  //继承Super
  Super.call(this) 
  //Super构造函数在为Sub的实例创建的新对象的上下文中执行了。
  //相当于新的Sub对象上运行了Super函数中所有的初始化代码,结果就是每个实例都会有自己的colors属性
}
const ins1 = new Sub();
ins1.colors.push('white') 
const ins2 = new Sub();
ins2.colors.push('black')
console.log(ins1.colors) // 'red','yellow','white'
console.log(ins2.colors) // 'red','yellow','black'

优点

  1. 可以在子类构造函数中向父类构造函数传参

缺点

  1. 必须在构造函数中定义方法,不能复用
  2. 子类不能访问父类原型上的方法,因为没有形成原型链
  • 组合继承
    综合原型链和盗用构造函数,将两者的优点集中起来。思路:使用原型链继承原型上的属性和方法,通过盗用构造函数继承实例属性。
function Super(name){
  this.name = name;
  this.colors = ['red','yellow']
}
Super.prototype.sayName = function(){
  return this.name
}
function Sub(name,age){
  //继承属性
  Super.call(this,name) //1
  this.age = age
}
//继承方法
Sub.prototype = new Super(); //2

缺点:

  1. 两次调用父类构造函数,使得父类的属性/方法重复定义
  • 原型式继承
    创建一个临时构造函数,并将传入的对象赋值给这个构造函数的原型。
    适用场景: 不需要单独创建构造函数,但仍然需要在对象间共享信息
function newObject(o){
  function F(){};
  F.prototype = o;
  return new F();
}
const person = {
  name : "ss",
  age : 18,
  colors : ['red','yellow']
}
const person2 = newObject(person);
person2.name = "sss";
person2.colors.push('white');

const person3 = newObject(person);
person3.name = "ssss";
person3.colors.push('black');
console.log(person.colors) // 'red','yellow','white','black'
//问题: person相当于是person2和person3的原型,所以person的属性和方法在person2和person3中是共享的

这里的功能类似于Object.create()

const person = {
  name : "ss",
  age : 18,
  colors : ['red','yellow']
}
const person2 = Object.create(person);
person2.name = "sss";
person2.colors.push('white');

const person3 = Object.create(person);
person3.name = "ssss";
person3.colors.push('black');
console.log(person.colors) // 'red','yellow','white','black'

缺点:

  1. 在对象间共享属性和方法
  • 寄生式继承
    思路类似于寄生构造函数和工厂模式:创建一个实现继承的函数,以某种方式增强对象,然后返回这个对象。
    适用场景: 主要关注对象,而不在乎类型和构造函数
function createAnotherPerson(original){
  const clone = newObject(original); // 通过调用函数创建一个对象
  clone.sayHi= function(){ //增强对象
    console.log("hi")
  }
  return clone //返回对象
}
const person1 = createAnotherPerson(person)
//基于person返回了一个新对象,person1具有person的所有属性和方法,还有一个新方法sayHi

缺点:

  1. 函数难以复用,与构造函数模式类似
  • 寄生式组合继承 [引用类型继承的最佳模式]
    为解决组合继承中两次调用重复的问题,有了寄生式组合继承。基本思路: 不通过调用父类构造函数给子类原型赋值,而是取得父类原型的一个副本。即使用寄生式继承来继承父类原型,然后将返回的新对象赋值给子类型。
function inheritPrototypr(sub,super){
  const prototype = newObject(super.prototype) //以super.prototype为原型创建对象,创建的对象只有原型上的属性和方法
  prototype.constructor = sub; //增强,指定
  sub.prototype = prototype  //赋值,形成原型链
}
function Super(){
  this.name = name;
  this.colors = ['red','yellow']
}
Super.prototype.sayName = function(){
  return this.name
}
function Sub(name,age){
  Super.call(this,name); //继承Super的属性和方法 1
  this.age = age
}
inheritPrototypr(Sub,Super)//继承
Sub.prototype.sayAge = function(){
  return this.age;
}

优点: 只调用了一次Super的构造函数,并保留了原型链。

instanceof

操作符 instanceof 用于检测构造函数的 prototype 属性是否出现在某个实例对象的原型链上。

function A(){};
function B(){};
A.prototype = new B();
const c = new A();
console.log( c instanceof B ) //true 
// c实例的原型链由new B()对象,B的原型,Object的原型组成
//在检查 c instanceof B 时,JavaScript引擎查找B的原型,是否存在于c实例的原型链上

参考资料
js [[Prototype]]、proto 和Prototype
你不知道的JavaScript
JavaScript高级程序设计 4th
JavaScript忍者秘籍 2th

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值