构造函数、原型、原型链

构造函数、原型、原型链

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

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

构造函数

定义:通过 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

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

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,919评论 6 502
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,567评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,316评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,294评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,318评论 6 390
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,245评论 1 299
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,120评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,964评论 0 275
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,376评论 1 313
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,592评论 2 333
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,764评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,460评论 5 344
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,070评论 3 327
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,697评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,846评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,819评论 2 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,665评论 2 354