搞明白 JavaScript 原型与原型链

写在前面

  • 所有引用类型(函数,数组,对象)都拥有__proto__属性(隐式原型)
  • 所有函数拥有prototype属性(显式原型)(仅限函数)
  • 原型对象:拥有prototype属性的对象,在定义函数时就被创建

原型prototype

1. 原型 prototype

prototype就是英语“原型”的意思。原型是构造函数创建对象的原始模型

原型的特点
原型也是对象,原型是函数对象的一个属性
原型自带 constructor 属性, constructor 指定构造函数
构造函数创建出的对象会继承原型的属性和方法

// 任何函数都有原型,只是原型对于普通函数没什么大用,但对于构造函数用处极大
function fun(){
    console.log("您好")
}

console.log(fun.prototype);
console.log(typeof fun.prototype);

在JavaScript中,任何一个函数,都有一个prototype属性,指向一个对象。打印prototype属性,你会发现这个属性指向一个空对象。打印prototype的类型,发现是object类型。

2. 原型对象与实例

一个函数的原型,对于普通函数来说,没任何作用。但是如果函数是一个构造函数,那么函数的原型,用处极大!

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

        Person.prototype = {
            major :'计算机科学与技术',
        }

        let why = new Person('高欢',18,'男');

        console.log(why.name); //高欢
        console.log(why.age);  //18
        console.log(why.sex);  //男
        console.log(why.major); //'计算机科学与技术'

why 就是构造函数 Person 的实例化对象.

有一个很奇怪的现象,那就是我们明明没有给 Person 构造函数添加 major 属性,那为什么我们可以通过 Person 的实例化对象 why 访问到这个属性呢???

这个时候我们就要了解 原型 的概念, 在前面我们已经提到每一个构造函数都有一个 prototype 属性,指向一个一个对象 ,这个对象就是构造函数 Person 的原型.

简单来说就是构造函数通过prototype属性访问到的对象就是这个构造函数的原型.

打印Person.prototype 得到如下的结果:
在这里插入图片描述
当一个对象被 new 出来的时候,不仅仅执行了构造函数里面的语句,我们的感觉,构造函数的原型中,所有的属性也加给了这个对象,因此我们通过 why 访问到 major 属性.

3. 实例对象的__proto__属性

当一个对象被new出来的时候,不仅仅执行了构造函数里面的语句,也会把这个函数的__proto__指向构造函数的 prototype

        // 构造函数
        // 构造函数里面没有任何语句,也就是说,这个构造函数在执行的时候,不会给创建出来的对象添加任何属性
        function Person() {

        }
        //构造函数的原型,我们更改了构造函数的原型,为一个新的对象:
        Person.prototype = {
            name:'why',
            age : 18,
            sex : '女',
            major :'计算机科学与技术',
        }

        //当一个对象被new出来的时候,不仅仅执行了构造函数里面的语句,也会把这个函数的__proto__指向构造函数的prototype。
        let why = new Person();
        //当我们试图访问sex、age属性的时候,身上没有。那么就去查找原型,原型身上有,就当做了自己的属性返回了。
        console.log(why.name);         //高欢
        console.log(why.age);            //18
        console.log(why.sex);            //男
        console.log(why.major);        //'计算机科学与技术'

        console.log(why.__proto__);
        console.log(why.__proto__ === Person.prototype)   //true

任何一个对象,都有__proto__属性,这个属性是Chrome自己的属性,别的浏览器不兼容,但是别的浏览器也有原型对象,只不过不能通过__proto__进行访问而已。 这是属性指向自己的原型对象。

每个实例化对象身上都有一个 _proto_ 属性,这个属性指向了实例化对象的原型对象.

简而言之,通过构造函数实例化出的对象都有__proto__ s属性,t通过这个属性访问到的对象就是这个实例化对象的原型对象

打印why.__proto__,结果如下:
在这里插入图片描述
仔细一看,咦,事情不对,这打印的结果咋和之前Person.prototype打印的结果那么像呢.
我们执行下面的代码来观察:

        console.log(Person.prototype.constructor);
        console.log(why.__proto__.constructor);
        console.log(why.__proto__.constructor === Person.prototype.constructor)

可以看到 Person.prototype 的打印结果与why.__proto__的结果是相同的.比较的结果为 true
在这里插入图片描述
所以说他们二者的结果本质上是相同的

但是我们把构造函数的 prototype 属性打印得到的对象称为构造函数的原型
把实例化对象的 _proto_ 属性打印得到的对象称为实例化对象的原型对象

这样做有利于我们更好的区分二者

用下面的一张图来解释三者之间的关系:
在这里插入图片描述

称 {constructor:Person} 为构造函数的原型
称 {constructor:Person} 为实例化对象的原型对象
Person 的实例化对象 why 的原型
why 是构造函数 Person 的实例化对象

说着么半天.其实就是了 2 个对象和一个函数的故事。任何一个函数都有原型,原型是一个对象,用prototype来访问。

当这个函数是构造函数的时候,new出来的对象,它们的原型对象就是这个构造函数的原型。(任何普通函数只要通过new操作符调用就可以称之为构造函数)

prototype我们称为“原型”,只有函数有原型

__proto__我们称为“原型对象”,任何对象都有原型对象。

2. 原型链机制

1. 原型链的本质

只要是对象,一定有原型对象,就是说只要这个东西是个对象,那么一定有__proto__ 属性。

我们想实例的原型对象也是一个对象,那么我们迫切的想看看这个原型对象的原型对象.如果这理论没有问题的话,这个原型对象应该也有 __proto__ 指向他的原型对象吧.
这句话很绕,但是如果你已经理解了什么是原型对象的话理解这句话也不会有很大的难度.

function People(){
    
}
var xiaoming = new People();
// 原型对象的原型对象
console.log(xiaoming.__proto__.__proto__);
// 原型对象的原型对象的构造函数是谁
console.log(xiaoming.__proto__.__proto__.constructor); //Object

// 那我们看看还能不不能再向上查原型对象
console.log(xiaoming.__proto__.__proto__.__proto__);
// 结果为null

也就是上面的一句话错了

世界上只有一个对象没有原型对象,这个对象就是Object.prototype。

2. 原型链查找

当我们试图访问一个对象身上的属性的时候,如果这个对象身上有这个属性,则返回它的值。如果它身上没有这个属性,那么将访问它的原型对象,检测它的原型对象身上是否有这个值,如果有返回它原型对象身上的这个值。

根据上面的一张图就能想明白一些事情,一个对象天生带一些属性和方法,其实并不一定是你创建这个对象时就有的,很大的可能,是这个方法或属性是在这个对象的原型链上的某个对象所拥有的.

这就跟作用域有些相似之处了,当我们在某处使用某个变量时,会先从当前作用域查找,如果有则使用,如果没有,则往上一层作用域查找,以此类推,如果直到全局作用域还没找到,就会报错.
在这里插入图片描述
比如:

function People(){
    
}
var xiaoming = new People();
// 小明马上可以调用toString()方法
console.log(xiaoming.toString());
// 打印 "[object Object]"

// 这个方法是小明原型对象的原型对象身上的方法,我们可以看下
console.log(xiaoming.__proto__.__proto__);

Object是一个函数,是系统内置的构造函数,用于创造对象的。Object.prototype是所有对象的原型链终点。
所以,当我们在一个对象上打点调用某个方法的时候,系统会沿着原型链去寻找它的定义,一直找到Object.prototype

Object.prototype是所有对象原型链的终点,现在我强行给它添加一个属性

        Object.prototype.sayName= function() {
                alert(`我叫${this.name},今年${this.age}岁了`);
        }
        why.sayName();
        // 岂止xiaoming能sayHello 数组也能sayHello
		var arr = [1,2,3];
		arr.sayHello();

		// 然后世界上一切都能sayHello了
		"么么哒".sayHello();

Object.prototype是所有对象的原型链的终点,所以我们直接给Object.prototype增加一个方法,那么世界所有的对象都能调用这个方法.

猜你喜欢

转载自blog.csdn.net/lhrdlp/article/details/106259275