精华内容
下载资源
问答
  • 几天没有更新JS灵魂之的专栏系列文章了,秋招季,也忙着备战笔试面试。今天得空再来写一篇文章,本篇要讲解的内容是关于 原型和原型链基础 ,那今天这篇看能不能倒你了,一起来探索一下吧。 仰望星空的...

    引言

    有几天没有更新JS灵魂之问的专栏系列文章了,秋招季,也在忙着备战笔试面试。今天得空再来写一篇文章,本篇要讲解的内容是关于 原型和原型链基础 ,那今天这篇看能不能问倒你了,一起来探索一下吧。

    仰望星空的人,不应该被嘲笑

    原型基础

    原型 prototype 其实是 function对象的一个属性,但是打印出来结果它也是对象。


    那我们直接看下面这个例子吧

    function Foo(name,age){
      this.name = name;
      this.age = age;
    }
    Foo.prototype.sex = '男士'
    
    var foo = new Foo('Chocolate',21);
    console.log(foo.name); // Chocolate
    console.log(foo.sex); // 男士
    

    拓展:prototype是定义构造函数构造出的每个对象的公共祖先,所有被该构造函数构造出的对象都可以继承原型上的属性和方法。

    原型的作用,如上述代码一样,将一些配置项写在构造函数里,对于一些写死的值或者方法,就可以直接挂载到原型上去,可以减少代码冗余。

    知识点补充:

    实例的 __proto__其实就是一个容器,就是为了在对象里面给 prototype设置一个键名。

    来一道简单题吧,会输出什么?

    function Car() { };
    Car.prototype.name = 'Math';
    var car = new Car();
    Car.prototype.name = 'Benz';
    console.log(car.name);
    

    答案是 Benz,相当于进行了一次覆盖操作。

    现在,我进行一点点修改,看看又会输出什么?

    Car.prototype.name = 'Benz';
    
    function Car() { };
    var car = new Car();
    
    Car.prototype = {
      name: 'Math'
    }
    console.log(car.name);
    

    答案是 Benz,实例化一个car对象,首先car.name先去找构造函数找对应 name 属性,没有找到,然后就去原型对象上去找,找到对应name值为 Benz,赋值。继续往下走,发现有对原型对象重定义的操作,但是此时实例对象早就通过原本构造函数 new出来了。(简单来说,就是再定义了一个 prototype,但是没有实例化)

    可能不太好理解上述表达,我们对上述代码修改一丢丢,看看又会打印什么?

    Car.prototype.name = 'Benz';
    
    function Car() {};
    Car.prototype = {
      name: 'Math'
    }
    var car = new Car();
    
    console.log(car.name);
    

    答案是 Math,因为你此时重新定义了构造函数的 prototype,并且进行了实例化。

    可能你会想到这个例子,这里只是更改了属性,并不是重写

    function Car() { };
    Car.prototype.name = 'Math';
    var car = new Car();
    Car.prototype.name = 'Benz';
    console.log(car.name);
    

    原型链基础

    下面我们就要开始讲解原型链相关了,直接看下面这个例子吧:

    Professor.prototype.tSkill = 'Java';
    function Professor(){}
    var progessor = new Professor();
    
    Teacher.prototype = progessor;
    function Teacher(){
      this.mSkill = 'js';
    }
    var teacher = new Teacher();
    
    Student.prototype = teacher;
    function Student(){
      this.pSkill = 'html';
    }
    
    var student = new Student();
    
    console.log(student);
    

    原型链就是像如下例子,沿着 __protp__这条线往上找相应的原型的属性值的链条,这就是原型链。

    补充,原型本身也有原型,但是原型链不可能一直链接,因此,会有一个顶端。原型链的顶端Object.prototype。因为Object也是有原型的。并且 Object.prototype保存了一个 toString()方法。

    继续,我们对上述代码进行一点修改,然后我们修改student实例对象里面的属性值,看是否teacher实例对象也会发生变化?

    Professor.prototype.tSkill = 'Java';
    function Professor(){}
    var progessor = new Professor();
    
    Teacher.prototype = progessor;
    function Teacher(){
      this.mSkill = 'js';
      this.success = {
        alibaba: '28',
        tencent: '30'
      }
    }
    var teacher = new Teacher();
    
    Student.prototype = teacher;
    function Student(){
      this.pSkill = 'html';
    }
    
    var student = new Student();
    student.success.baidu = '100';
    student.success.tencent = '50';
    console.log(teacher,student);
    

    结果:

    上述问题明白之后,我们再来看看下面这道题,看看又会有什么变化?

    Professor.prototype.tSkill = 'Java';
    function Professor(){}
    var progessor = new Professor();
    
    Teacher.prototype = progessor;
    function Teacher(){
      this.mSkill = 'js';
      this.students = 500;
    }
    var teacher = new Teacher();
    
    Student.prototype = teacher;
    function Student(){
      this.pSkill = 'html';
    }
    
    var student = new Student();
    console.log(student.students);
    student.students++;
    console.log(student, teacher);
    


    从结果我们发现,只有 student实例对象底下的 students变成了 501,而 teacher 实例对象下面的 students没有变化。因为对于原始值而言, student对象底下没有 students这个属性,于是就会创建一个,然后自加。上一题是拿到了引用地址,于是可以修改,这道题意思和如下代码类似:

    let obj = {
    	name: 'Chocolate'
    }
    obj.age = 21; // obj没有age属性,于是创建一个。
    console.log(obj.age); 
    

    注意,一般不推荐按照如上两种方式修改原型对象上的属性值,后文会详细介绍继承的方式,这里只是抛砖引玉。

    继续,看下一题,一道经典的笔试题:

    function Car(){
      this.brand = 'Benz';
    }
    Car.prototype = {
      brand: 'Mazda',
      intro: function(){
        console.log('我是' + this.brand + '车');
      }
    }
    var car = new Car();
    car.intro();
    

    答案是 我是Benz车,首先new出来一个实例对象,然后访问实例的 intro()方法,发现没找到,于是会沿着原型链往上找,发现存在,然后打印。关键是 this.brand,因为 this会指向这个实例,实例访问的话,会首先访问由对应构造函数实例出来的对象,发现存在,直接打印。

    最后

    文章产出不易,还望各位小伙伴们支持一波!

    往期精选:

    小狮子前端の笔记仓库

    访问超逸の博客,方便小伙伴阅读玩耍~

    学如逆水行舟,不进则退
    
    展开全文
  • 原型与原型链是JavaScript中的核心概念之一,也是初学者的噩梦,同时面试时被到的概率大概80%以上! 通过这个问题,可以考察应聘者对JavaScript基础知识的理解与掌握程度。原型链的知识通过死记硬背是记不住的...

    本文首发于微信公众号「IT自学课堂」,公众号IT:ItSelfStudyClass

    1. 前言

    原型与原型链是JavaScript中的核心概念之一,也是初学者的噩梦,同时面试时被问到的概率大概在80%以上!

    通过这个问题,可以考察应聘者对JavaScript基础知识的理解与掌握程度。原型链的知识通过死记硬背是记不住的,就算今天记住,明天准忘了。

    但是,当你真正理解原型链的概念之后,你会发现,原型链竟然如此简单,而且很难忘记。

    最近两个月都在招人面试,面了有30+,不管对方是初级、高级还是资深级别,这道题必问。但是真正理解原型链的人不到3个。

    很多人能够说准确说出原型链的概念,但是你让他针对具体的实例写出对应的原型链,能写对的人寥寥无几。

    所以今天准备写一篇关于原型链的文章,和大家分享一下我对原型链的理解,如有错误的地方,欢迎大家探讨。

    2. 原型与原型链的概念

    我们来看一下MDN对原型与原型链的解释:

    当谈到继承时,JavaScript 只有一种结构:对象。每个实例对象( object )都有一个私有属性(称之为 __proto__ )指向它的构造函数的原型对象prototype)。该原型对象也有一个自己的原型对象( __proto__ ) ,层层向上直到一个对象的原型对象为null。根据定义,null没有原型,并作为这个原型链中的最后一个环节。

    上面有个概念,我们务必记住:

    • 每个实例对象都有一个私有属性:__proto__ ,该私有属性 指向实例构造函数的原型对象。

    3. 原型与原型链图解

    所谓原型”“,不管是现实中的锁链,还是计算机算法中的链表,他们都是把一个一个节点串联起来,从而形成一条链。

    在JavaScript中,原型链也是如此。原型对象是节点,而私有属性 __proto__ 就是串联连接两个原型对象节点的”线“。

    例如,有一个超类(SupType)和子类(SubType),子类通过原型继承超类,并创建一个子类的实例对象 instance,具体代码如下:

    function SupType() {
      this.supProperty = true;
      this.name = 'SupType';
    }
    
    SupType.prototype.getSupValue = function() {
      return this.supProperty;
    }
    
    function SubType() {
      this.subProperty = false;
    }
    
    SubType.prototype = new SupType();
    
    SubType.prototype.constructor = SubType;
    
    SubType.prototype.getSubValue = function() {
      return this.subProperty;
    }
    
    SubType.prototype.getName = function() {
      return this.name;
    }
    
    const instance = new SubType();

    按照上面我们对“务必记住的概念”的理解,instance 对象的 __proto__ 指向实例(instance)构造函数原型对象:SubType.prototype,图解如下:

    SubType.prototype 是超类 SupType 的实例,也有一个 _proto 属性,所以SubType.prototype.__proto__指向 SubType.prototype 对象构造函数的原型对象:SupType.prototype,图解如下:

    SupType.prototype 也有一个 __proto__ 属性,那 SupType.prototype 对象又是哪个构造函数的实例对象呢?

    答案是 Object,所以 SupType.prototype.__proto__ 指向的是 Object.prototype 对象。那Object.prototype 又是哪个构造函数的实例对象呢?是 null 对象,无中生有,原型链的终点。

    图解如下:

    用代码表达,这条原型链如下:

    instance.__proto__ === SubType.prototype
    
    SubType.prototype.__proto__ ==== SupType.prototype
    
    SupType.prototype.__proto__ === Object.prototype
    
    Object.prototype.__proto__ === null

    4. 其他

    在JavaScript中,引用类型和基本包装类型可以通过构造函数创建实例对象,这些实例的原型链与Object创建实例的原型链类似,最终也是指向 null 对象,原型链完整版图解如下:

    5. 总结

    • 每一个实例对象都有一个私有属性 __proto__ ,该私有属性总是指向实例构造函数的原型对象;
    • 不同的原型对象”节点“通过 __proto__ 指向进行串联连接,从而形成一条原型链。
    • 原型链的终点为 null 对象,即无中生有。

     

    展开全文
  • 众所周知js原型及原型链是很多开发者的一个疼点(我也不例外),我也曾多次被起,也过不少其他,如果自己没有真正的去实践和理解过;那么突然之间要去用最简单的话语进行概述还真不是一件容易的事情; 其实...

    众所周知js原型及原型链是很多开发者的一个疼点(我也不例外),我也曾多次被问起,也问过不少其他人,如果在自己没有真正的去实践和理解过;那么突然之间要去用最简单的话语进行概述还真不是一件容易的事情;
    其实工作中看似神秘的js原型也并不是那么难以理解,最终其目的无非是为了达到方法、属性共享代码重用的目的;在我所了解的编程语言中都会用到object这个最顶层对象,然而生命的法则最终是从无到有,就如同世界先有鸡还是先有蛋一样。

    一、 Class

    先来看一个简单的es6的例子吧

    假如我们定义一个类

    
    /*用于全局状态管理*/
    class State{}
    
    

    然后在某个页面为其State.draw = true 然后在项目的任何地方都能使用State.draw来获取其值;而且当你一个地方更改,其它页面也会同时获取到更改后的值

    
    class State{
    	constructor(draw){
    		this.draw = draw;
    	}
    }
    
    

    然而使用对象属性方式就不一样了;

    
    var state1 = new State('bar');
    var state2 = new State('pie');
    state2.draw = 'hello'; // 不管state2 的draw 怎么改都不会影响到 state1 的属性值
    
    

    为啥扯上了这么一大圈,这个和原型好像没关系,其实是有的,State.draw 就等同于 State.prototype.draw = true
    this.draw = draw;就等同于 普通的函数赋值

     function Sate(){
       	this.draw = draw;
    }
    

    二、普通 function

    
    function Sate(draw){
    	this.draw = draw;
    }
    var state1 = new State('bar');
    var state2 = new State('pie');
    state2.draw = 'hello'; // 不管state2 的draw 怎么改都不会影响到 state1 的属性值
    
    

    使用node运行测试

    C:\Users\Lenovo>node
    > function State(draw){
    ... this.draw = draw;
    ... }
    undefined
    > var state1 = new State('bar');
    undefined
    > var state2 = new State('pie');
    undefined
    > console.log(state1.draw);
    bar
    undefined
    > console.log(state2.draw);
    pie
    undefined
    > state2.draw = 'circle';
    'circle'
    > console.log(state1.draw);
    bar
    undefined
    > console.log(state2.draw);
    circle
    undefined
    >                                                                                                                                 
    

    柑橘和第一个Class案例是有点相同了,在题中state1 和 state2 完全是两个不同的对象,他们各自维护自己的属性和方法和其他人没得关系

    > console.log(state1);
    State { draw: 'bar' }
    undefined
    > console.log(state2);
    State { draw: 'pie' }
    undefined
    >   
    

    三 、原型(prototype)

    function State(){}
    State.prototype.draw = 'pie';
    
    var state1 = new State();
    var state2 = new State();
    
    console.log(state1.draw);
    console.log(state2.draw);
    
    state2.draw = 'bar'; 
    
    console.log(state1.draw);
    console.log(state2.draw);
    
    

    使用node运行测试

    C:\Users\Lenovo>node
    > function State(){}
    undefined
    > State.prototype.draw = 'pie';
    'pie'
    > var state1 = new State();
    undefined
    > var state2 = new State();
    undefined
    > console.log(state1.draw);
    pie
    undefined
    > console.log(state2.draw);
    pie
    undefined
    > console.log(state1); // 看:打印的 state1和 state2 是一个空的 State {} 那上面却能正常打印draw
    State {}
    undefined
    > console.log(state2);
    State {}
    undefined
    >     
    

    看:打印的 state1和 state2 是一个空的 State {} 那上面却能正常打印draw;其实这里的state1 和state2 只是State 的一个引用,在实例本身是没有任何属性的,但是他可以通过自身的__proto__关联到Sate这个构造函数

    
    > console.log(state1.__proto__);
    State { draw: 'pie' }
    undefined
    > console.log(state2.__proto__);
    State { draw: 'pie' }
    undefined
    >   
       
    

    而注意的是这个属性并没有直接关联构造函数;只是关联了构造函数的prototype属性(原型对象)

    
    > console.log(State);
    [Function: State]
    undefined
    > console.log(State.prototype);
    State { draw: 'pie' }
    undefined
    >               
    

    从打印可以得出 State.prototype 就等同于了state2.__proto__

    so state2.proto == State.prototype

    
    > console.log(state2.__proto__ == State.prototype);
    true
    undefined
    >      
    

    四、原型的用途

    然而说了半天,这个原型的用处究竟在哪里呢?
    其实他的主要用途就是 继承(extends),代码重用
    如:

    
    function State(){}
    State.prototype.draw = 'pie';
    State.prototype.drawGraphic  = function(){
    	let height = 100;
    	let width = 100;
    	// draw a graphics...
    	console.log(`Draw a graphic with a height of ${height}px and a width of ${width}px`);
    }
    

    上面定义了绘图的构造函数和方法,现在就可以开始使用了

    
    var state1 = new State();
    var state2 = new State();
    var state3 = new State();
    var state4 = new State();
    
    // 使用实例化的四个实例,调用drawGraphic进行图形绘制
    state1.drawGraphic();
    state2.drawGraphic();
    state3.drawGraphic();
    state4.drawGraphic();
    

    如上:实例化的四个实例,调用drawGraphic进行图形绘制,
    然而他们并没有去创建各自的方法,只是直接从原型引用了State 上的drawGraphic,这样就极大的节约了开销;
    如果不使用原型的方式,这个四个对象将会创建四个对应的方法,这就是一种极大浪费。

    如果不明白可以来看看看开始的例子

    4.1 普通function, 实例化多个对象

    C:\Users\Lenovo>node
    > function State(draw){
    ... this.drawGraphic = function(){
    ..... let height = 100;
    .....   let width = 100;
    .....   // draw a graphics...
    .....   console.log(`Draw a graphic with a height of ${height}px and a width of ${width}px`);
    ..... }
    ... };
    undefined
    > var state1 = new State('bar');
    undefined
    > var state2 = new State('pie');
    undefined
    > console.log(state1);
    State { drawGraphic: [Function] }
    undefined
    > console.log(state2);
    State { drawGraphic: [Function] }
    undefined
    >           
    

    从运行可以看出每个new出来的实例都会创建属于自己的实例方法和属性

    4.2 使用原型方式

    只用从State 上面调用drawGraphic 方法,而不会自己再去创建,就同java 的继承一个意思,直接从父类继承方法,属性,然后使用。看:

    C:\Users\Lenovo>node
    > function State(){}
    undefined
    > State.prototype.drawGraphic  = function(){
    ...     let height = 100;
    ...     let width = 100;
    ...     // draw a graphics...
    ...     console.log(`Draw a graphic with a height of ${height}px and a width of ${width}px`);
    ... }
    [Function]
    > var state1 = new State();
    undefined
    > var state2 = new State();
    undefined
    > console.log(state1);
    State {}
    undefined
    > console.log(state2);
    State {}
    undefined
    > state1.drawGraphic();
    Draw a graphic with a height of 100px and a width of 100px
    undefined
    > state2.drawGraphic();
    Draw a graphic with a height of 100px and a width of 100px
    undefined
    >   
    

    4.3 实例原型重写

    那有的童鞋就会问了你这个只能调一个方法打印同样的图形,太死板了;其实不然,原型也支持重写(override)

    改改刚才的案例

    function State(){}
    State.prototype.draw = 'pie';
    State.prototype.drawGraphic  = function(){
    	let height = 100;
    	let width = 100;
    	// draw a graphics...
    	console.log(`Draw a ${this.draw} with a height of ${height}px and a width of ${width}px`);
    }
    

    使用node运行测试

    C:\Users\Lenovo>node
    > function State(){}
    undefined
    > State.prototype.draw = 'pie';
    'pie'
    > State.prototype.drawGraphic  = function(){
    ...     let height = 100;
    ...     let width = 100;
    ...     // draw a graphics...
    ...     console.log(`Draw a ${this.draw} with a height of ${height}px and a width of ${width}px`);
    ... };
    [Function]
    > var state1 = new State();
    undefined
    > var state2 = new State();
    undefined
    > var state3 = new State();
    undefined
    > state1.drawGraphic();
    Draw a pie with a height of 100px and a width of 100px
    undefined
    > state2.drawGraphic();
    Draw a pie with a height of 100px and a width of 100px
    undefined
    > state3.drawGraphic();
    Draw a pie with a height of 100px and a width of 100px
    undefined
    > state2.draw = 'circle';
    'circle'
    > state1.drawGraphic();
    Draw a pie with a height of 100px and a width of 100px
    undefined
    > state2.drawGraphic();
    Draw a circle with a height of 100px and a width of 100px
    undefined
    > state3.drawGraphic();
    Draw a pie with a height of 100px and a width of 100px
    undefined
    >   
    

    看运行结果:state2将 draw 重新设置为了 circle ;再次调用打印 Draw a circle with a height of 100px and a width of 100px;然而他并没有影响到其他的实例(其实说白了就是在执行state2.draw = 'circle'; 在state2实例对象上新增了一个draw的属性)


    为了能更好理解继承和重写:来用我们小学老师教我的语文解释一哈【

    继承如同你父亲开了一家xxx公司,你就可以直接找财务开个20万,今天要去约个女朋友吃饭,然后财务一看原来是少爷呀,大笔一挥给你了,然后你再开上你父亲的法拉利愉快的约会去了。
    当某一天你发现自己也该干一番事业了,于是开始模仿你父亲建起来了同样的公司和相同的经验模式,也许有人会想干嘛不直接把父亲公司改为自己的?
    那肯定不行啦,因为那样老大,姥二,老三……不会把你打死呀。最后只好自己模拟了个和你父亲相同的xxx子公司,现在出去约会就直接叫自己财务开单了,然后开着自己的法拉利愉快的且。
    假如哪天经验不当(女朋友太多,哈哈),又得去找你父亲的财务了,那就不一样了现在得明确指定是去父亲的财务那里(super.财务)还是自己的财务那里开支票;
    否则你直接给你手下说去叫财务给我20万,他第一反应当然是去找你自己公司的财务了,啊哈哈。】


    C:\Users\Lenovo>node
    > console.log(state2);
    State { draw: 'circle' }
    undefined
    > console.log(state2.__proto__);
    State { draw: 'pie', drawGraphic: [Function] }
    undefined
    >         
    
    

    如果上面啥子重写依然搞不清楚那可以把它看成(虽然不是太准确,为了理解还是可以的)
    在执行state2.draw = ‘circle’;是为该state2实例对象新增了一个draw的属性,

    那你可能会迷惑了打印是为啥会是circle 而不是pie呢,不是说好的state2 的原型指向State.prototype吗,
    State中的draw并没有改变呀,其实问题在于js 属性查找机制(就近原则)

    首先获取属性值会优先从自己对象上面查找,当对象没有该属性才会通过__proto__到原型对象上面去找那个属性;
    假如该State.prototype原型对象上也没有该属性,他会再根据State.prototype.__proto__继续向上,直到Object;直达Object.protopyte.__proto__,最后如果还没有找到对于的属性;那就给你个undefined了

    4.4 原型对象重写

    那你说假如我就是想更改所有对象的draw咋办呢。当然有办法(你想翻天谁都拦不住,,哈哈)

    > state2.__proto__.draw = 'trinagle';
    'trinagle'
    > console.log(state3.drawGraphic());
    Draw a trinagle with a height of 100px and a width of 100px
    undefined
    undefined
    > console.log(state2.drawGraphic());
    Draw a circle with a height of 100px and a width of 100px
    undefined
    undefined
    > console.log(state1.drawGraphic());
    Draw a trinagle with a height of 100px and a width of 100px
    undefined
    undefined
    >      
    

    看到没有state2.__proto__.draw = 'trinagle'一执行,其他state1和state3就瞬间改变了打印为Draw a trinagle ...
    state2没有遭更改因为最开始他就重写了一次(他自己有了自己的子公司)

    五、 原型链

    说了半天没讲到原型链,不地道,其实上面都已经出现过n次了,只是你没注意(世上最遥远的距离是:我就在你眼前你却不认识)哈哈扯远了。

    想想一个实例对象和函数对象是怎么取得联系的,不就是通过__proto__这个属性(这个属性会在函数对象或是普通对象一降生就会自带而来)吗?

    那就对了其实他作用就是建立实例与函数对象之间的一条链子简称原型链

    注意这个__proto__ 是所有人都有(一视同仁);然而prototype这就就不一样了只有函数对象才具备(只有大佬才具备,哈哈)

    C:\Users\Lenovo>node
    > function State(){}
    undefined
    > var state1 = new State();
    undefined
    > console.log(State.prototype);
    State {}
    undefined
    > console.log(state1.prototype);
    undefined
    undefined
    >     
    

    看到了吧。


    5.1 函数对象

    现在来揭开原型链的神秘面纱了
    接着看

    C:\Users\Lenovo>node
    > function State(){}
    undefined
    > var state1 = new State();
    undefined
    > console.log(State.prototype);
    State {}
    undefined
    > console.log(state1.__proto__);
    State {}
    undefined
    > console.log(State.prototype == state1.__proto__);
    true
    undefined
    
    

    从上得出State.prototype == state1.__proto__ 证明 State.prototype也就如同State的一个实例

    佐证一下:每一个实例对象都会默认携带一个constructor属性指向其构造函数,那么原型对象为什么也会有一个constructor属性呢,其实他也是构造函数的一个实例

    undefined
    > console.log(State.prototype.constructor);
    [Function: State]
    undefined
    > console.log(state1.constructor)
    [Function: State]
    undefined
    > console.log(state1.constructor == State.prototype.constructor)
    true
    undefined
    >        
    

    ok State和他的实例之间的爱恨情仇算是基本清楚了吧

    不清楚再简单画一哈:
    State ----------prototype------------------->State.prototype
    函数对象(State {})通过prototype属性指向它的原型对象(State.prototype

    State.prototype ---------constructor----------------> State
    state1---------------------constructor----------------> State
    然而原型对象和实例对象都会有个一个constructor指向其构造函数([Function: State]
    state1 -----------------__proto__·-----------------> State.prototype
    实例对象会通过原型链属性__proto__指向其构造函数的原型对象


    5.2 构造函数

    因为State是通过new function来的所有他是一个构造函数,而State.prototype则是该State的一个实例对象,就是一个普通的函数State {},从运行可以看出

    > console.log(State)
    [Function: State]
    > console.log(State.prototype);
    State {}
    > console.log(state1)
    State {}
    

    看看这个定一个匿名函数

    > var fun3 = new Function();
    undefined
    > console.log(fun3);
    [Function: anonymous]
    undefined
    >   
    

    再回头去看看上面的 function State(){} 定义函数是否理解了呢

    上面说State的实例对象就是一个普通对象,怎么理解,
    在工作中是不是常常会var obj = {};这样来定义一个对象呢,应该都有过吧,这就是定于了一个普通对象,然而State.prototype的原型对象也是一个{}
    这就和好的论证了他是一个普通对象

    > var obj = {};
    undefined
    > console.log(obj.prototype);
    undefined
    undefined
    > console.log(obj.__proto__);
    {}
    undefined
    >   
    

    这里的obj.prototype == undefined也充分证明其只是一个普通对象,只有函数对象才会有prototype

    > console.log(State.prototype);
    State {}
    undefined
    > console.log(State.prototype.__proto__);
    {}
    

    看到这个是否理解了上面说的【函数对象(State {})通过prototype属性指向它的原型对象(State.prototype)】

    5.3 构造函数的由来

    那么构造函数最终来之哪里呢

    > console.log(State.__proto__);
    [Function]
    undefined
    >       
    

    他的原型对象是Function;那么就会问Function的原型对象又是谁呢?(Object)? no 他是他自己。。。

    > console.log(Function.prototype);
    [Function]
    undefined
    >                                                                                                                             
    

    why?
    再来回顾下
    function State(){}的 State.prototypeState 的一个实例那就等同于
    State.prototype = new State();

    以此类推
    Function.prototype 也是Function 的一个实例;那么
    Function.prototype = new Function();

    而 上面曾经定义匿名函数时提到使用new Function()创建的函数就是构造函数
    那么Function.prototype 也是通过new Function()创建的,那么他是不是也该是个构造函数呢,也就是等于了他自己

    那么Function的__proto__就很简单了,
    Function.__proto__ == Function.prototype == Function

    那最后Function.prototype 的原型链对象又指向了谁,不可能还是Function吧?
    那倒不是了。那岂不成死循环了
    所以它依然遵循了万物法则,一切皆从无中来

    > console.log(Function.prototype.__proto__);
    {}
    undefined
    

    也就是说

    Function.prototype.__proto__ == {}
    var obj = {};
    var obj2 = new Object();

    使用node运行测试

    > var obj2 = new Object();
    undefined
    > console.log(obj2.prototype);
    undefined
    undefined
    > console.log(obj2.__proto__);
    {}
    undefined
    >    
    

    是否圆满了所有对象都继承之Object的论题!

    个人理解,有不正确之处,欢迎留言指出,共同学习一起进步

    展开全文
  • 原型链一直都是一个JS中比较让费解的知识点,但是面试中经常会被到,这里我来做一个总结吧,首先引入一个关系图: 一.要理解原型链,首先可以从上图开始入手,图中三个概念: 1.构造函数: JS中所有函数...

    原型链

    原型链一直都是一个在JS中比较让人费解的知识点,但是在面试中经常会被问到,这里我来做一个总结吧,首先引入一个关系图:

    一.要理解原型链,首先可以从上图开始入手,图中有三个概念:

    1.构造函数: JS中所有函数都可以作为构造函数,前提是被new操作符操作;

    function Parent(){
        this.name = 'parent';
    }
    //这是一个JS函数
    
    var parent1 = new Parent()
    //这里函数被new操作符操作了,所以我们称Parent为一个构造函数;
    复制代码

    2.实例: parent1 接收了new Parent(),parent1可以称之为实例;

    3.原型对象: 构造函数有一个prototype属性,这个属性会初始化一个原型对象;

    二.弄清楚了这三个概念,下面我们来说说这三个概念的关系(参考上图):

    1.通过new操作符作用于JS函数,那么就得到了一个实例;

    2.构造函数会初始化一个prototype,这个prototype会初始化一个原型对象,那么原型对象是怎么知道自己是被哪个函数初始化的呢?原来原型对象会有一个constructor属性,这个属性指向了构造函数;

    3.那么关键来了实例对象是怎么和原型对象关联起来的呢?原来实例对象会有一个__proto__属性,这个属性指向了该实例对象的构造函数对应的原型对象;

    4.假如我们从一个对象中去找一个属性name,如果在当前对象中没有找到,那么会通过__proto__属性一直往上找,直到找到Object对象还没有找到name属性,才证明这个属性name是不存在,否则只要找到了,那么这个属性就是存在的,从这里可以看出JS对象和上级的关系就像一条链条一样,这个称之为原型链;

    5.如果看到这里还没理解原型链,可以从下面我要说到继承来理解,因为原型继承就是基于原型链;

    三.new操作符的工作原理

    废话不多说,直接上代码
    var newObj = function(func){
        var t = {}
        t.prototype = func.prototype
        var o = t
        var k =func.call(o);
        if(typeof k === 'object'){
            return k;
        }else{
            return o;
        }
    }
    var parent1 = newObj(Parent)等价于new操作
    
    1.一个新对象被创建,它继承自func.prototype。
    2.构造函数func 被执行,执行的时候,相应的参数会被传入,同时上下文(this) 会被指定为这个新实例。
    3.如果构造函数返回了一个新对象,那么这个对象会取代整个new出来的结果,如果构造函数没有返回对象,
    那么new出来的结果为步骤1创建的对象。
    复制代码

    继承

    一.构造函数实现继承(构造继承)

    function Parent(){
        this.name = 'parent1'
        this.play = [1,2,3]
    }
        
    function Child{
        Parent.call(this);//apply
        this.type = 'parent2';
    }
    Parent.prototype.id = '1'
    var child1 = new Child()
    console.log(child1.name)//parent1
    console.log(child1.id)//undefined
    
    //以下代码看完继承方式2,再回过头来看
    var child2 = new Child();
    child1.play[0] = 2;
    console.log(child2.play)//[1,2,3]
    
    从上面构造继承的代码可以看出,构造继承实现了继承,
    打印出来父级的name属性,但是实例对象并没有访问到父级原型上面到属性;
    复制代码

    二.原型链实现继承

    function Parent(){
        this.name = 'parent'
        this.play = [1,2,3]
    }
    function Child(){
        this.type = 'child';
    }
    Child.prototype = new Parent();
    Parent.prototype.id = '1';
    var child1 = new Child();    
    console.log(child1.name)//parent1
    console.log(child1.id)//1
    
    从这里可以看出,原型继承弥补了构造继承到缺点,继承了原型上到属性;
    但是下面再做一个操作:
    var child2 = new Child();
    child1.play[0] = 2;
    console.log(child2.play)//[2,2,3]
    这里我只是改变了实例对象child1到play数组,但是实例打印实例对象child2到paly数组,发现也跟着变化
    了,所以可以得出结论,原型链继承引用类型到属性,在所有实例对象上面改变该属性,所有实例对象该属性都会
    变化,这样肯定就存在问题,现在我们回到继承方式1(构造继承),会发现构造继承不会存在这个问题,所以
    其实构造继承和原型链继承完全可以互补,由此我们引入第三种继承方式;
    
    额外解释:这里通过一个原型链继承,我们再来回顾一下对原型链的理解,上面代码,我们进行了一个操作:
    Child.prototype = new Parent();
    这个操作把父类的实例赋值给子类的原型,然后结合上面原型链的关系图,我们再来理一下(为了阅读方便,复
    制上图到此处):
    复制代码

    现在我们可以把图中到实例看成child1,首先如果要找child1实例对象中的name属性,那么我首先到Child本身去找,发现没有找到name属性,因为Child函数里面只有一个type属性,那么通过__proto__找到Child的原型对象,而刚才我们做了一个操作:

    Child.prototype = new Parent(); 这个操作把父类的实例给了Child的原型,所以通过这个我们就可以找到父级的name,这就是原型链,一层一层的,像一个链条;

    三.组合继承

    function Parent(){
        this.name = 'parent1'
        this.play = [1,2,3]
    }
        
    function Child{
        Parent.call(this);//apply
        this.type = 'parent2';
    }
    Child.prototype = new Parent();
    Parent.prototype.id = '1'
    var child1 = new Child()
    console.log(child1.name)//parent1
    console.log(child1.id)//1
    var child2 = new Child();
    child1.play[0] = 2;
    console.log(child2.play)//[1,2,3]
    
    从上面代码可以看出,组合继承就是把构造继承和原型链继承组合在一起,把他们的优势互补,从而弥补了各自的
    缺点;那么组合继承就完美了吗?我们继续思考,从代码中可以发现,我们调用了两次Parent函数,一次是
    new Parent(),一次是Parent.call(this),是否可以优化呢?我们引入第四种继承方式;
    复制代码

    四.组合继承(优化1)

    function Parent(){
        this.name = 'parent1'
        this.play = [1,2,3]
    }
        
    function Child{
        Parent.call(this);//apply
        this.type = 'parent2';
    }
    Child.prototype = Parent.prototype;//这里改变了
    Parent.prototype.id = '1'
    var child1 = new Child()
    console.log(child1.name)//parent1
    console.log(child1.id)//1
    var child2 = new Child();
    child1.play[0] = 2;
    console.log(child2.play)//[1,2,3]
    
    我们改成Child.prototype = Parent.prototype,这样就只调用一次Parent了,解决了继承方式3的问题,
    好吧,我们继续思考,这样就没有问题了吗,我们做如下操作:
    console.log(Child.prototype.constructor)//Parent
    这里我们打印发现Child的原型的构造器成了Parent,按照我们的理解应该是Child,这就造成了构造器紊乱,
    所以我们引入第五种继承优化
    复制代码

    五.组合继承(优化2)

    function Parent(){
        this.name = 'parent1'
        this.play = [1,2,3]
    }
        
    function Child{
        Parent.call(this);//apply
        this.type = 'parent2';
    }
    Child.prototype = Parent.prototype;
    Child.prototype.constructor = Child//这里改变了
    Parent.prototype.id = '1'
    var child1 = new Child()
    console.log(child1.name)//parent1
    console.log(child1.id)//1
    var child2 = new Child();
    child1.play[0] = 2;
    console.log(child2.play)//[1,2,3]
    
    现在我们打印
    console.log(Child.prototype.constructor)//Child
    这里就解决了问题,但是我们继续打印
    console.log(Parent.prototype.constructor)//Child
    发现父类的构造器也出现了紊乱,所有我们通过一个中间值来解决这个问题,最终版本为:
    
    function Parent(){
        this.name = 'parent1'
        this.play = [1,2,3]
    }
        
    function Child{
        Parent.call(this);//apply
        this.type = 'parent2';
    }
    
    var obj = {};
    obj.prototype = Parent.prototype;
    Child.prototype = obj;
    //上面三行代码也可以简化成Child.prototype = Object.create(Parent.prototype);
    Child.prototype.constructor = Child
    
    console.log(Child.prototype.constructor)//Child
    console.log(Parent.prototype.constructor)//Parent
    用一个中间obj,完美解决了这个问题复制代码

    转载于:https://juejin.im/post/5c08ba1ff265da612577e862

    展开全文
  • 本来是学习原型模式的,后来发现它就是一个拷贝,然后就去研究了下深浅拷贝以及它们的实现,这里不说大道理,尽量通俗易懂地把它们都讲清楚。 1.引入 :java 对象拷贝的意义何在?为啥要拷贝? 答:因为懒,不想...
  • 1.原型及原型链的问题 这个问题面试的公司都有问。其实这个问题也是好多新手特别犯难的一个问题。对于一些来说原型和原型链要解释得清楚又准确就要把原型这块的知识点看好多遍。OK,直接说我的方法吧,我直接纸...
  • 什么是原型链 然后就约了2号下午面试 4/3 腾讯第一次捞一面 这次面试让感觉像是聊天,轻轻松松地我就没了,本次面试是两位面试官轮流面试 第一个面试官 项目经历 前端知识体系 什么是原型链 做题:leetcode ...
  • 这三儿绝对面试必的东西吧 如果你单单是根据书上的概念,自己冥想,对于前端基础不太好(比如我)的来说,光是一个原型链就可以把你绕的崩溃。 今天这篇文章 就让我们彻底揭开闭包的真面目把! 面试的时候 常常...
  • 今天有人问到了我使用了什么框架:点击打开链接前端的不同框架,像bat里的固定框架。其次了解什么是继承:点击打开链接:1:原型链继承;2:原型式继承;3寄生组合式继承;4组合继承;5:构造函数继承。最后的是...
  • 前些日子群里有人问说了这么一段话引起里我的好奇: 我一直觉得语言层上没有什么好说的,不过不知道大家学习语言的时候有没有像他一样的困惑。 他其实讲的是js对象和Java对象的区别,Java面向对象有继承,封装...
  • 文章目录百度提前批-百度智能小程序1、定位2、z-index 、层叠3、作用域...牛客网看面经时随机找的一个面经,将到的知识点详细介绍概括了一下,一些知识点参见其他博客,希望能帮助到更多,最后大家一起学习进步
  • 其实我们写代码中或多或少会用到一些模式,面试官你设计模式的问题,更多是看你没有总结过。如果一直都是那垒代码,你当然会认为这是个很难的问题。所以我们需要总结一下设计模式。       1....
  • 一般来说很少有人这么使用var xxx = new RegExp(),而是用字面量的方式,比如var xx = /[bc]/gi;像用的比较多的方法有exec用于捕获包含第一个匹配项的数组,没有则返回null。test,...
  • 软件工程教程

    热门讨论 2012-07-06 23:10:29
    删除操作一旦执行,立即被监听器捕获到,进而执行 删除操作前执行自定义的函数体,即判断实体有无undeletable标签,则中断删除操作,无则正常删除。 用例图 关系 关联关系 ;依赖关系 ;泛化关系;关系的...
  • 21天学通C++ (中文第五版)

    热门讨论 2010-06-23 16:57:03
    没有三个星期内就能学好一种严谨的编程语言,但本书每章的内容都可以几小时内阅读完毕。 只需21天,读者就能学习诸如控制输入/输出、循环和数组、面向对象编程、模板和创建C++应用程序等基本知识,所有...
  • 《你必须知道的495个C语言问题》

    热门讨论 2010-03-20 16:41:18
    1.27 我的编译器总报函数原型不匹配的错误,可我觉得没什么问题。这是为什么? 15 1.28 文件中的第一个声明就报出奇怪的语法错误,可我看没什么问题。这是为什么? 15 1.29 为什么我的编译器不允许我定义大数...
  • php高级开发教程说明

    2008-11-27 11:39:22
    一些问题应用开发中是总会涉及到的。 当从事一个专业项目的时候,考虑一下你正在做什么是至关重要的,“了解你的敌人,永远 不要低估它”。尽管你的项目并不是一个真正的敌人,这句话的寓意仍然适用,转向其他...
  • 如果大家任何疑问,可以扫描下面二维码或者直接微信搜索:sun_shine_lyz加我微信进行交流,也可以拉你进技术交流群,和技术大佬们一同交流进阶~~ 我出版的图书 《海量数据处理与大数据技术实战》 《MySQL...
  • C#23种设计模式

    2013-06-02 16:49:43
    不要我“早上碰到MM新做了个发型怎么说”这种问题,自己用BRIDGE组合一下不就行了 桥梁模式:将抽象化与实现化脱耦,使得二者可以独立的变化,也就是说将他们之间的强关联变成弱关联,也就是指一个软件系统的...
  • 责任模式: 责任模式中,很多对象由每一个对象对其下家的引用而接起来形成一条 责任模式 。 请求这个上传递, 直到上的某一个对象决定处理此请求。 客户并不知道上的 哪 一个对象最终处理这个请求...
  • 使 Axios 运行时共享 Vue 原型链的内容,减少了很多指代 Vue 的临时变量 <ul><li>举个栗子</li></ul> 传统情况 <pre><code>javascript import axios from 'axios' new Vue({ data: { user: "...
  • 很多,线程池听起来高大上,但实际工作中却很少使用。其实不然,各种流行框架或者高性能的架构中,池化技术是无处不在的。 本工程中从JUC中ThreadPoolExecutor类的四个应用和Spring的线程池开始介绍: ...
  • 在原型链中的this是指向new出来的实例的。故</p><pre><code>this.name === a.name</code></pre>,即world <h3>q5:“函数与构造函数的区别” 个人认为这个问题可以归结为,函数为普通...
  • 本书最后还介绍了DelphiInternet方面的应用。移动商务网络编程越来越热的今天,无疑会成为程序员关注的焦点 译者序\r\n序言\r\n前言\r\n作者介绍\r\n\r\n第一部分 基本知识\r\n\r\n第1章 Delphi编程简介\r\n\r\n...
  • 本书最后还介绍了DelphiInternet方面的应用。移动商务网络编程越来越热的今天,无疑会成为程序员关注的焦点 译者序\r\n序言\r\n前言\r\n作者介绍\r\n\r\n第一部分 基本知识\r\n\r\n第1章 Delphi编程简介\r\n\r\n...

空空如也

空空如也

1 2
收藏数 29
精华内容 11
关键字:

有人在问原型链