构造函数、原型、原型链

构造函数、原型、原型链

原型和原型链应该是前端的面试中,必定会问到的东西吧。但是这一块很多小伙伴们都会绕不明白,今天我们就一起来深入了解一下原型原型链吧。

咳咳,第一次写,大佬轻喷

要说原型的话,我们就要先来知道构造函数是一个什么东西。

构造函数

定义:通过 new一个 函数名,来实例化对象的函数就叫做构造函数。

注意:构造函数在定义的时候,必须使用大驼峰命名方式,比如:CreateHuman

一、构造函数和普通函数的区别
  • 构造函数需要用 new 来创建,而普通函数不需要。
    构造函数:
        function Person() {
            this.name = '小柒'
        };
        var p = new Person();
    
    普通函数
        function person() {};
        var p = person();
    
  • this指向,在构造函数的内部,this指向的是构造出来的那个对象;而普通函数的this指向的是window全局对象。
    构造函数:
        function Person() {
            this.name = '小柒'
        };
        var p = new Person();
        console.log(p.name); // 小柒
        这个时候的this就是p
    
    普通函数:
        function person() {
            return this;
        };
        var p = person();
        console.log(p); // window
    
  • 构造函数默认return this,也就是新的实例对象;而普通函数默认返回的是undefined;要是设置了return的值的话,那么返回值会根据return的值类型来决定了。

    如果return的是五种简单的数据类型NumberStringBooleanNullUndefined的话,构造函数还是返回this对象,而普通函数会返回return后面的值。
    如果return的是引用类型的数据类型ArrayDateObjectFunctionRegExpError的话,构造函数和普通函数都会返回return后面的值。

二、构造函数的两种形式

构造函数可以分为两种,一种是系统自带的构造函数,另一种是自定义的构造函数,接下里我们就来看看这两种构造函数吧。

  • 系统自带的构造函数

    new Objectt() new Array() new Number() new Boolean() new Date()

    系统自带的构造函数Object()可以批量的生产出对象,每一个对象都一样,但是彼此之间是相互独立的

    Object()前面加上一个new,变成new Object()的执行,通过return返回一个真正的对象,然后在拿一个变量来接收。如:var a = new Object()

    注:var obj = {}var obj = new Object()这样写区别不大

  • 自定义的构造函数

    Object.create(proto, [propertiesObject])创建一个新的对象,使用现有的对象来提供新创建的对象proto

    • 参数
      • proto:必须,表示新建对象的原型对象,也就是说该参数会被赋值到目标对象的原型上。该参数可以是null对象、函数的prototype属性。 > **注:**创建空对象的时候,要填null,否则会报类型错误。

      • propertiesObject : 可选。 添加到新创建对象的可枚举属性,对象的属性描述符以及相应的属性名称。这些属性对应Object.defineProperties()的第二个参数。

三、构造函数的内部原理

注: 接下来的前提必须是new之后的,而且下面的三步都是隐式的。

  1. 在函数体的最前面隐式的添加上var this = {}空对象(但这个不是空对象,里面有什么我在后面揭晓)
  2. 执行this.xxx = xxx
  3. 隐式的返回return this
我们看看下面的这两个例子,应该就可以明白了
例子1:
    function Student (name, age, sex) {
		//一、隐式的创建this对象
		// this = {
		// 	name: " ",
		// 	age: " ",
		// 	sex: " ",
		// }
		//二、执行this.XXX = XXX;
		this.name = name;
		this.age = age;
		this.sex = sex;
		//三、隐式的返回  return this;
	}
	var student1 = new Student('zhangsan', 20, 'male');
	console.log(student1);// {name: "zhangsan", age: 20, sex: "male"}
	
例子2:
    function Person (name, height) {
	    //隐式的var this = {}, 下面正常的执行this
	    this.name = name;
	    this.height = height;
	    this.say = function () {
	    	console.log(this.say)   //这里的this和外面的this不一样
	    }
	    //return this
    }
    console.log(new Person('xiaowang', 170).name);  //xiaowang

原型

说了这么久,终于讲到了原型,我们我们就来看看什么是原型吧。

定义:原型是function对象的一个属性,他定义了构造函数制造出的对象的公有祖先。通过该构造函数产生的对象,可以继承原型的属性和方法。并且原型也是对象噢~

利用原型可以做什么事呢?

利用原型的特点和概念, 可以提取共有属性

看下面的这个例子你就知道是什么意思了

//第一个应用,在工厂里面生产每个都一样的时候,我们可以把共有的属性给提供出来,放到原型里面
function Car (color, owner) {
    this.color = color;
    this.owner = owner;
}
//Car.prototype 刚出生的时候就已经定义好了
Car.prototype = {
    name: "BMW",
    height: 1400,
    lang : 4900
}
var a = new Car('red', 'prof.Ji'); // Car {color: "red", owner: "prof.Ji"}
var b = new Car('black', 'prof.Deng'); // Car {color: "black", owner: "prof.Deng"}
//看上去a和b的属性是这样的,但是真正要用的时候:
console.log(a.name); //BMW
console.log(b.name); //BMW

上面的这个例子,每个对象都会有一些一样的属性(我把这样的属性叫做工程化属性),我们把这些工程化属性放在原型里不是更好嘛。

一、原型的增、删、改、查
Person.prototype.LastName = 'Qi';
function Person(name) {
    this.name = name;
}
var a = new Person('Xiao');

我们就根据这上面的代码当作增删改查的例子


  • Person.prototype这样就会返回一个对象,里面有原型的所有属性

    //返回结果
    {LastName: "Qi", constructor: ƒ}
    

  • 要是想在原型链上修改的话,比如说我们把Person.prototype.LastName修改一下,需要这样Person.prototype.LastName = 'XiaoQi'
    但要是a.LastName = 'XiaoQi'这样修改的话,那么这个就不是在原型链上修改了,而是在a自己的身上增加了一个LastName='XiaoQi'属性,原型链上的LastName还是Qi

    :原型上想要修改自己的属性,除非Person.prototype.LastName = 'XiaoQi'这么来修改,要是想通过对象来改原型的东西,那么基本是不可能的。


  • 和改的原理类似,除非是调用Person.prototype.xxx来增加属性,比如:

    Person.prototype.age = 18
    //那么我们再来看看原型上的属性
    console.log(Person.Prototype);// {LastName: "Qi", age: 18, constructor: ƒ}
    

  • delete Person.xxx 这样的话是可以删除原型上的属性的,但要是delete a.age的话,虽然返回的是一个true,但是你在访问a.age的话,还是会返回18。虽然他返回的是true,那是以为你想删除一个你没有的属性,那么电脑当然是同意的啦,所以返回的是true

下面我们来介绍一下原型的几个属性吧
  • prototype (原型对象)

    定义:prototype属性是函数独有的,它的含义是函数的原型对象,也就是这个函数所创建的实例的原型对象;这个属性是一个指针,指向一个对象。

    用处:包含所有实例共享的属性和方法

  • constructor (构造器)

    定义:在这个原型的内部,系统自带了一个属性叫做constructor

        function Car () {}   
        var a = new Car();
        console.log(a.constructor)  //function Car() {}
    

    在上面这个构造函数中,aconstructor就是Car

    作用:让构造函数构造出的所有对象,想找它的构造器可以找到。就好比,你的爸爸给你写了一个你家的家庭地址,当你出去玩想回家的时候,可以通过你爸爸写给你的地址回到家。这个constructor就可以看作是你爸爸给你写的家庭地址。

    constructor是可以被人为的修改的
    如:

        function A() {}
        function B() {}
        A.prototype = {
            constructor: B
        }
        var a = new A()
        console.log(a.constructor); //B() {}
    
  • __proto__

    我们先来看看这个例子

    Person.prototype.name = 'abc';
    function Person () {}
    var a = new Person();
    console.log(a.__proto__);
    //打开这个对象▼Object
    //				name: "abc"
    //			  ▶constructor: ƒ Person()
    //			  ▶__proto__: Object
    

    从上面这个例子就可以看出来,这个__proto__里面放的就是原型。

    那么这个__proto__到底是哪里来的呢?或者说这个__proto__放在原型里面有什么用呢?

    在这里,我们就要给构造函数的内部原理填上一个坑了。在构造函数内部原理new之后的第一步,是会在里面隐式的创建一个var this = {}的一个类似空对象的对象,其实这个对象它不是空的,里面本来就有这个__proto__属性,指向的是Person.prototype

    那么这个__proto__到底有什么用呢?

    当你访问这个对象的属性的时候,这个对象如果没有这个属性的话,它就会通过__proto__指向的索引,去找这个哥们身上有没有你想要的那个属性,它相当于一个链接的关系,把原型和自己连接在一起,假如你现在访问a.name,他会现在自己的身上找有没有name这个属性,如果没有的话,他就会沿着__proto__指向的地方去找它身上有没有这个属性。

    为什么自己身上没有会去找原型呢?

    因为__proto__里面存放的就是这个对象的原型。

现在我们来看几个例子
  • 例一

    Person.prototype.name = 'sunny';
    function Perosn () {};
    var a = new Person();
    Person.prototype.name = 'cherry';
    console.log(a.name); //cherry
    

    解释:现在访问的这个name属性,自己的身上没有,就沿着__proto__的指向找。它的__proto__指向的是Person.prototype,现在又把Person.prototype的值给修改了,那么访问的肯定是修改后的值啦。

  • 例二

    Person.prototype.name = 'sunny';
    function Person() {};
    var a = new Person();
    Person.prototype = {
        name : 'cherry',
    };
    console.log(a.name); // sunny
    

    为什么输出的是sunny呢?
    首先我们来搞清楚Person.prototype.xxxPerson.prototype = {}有什么不一样!

    • Person.prototype.xxx是在原来的基础上把属性给修改了。
    • Person.prototype = {}是把原型给修改了,换了一个新的对象。

    解释:上面的是在new之后,发生了一个过程:var this= {__proto__: Person.prototype}, 然后a的__proto__Person.prototype,然后__proto__Person.prototypePerson.prototype是一个人,也就是Person指向的空间,当a构建完成之后,Person.prototype把自己的空间转移了,但是__proto__所指向的没有改变,还是原来的,原来空间的 namesunny,所以当访问a.name的时候,返回的还是sunny

  • 例三

    Person.prototype.name = 'sunny';
    function Person () {}
    Person.prototype = {name : 'cherry'};
    var a = new Person();
    console.log(a.name); //cherry
    

    为什么会这样呢?

    解释:函数的提升,让后面的Person.prototype = {name: 'cherry'}把前面的Person.prototype.name = 'sunny'给覆盖了。因为要new之后才会有隐式的三步,才会在里面创建一个this的类似空对象的属性,里面的__proto__指向的是覆盖后的房间,所以访问a.name的返回结果是cherry。而上面的那个是先new了之后把那个对象给生成后,才修改的,那个时候已经玩了;现在这个是先修改,在创建的对象,所以是cherry

原型链

定义: 在原型上面加一个原型再加一个原型,这样的一个方法,把原型形成链,访问顺序也是按照链的顺序,像作用域链一样的去访问东西,叫做原型链。原型链的连接点就是__proto__,访问的顺序都是先近后远的查。

当了解了构造函数和原型之后,我们理解原型链的话就更加的简单了。我们先看看下面这个代码

// 首先我们创建一个构造函数  
function Foo () {}

//这个Foo构造函数的原型是Foo.prototype

//这个Foo构造函数的__proto__指向的是Function.prototype
console.log(Foo.__proto__ === Function.prototype); // true

console.log(Function.constructor); //Function () {} (函数对象)

// 这个时候创建一个实例化对象  foo
var foo = new Foo();


console.log(foo.__proto__); //Foo.prototype

console.log(Foo.prototype.__proto__); //Object.prototype

console.log(Object.prototype.__proto__); //unll

console.log(Object.prototype.constructor); //Object (){} (函数对象)

下面我们就用图来好好的捋一下思路吧
在这里插入图片描述

我们在做几道题目来巩固一下原型链吧
  • 例一
Person.prototype = {
    name: 'a',
    sayName: function () {
        console.log(this.name);
    }
};
function Person () {
    this.name = "b";
};
var c = new Person();
console.log(c.name); // b
console.log(Person.prototype.sayName); // a

  • 例二
Person.prototype = {
    height: 100
};
function Person () {
    this.eat = function () {
        this.height ++;
    }
}
var a = new Person();
a.eat(); // 101

今天就先到这里吧,后面我会继续补充的,第一次写文章,请大佬轻喷~

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值