原型链 构造函数

615 阅读6分钟

构造对象有两种方式

  • 方式1: 构造函数+prototype 或者你就理解为一个函数+prototype
  • 方式2: es6语法class
  • class语法只是一种语法糖

原型链的理解

  • [anyobject].--proto-- === [Object].prototype // 公式1 含义是:对象anyobject是由什么构造出来的?
  • [anyfunction].prototype.constructor === [anyfunction] // 公式2 含义是:任何一个构造函数的prototype上的构造函数指针指向这个构造函数自身
  • Object.prototype.--proto-- === null // 就是这么设计的
  • Function.--proto-- === Function.prototype // 带入公式1
  • Function.prototype.proto === Object.prototype // 带入公式1 然后思考Function.prototype这是一个对象,对象的构造函数当然是Object啦
  • 所有函数一出生就就有prototype, 所有prototype一出生就有constructor属性,constructor属性一出生就保存着对应函数的地址

基础定义

  • 原型:存放公有方法的地方,简化了代码 也就节省了内存
  • 构造函数:可以new出对象的函数就叫构造函数**,开头是大写的!!**
  • Array Function Date RegExp Object等都是class,也就是对象这种复杂数据类型分支下的分类名称而已
    • **在对象这种复杂数据类型中,Object是顶级的分类,是最上面的 **
  • 言语的表达上,可以说 数组是一种对象,也可以说数组是一类class ✔
  • 七大数据类型是对 JS 中数据的分类,与class是两回事
  • 数据类型中人们说的对象,和Object不是一回事
    • 正确的口头表达是:'对象'/object/Object这三种写法都可以被认作是一种数据类型,名叫对象✔
    • **大写的Object可以被称之为是一种构造函数 ✔ **
    • **大写的Object是对象这种数据类型的一个class ✔ **
  • 每个新的函数都出厂自带prototype,这是很特殊的,因为每个obj是不会自带prototype的

Object.prototype(直接或间接)是所有对象的原型

为什么Function instanceof Object为true?

Function.__proto__ === Function.prototype //Function是一个构造函数,也就是函数,那么一个函数是由什么构造出来的?当然Function类
Function.prototype.__proto__ === Object.prototype //Function.prototype是一个对象没错吧,那么对象是由什么构造出来的?当然就是Object类啦
// 就此,我们证明了Function.__proto__.__proto__ === Object.prototype
// 当然你也可以console.dir(Function) 连续两次打开它的隐藏属性__proto__去查看

首字母大写的Math不是构造函数,而只是一个普通的对象

关于原型在口语中的正确描述

  • obj.--proto-- 与 Object.prototype 都只是地址,地址指向一个叫原型的空间,描述的是同一个东西
  • 只要一个东西是由另外一个构造出来的那么 那么这个东西就是有原型的
  • 句式1: 子对象的原型是构造函数.prototype 或者 子对象的原型是子对象.--proto--
  • 例如我们可以说obj的原型是Object.prototype ✔
  • 例如我们可以说obj的原型是obj.--proto--,与上面这句是一模一样的 ✔
  • 例如我们可以说Object的原型是Function.prototype ✔
  • 句式2: 构造函数.prototype是子对象的原型 或者 子对象.--proto--是子对象的原型
  • 例如我们可以说Object.prototype是obj的原型 ✔
  • 例如我们可以说Function.prototype是Object的原型 ✔
  • 例如我们可以说Object.--proto-- 是 Object 的原型,与上面这句是一模一样的 ✔

  • Object 的原型是 Object.--proto-- ✔而不是Object.prototype ❌

重要理解

构造函数开头是大写的

普通函数开头是小写的

构造函数和普通函数都有 prototype 属性,只不过普通函数的prototype属性没什么用

构造函数往下找用 Xxx.prototype

构造函数往上找用 Xxx.--proto--. 或者省略这个--proto--隐藏属性

obj.--proto-- 与 Object.prototype 都是地址,地址指向一个叫原型的空间

[anyfunction].prototype.constructor === anyfunction // true 这算是一个公式

// 例子1
Object.prototype.constructor === Object // true 
// 任何一个函数都有prototype,因为公式,任何一个函数.prototype.constructor是函数自身
// 例子2
let fn1=function(){};
fn1.prototype.constructor === fn1 // true 
// 例子3
为什么Function.--proto--.constructor === FunctionFunction.__proto__ === Function.prototype // Function是一个函数,函数当然是由Function类型的对象构建的
Function.prototype.constructor === Function // 任何一个函数.prototype.constructor是自身

对象的构造函数是Object

let obj={};
obj.__proto__.constructor === Object // true 你就问自己 obj是通过谁创造的? Object
// 套用公式解题:
obj.__proto__ === Object.prototype // 公式1
Object.prototype.constructor === Object // 公式2

函数的构造函数是Function

let fn1=function(){};
fn1.__proto__.constructor === Function // true 你就问自己 fn1是通过谁创造的? Function
// 套用公式解题:
fn1.__proto__ === Function.prototype // 公式1
Function.prototype.constructor === Function // 公式2

window是Window构造函数构造出来的

window.__proto__ === Window.prototype; // true  通过原型判断
window.constructor === Window; // true 通过构造函数判断 其实是window.__proto__.constructor === Window;

Window是Function类构造出来的

因为Window本身就是构造函数,是一个函数,当然是由Function类构造出来的

Window.constructor === Function // true  这其实是Window.__proto__.constructor

Object是Function类构造出来的

因为Object本身就是构造函数,是一个函数,当然是由Function类构造出来的

window.Object.constructor === window.Function // true  这其实是window.Object.__proto__.constructor

Function也是Function类构造出来的

Function是构造函数,是一个函数,当然是由Function类构造出来的

window.Function.constructor === window.Function // true 这其实是window.Function.__proto__.constructor

为什么arr.--proto--.--proto-- 指向的是Object的公有方法?

var prototypeObj={name:'ryan'};
var obj=Object.create(prototypeObj); // 让prototypeObj这个对象成为obj的原型,即obj.__proto__就是prototypeObj的内容
obj.__proto__ === prototypeObj // true 这里得到了印证
prototypeObj.__proto__ === Object.prototype // 当然是成立的,因为prototypeObj这个普通对象本身就是由Object构造函数所创建的
var arr=[1,2,3];
arr.__proto__ === Array.prototype; // Array.prototype也是一个Obejct类型对象,只要是对象,必然继承Object的公有属性
Array.prototype.__proto__ === Object.prototype;

检测一下是否理解了原型链?

// 运行以下代码,创建构造函数,让你回答以下问题
let square=[];
let width=5;

function Square(width){
this.width=width; // 为new出来的新对象提供私有属性
}
Square.prototype.getArea=function(){ // 为new出来的新对象提供公有属性
return this.width * this.width;
}
Square.prototype.getLength=function(){
return 4 * this.width;
}

square=new Square(width);
// 可以用 === 依次验证一遍
square.__proto__ 指向什么? Square.prototype
square.__proto__.__proto__ 指向什么? Object.prototype
Square.__proto__ 指向什么? Function.prototype
Square.__proto__.constructor 指向什么? Function
Square.prototype.__proto__ 指向什么? Object.prototype
// 因为prototype是一个对象,那么一个对象的原型自然是来自于Object.prototype
Square.prototype.constructor 指向什么? Square

命名规范

  • 普通函数小写开头且动词开头 createElement
  • 构造函数大写开头且名词 new Object()

如何实现继承

已知4个正方形的边长,输出他们的面积与周长

  • V1-V3都是一样的东西,不用多虑

V1 用到了原型 但是未封装构造函数

let squareList=[];
let widthList=[5,6,5,6];
let squarePrototype={ // 原型对象 ,注意这只是一个对象,甚至不是函数,当然就不叫构造函数
	getArea(){
     return this.width * this.width;
    },
    getLength(){
     return 4 * this.width;
	}
}
for(let i=0;i<4;i++){
squareList[i]=Object.create(squarePrototype); // 为每个数组元素创建对象,并设置原型
squareList[i].width=widthList[i];
}
console.log(squareList); // 可以看到每个数组元素是有width属性的,而且原型是squarePrototype
squareList[0].getArea(); // 可以得到面积
squareList[0].getLength(); // 可以得到周长

V2 用到了原型且封装了构造函数

但是上面这个代码太松散了,不够有组织结构,那么我们现在制作一个有组织的构造函数

// 现在制作一个有组织的构造函数createSquare
let squareList=[];
let widthList=[5,6,5,6];
function createSquare(width){ // 构造函数
let obj = Object.create(squarePrototype); // 把原型对象灌进obj
obj.width = width;
return obj
}
let squarePrototype={ // 原型对象
	getArea(){
     return this.width * this.width;
    },
    getLength(){
     return 4 * this.width;
	}
}
for(let i=0;i<4;i++){
squareList[i]=createSquare(widthList[i]);
}
console.log(squareList); 
squareList[0].getArea(); 
squareList[0].getLength(); 

V3 将原型与封装好的构造函数一体化

但是上面的代码中函数和原型依旧是分散的,能否让他们更加紧密呢?
**答案是可以的,因为函数也是object类型,所以也有.prototype **

// 原型与构造函数互相引用
let squareList=[];
let widthList=[5,6,5,6];
function createSquare(width){ // 构造函数
let obj = Object.create(createSquare.squarePrototype);
obj.width = width;
return obj
}
createSquare.squarePrototype={ // 构造函数身上的原型对象,足够紧密了吧
	getArea(){
     return this.width * this.width;
    },
    getLength(){
     return 4 * this.width;
	},
    constructor:createSquare // 指向构造函数,这是必须要这么做的,虽然没什么用,参照公式2
    // [anyfunction].prototype.constructor === [anyfunction]
}
for(let i=0;i<4;i++){
squareList[i]=createSquare(widthList[i]);
}
console.log(squareList); 
squareList[0].getArea(); 
squareList[0].getLength(); 

es6前的终极版本 new + this 这是JS之父为我们提供的便捷

  • 与上面不同的是,不用Object.create了,直接挂在构造函数.prototype
  • 构造函数名记得要首字母大写 Square
// new一个对象
let squareList=[];
let widthList=[5,6,5,6];
function Square(width){
this.width=width; // 为new出来的新对象提供私有属性
}
Square.prototype.getArea=function(){ // 为new出来的新对象提供公有属性
return this.width * this.width;
}
Square.prototype.getLength=function(){
return 4 * this.width;
}
for(let i=0;i<4;i++){
squareList[i]=new Square(widthList[i]);
}
console.log(squareList);
squareList[0].getArea(); 
squareList[0].getLength(); 

new的4个作用

let obj = new Constructor()

  • 自动创建空对象
  • 为空对象关联原型,即设置obj.--proto-- = Constructor.prototype
  • 让this指向空对象并且运行构造函数
  • 自动return 新创建的对象

以下面这个例子为例解释一下即可

function Animal(login) {
  this.login = login;
  this.sayHi = function() {
    console.log(this.login);  //undefined
  }
}
var dog = new Animal('John');
console.log(dog)

请画出let x = {}的内存图

Object.prototype.--proto-- ==== null

数组Array只是一种对象

let arr=[1,2,3];
Object.keys(arr);
// ["0", "1", "2"] 由此可见数组Array只是一种对象

如何理解数组的公有方法 push pop shift unshift

搞清楚英文名的意思即可

用class语法新建一个class

class Square{
  constructor(width){
  	this.width=width; // 私有属性
  }
  getArea(){ // 写在原型上的方法
  	return this.width * this.width;
  }
  getLength(){ // 第一种形式,几乎只用这种写法
  	return 4 * this.width;
  }
}

以下两种写法引以为戒即可

  getLength=()=>{ // 第二种形式,几乎不用这种写法
  	return 4 * this.width;
  }
  getLength:function(){ // 错误的形式
  	return 4 * this.width;
  }

typeof与instanceof的区别

注意 typeof 可以判断数据类型,唯独null比较特别,typeof null === 'object' typeof function(){} === 'function'

  • 这两个在判断函数的参数时会有用到
  • typeof主要是用于判断一个东西对应的是JS 7种数据类型中的哪一种
  • 当你要判断的这个参数是object类型时 比如 arr fn obj等等
    • 就请你 arr instanceof Array 这样的方式去判断
    • fn instance of Function
    • obj intance of Object

typeof返回值为字符串

  • 特例是typeof null // 为"object"而不为null
  • 在所有typeof结果中能返回"object"的实在太多了,因此这个typeof局限性很大,判断不精准是哪一种对象

instanceof用以比较真假

  • 本质是console.dir(anyObj)以查看其结构,然后你一层一层拨开--proto--以观察原型链
  • 比如上面详解过的Function instanceof Object为true是因为Function.__proto.proto === Object.prototype

typeof 小例子

let hashmap=[{name:'ryan',no:1},{name:'leo',no:2}]
let string=JSON.stringify(hashmap);
console.log(typeof string); // string
console.log(typeof hashmap); // object