构造函数、原型、原型链

构造函数、原型、原型链 原型和原型链应该是前端的面试中,必定会问到的东西吧。但是这一块很多小伙伴们都会绕不明白,今天我们就一起来深入了解一下原型和原型链吧。
要说原型的话,我们就要先来知道构造函数是一个什么东西。
构造函数

定义:通过 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); // trueconsole.log(Function.constructor); //Function () {} (函数对象)// 这个时候创建一个实例化对象foo var foo = new Foo(); console.log(foo.__proto__); //Foo.prototypeconsole.log(Foo.prototype.__proto__); //Object.prototypeconsole.log(Object.prototype.__proto__); //unllconsole.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

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

    推荐阅读