原型和原型链

939 阅读5分钟

一、前奏:面向对象

创建对象的方式:

  1. 简写方式,直接通过语法糖简写 var a = {}
  2. 通过new Object创建 let a = new Object()

关于Object:

可以看出Object是一个函数,之所以可以用new Object创建对象是因为,Object的本质是一个构造函数,构造函数和普通的函数没有本质区别,只是可以用来构造一些东西。

二、对象和构造函数

写一个构造函数:

  1. 内部用this指代需要生成的实例化对象
  2. 使用new实例化对象
  3. 函数的名称需要首字母大写
function Foo(a) { 
    this.a = a;
    this.b = a;
    this.say = function () {
        console.log("哈哈哈")
    }
}
var obj = new Foo(3);
obj;//Foo {a: 3, b: 3, say: ƒ}
obj.say();//哈哈哈

构造函数和实例化对象的关系是什么?

——构造函数是妈,构造函数实例化的对象是儿子。

因为JavaScript是没有类的。只有基于原型。在构造函数中通过new关键字可以生成对象,和基于类的语言非常类似,所以我们称构造函数也是一个类。

instanceof用于判断一个变量是否是某个对象的实例

obj instanceof Foo;// true

Object是对象,同时也是构造函数。

Object instanceof Object;//true
Object instanceof Function;//true

任何对象都可以看作是Object的实例。

三、constructor构造器

任何(实例化)对象拥有一个属性constructor,构造器。它指向当前对象的构造函数。

function Person(a) {
    this.name = a;
}
var p = new Person("张三");

如果没有修改Person的原型,p.constructor===Person为true

如果修改了Person的原型,p.constructor是Object,不是Person

Person.prototype = {
    say: function () {
        //通过this访问实例化对象的属性
        console.log(`我的名字是${this.name}`)
    }
}
// 这一步的本质是通过new Object(say())创建一个对象赋值给Person.prototype
// 所以Person.prototype.constructor === Object
var p2 = new Person("李四");
p2.say()// 我的名字是李四

p2.constructor === Person // false
// 修正constructor的指向,修正之后访问constructor就可以知道实例化对象的构造函数
Person.prototype.constructor = Person
p2.constructor === Person // true

函数也是一个对象,也有构造函数

Person.constructor === Function;//true
Function.constructor === Function;//true
Object.constructor === Function;//true

可得:

Function构造了Person构造函数

Function构造了Function

Function构造了Object

四、原型(构造函数的基因)

只有构造函数拥有原型 prototype,可以直接访问修改,相当于函数的基因,可以让实例化对象使用的公用的方法。

let proto = {
    say: function () { 
        console.log('say')
    },
    walk: function () { 
        console.log('walk'this === obj2)
    }
}
Foo.prototype = proto;
var obj2 = new Foo(5); 
console.log(obj2)// Foo {a: 5, b: 5, say: ƒ}
// 实例化对象的基因(proto 隐式原型)
obj2.__proto__ ; // {say: ƒ, walk: ƒ}
obj2.walk(); // "walk,true"
console.log(obj2.__proto__ === Foo.prototype)// true
console.log(obj.__proto__ === obj2.__proto__)// true
// 构造函数的原型就是实例化对象的隐式原型

obj2通过 __proto__ 继承继承了母亲(Foo)的原型(prototype),于是,我们就把实例化对象和构造函数关联到一起了。通过 __proto__ 可以访问到构造函数原型的方法。

function Foo(){
    this.name = "Foo构造函数"
}
Foo.prototype.say = function(){
    console.log(this.name);
}
let fo1 = new Foo();
fo1.say();//函数存在并执行,访问了fo1构造函数原型上的方法

new关键字做了什么??

function Foo(){}
//let o = new Foo();等价于做了以下
o = new Foo();//新建一个对象,此时o.__proto__ === Object.prototype
o.__proto__ = Foo.prototype;//修改对象原型指向
Foo.call(o);//将this指向o执行

隐式转化原理:

原理: {}调用了隐式原型__proto__即Object.prototype中的toString()方法转换成"[object Object]"

原理: 根据原型链,调用原型的时候,对象里面有toString()方法优先调用对象里面的

Function,所有函数的构造函数。

let fo1 = () => {};
fo1.__proto__ === Function.prototype;//true
let fo2 = function(){};
fo2.__proto__ === Function.prototype;//true
function fo3(){}
fo3.__proto__ === Function.prototype;//true

访问对象的构造函数 constructor

let o = {};
o.constructor === Object;// true

任意函数都是由Function构造来的。 Object是构造函数,所以

Object.__proto__ === Function.prototype;//true
//甚至
Function.__proto__ === Function.prototype;//我构造了我自己!

任意对象都是由Object构造出来的。

let o1={};
o1.__proto__ === Object.prototype;//true

构造函数Object的原型对象的构造器是他自己

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

Function是由Function构建出来的,Object是由Function构建出来的。Object的原型对象是由自己构建出来的

五、原型链

每一个对象都有一个原型对象,对象以原型为模板,继承原型的方法和属性。原型也可以拥有原型,也继承方法和属性,一层一层,这个关系就叫原型链。

首先这个JavaScript的世界里面有个Function,是上层语言设计出来的。上层语言的Function生成了一个Object并且将Object的原型修改成了自己。

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

然后Object生成了Function的原型。

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

那么先有鸡还是先有蛋?Object是由Function构造出来的

观察:

Function.__proto__ === Function.prototype;//Function自己创造了自己
Function.constructor === Function;
Function.prototype.__proto__ === Object.prototype;//Function的原型的构造函数是Object
Object.__proto__ === Function.prototype;//Object是由Function构造的
Object.prototype.constructor === Object;//原型的构造函数是他自己!
Object.prototype.__proto__ === null;//万物之源!

于是得出以下上帝造人的故事。

  1. Object.prototype是凭空出来的(是上层对象创建的),Object.prototype的构造函数指向了自己。
  2. 根据Object.prototype生成了Function构造函数的原型
  3. Function就由此产生,并且它自己的构造函数就是自己
  4. Function生成了Object构造函数。
  5. Object生成了万物,包括构建了(window对象和document)

对象身上属性的访问顺序:

先在对象身上查找,如果没有就查找隐式原型,如果还是没有就去隐式原型里面的隐式原型查找,直到找到Object的原型。这个查询顺序就是原型链,这也就是为什么每个对象都有toString方法的原因了。因为Object.prototype上有这个方法。

Object.prototype对象

拓展:

生成一个对象,函数内部的this会变成这个对象。

var obj = Object.create()//创建一个新的对象,接收一个参数
var obj2 = Object.create(null)  //{} 无中生有,不适用于Object原型链的规则
obj2.__proto__ //undefined create创建的对象没有任何参数方法
obj2.__proto__ = Person.prototype //关联原型
Person.call(obj2)//通过call关联this
obj2.say()//报错
obj2.__proto__.say() //__proto__是属性不是原型

var obj3 = Object.create({});//除了null,传入其他的内容都适用原型链查找规则
obj3.__proto__ // 有参数