精华内容
下载资源
问答
  • 之前面试的时候遇到过原型和原型链方面题目,具体已经忘了,只记得当时回答稀里糊涂,今天查了一些资料,把自己所理解的写出来,加深记忆。 1,前提  在js中,对象都有__proto__属性,一般这个是被称为...

      之前面试的时候遇到过原型和原型链方面的题目,具体的已经忘了,只记得当时回答的稀里糊涂,今天查了一些资料,把自己所理解的写出来,加深记忆。

     

    1,前提

      在js中,对象都有__proto__属性,一般这个是被称为隐式的原型,该隐式原型指向构造该对象的构造函数的原型。

      函数比较特殊,它除了和其他对象一样有__proto__属性,还有自己特有的属性----prototype,这个属性是一个指针,指向一个包含所有实例共享的属性和方法的对象,称之为原型对象。原型对象也有一个constructor属性,该属性指回该函数。

     

    2,题目分析

      网上找了一个题目,先分析一下

    var F = function () {}
    Object.prototype.a = function () {}
    Function.prototype.b = function () {}
    
    var f = new F()
    // 请问f有方法a  方法b吗

      f的__proto__指向F.prototype,F.prototype.__proto__指向Object.prototype,所以f 可以取到a方法, 由于f的原型链上没经过Function.prototype,所以取不到b方法。

     由于构造函数F是由Function new出来的,所以F.__proto__指向Function.prototype,所以F函数可以取到b方法。

     

      另外一道题目:

      

    function Person(){}
    
    let p1 = new Person()
    let p2 = new Person()
    let obj = {}

      具体的题目我忘记了,差不多就是写出 p1  p2  Person  Function   obj   Object等的原型链。

      p1:      __proto__ :  Person.prototype       

      p2:      __proto__ :  Person.prototype 

      Person  :         __proto__: Function.prototype,    prototype: Person.prototype

      Person.prototype :         __proto__ : Object.prototype ,  constructor: Person

      Function:       __proto__ : Function.prototype,   prototype: Function.prototype

      Function.Prototype:     __proto__ :  Object.prototype ,   constructor:  Function

      obj:    __proto__ : Object.prototype

      Object:   __proto__ : Function.prototype  ,   prototype:  Object.prototype

      Object.prototype:    __proto__ :  null  ,   constructor  :  Object

         

       具体的就是上面这些了,有两个点需要注意下

        1,Function.__proto__    ===     Function.prototype

          谨记上面的红字部分,每个对象都有__proto__,指向生成该对象的构造函数的原型。 这里Function是一个构造函数,那么它也是一个函数,既然是函数,那也是由Function这个构造函数生成的,也就是它自己本身,所以它的__proto__就指向它自己的prototype

          上面的说法是错误的,这里参考了一位大佬的话语: Function.prototype是引擎创造出来的对象,一开始就有了,又因为其他的构造函数都可以通过原型链找到Function.prototype,Function本身也是一个构造函数,为了不产生混乱,就将这两个联系到一起了。

        2,Object.__proto__  === Function.prototype

          Object是对象的构造函数,那么它也是一个函数,当然它的__proto__也是指向Function.prototype

     

      这里放一张关于这方面的经典的图

     

     

        

     

    转载于:https://www.cnblogs.com/wjyz/p/10219106.html

    展开全文
  • Day03]使用原型最大的好处及原型链的理解面试题题解原型优缺点简单分析原型详细分析2.1 原型的好处2.2 原型链2.2.1 函数对象2.2.2 constructor 构造函数2.2.3 new 操作符2.2.4 构造函数的问题2.2.5 prototype 原型...

    面试题

    使用原型最大的好处,原型链的理解

    题解

    原型优缺点简单分析

    1. 通过原型链继承的方式,原先存在父类型的实例中的所有属性和方法,现在也能存在于子类型的原型中了;
    2. 在通过原型链实现继承时,原型实际上会成为另一个类型的实例。所以父类的实例属性实际上会成为子类的原型属性。结果就是所有的子类的实例都会共享父类的实例属性(引用类型的)
    3. 在创建子类型的实例时,没有办法在不影响所有实例的情况下,向父类型的构造函数传递参数。

    原型详细分析

    2.1 原型的好处

    • JavaScript 采用原型编程,所有的对象都能共享原型上的方法,通过构造函数生成的实例所拥有的方法都指向一个函数的索引,这样可以节省内存,如果不使用原型法就会造成每创建一个对象就会产生一个内存地址
    • 方便实现继承

    2.2 原型链

    原型链是一种机制,指的是 JavaScript 每个对象都有一个内置的_proto__ 属性指向创建它的构造函数的 prototype(原型)属性。原型链的作用是为了实现对象的继承,要理解原型链,需要先从函数对象constructornewprototype_proto__ 这五个概念入手

    2.2.1 函数对象

    在 JavaScript 里,函数即对象,程序可以随意操控它们。比如,可以把函数赋值给变量,或者作为参数传递给其他函数,也可以给它们设置属性,甚至调用它们的方法。下面示例代码对 [普通对象] 和 [函数对象] 进行了区分。

    普通对象

    var o1 = {};
    var o2 = new Object();
    

    函数对象:

    function f1 () {};
    var f2 = function () {};
    var f3 = new Function('str','console.log(str)');
    

    简单的说,凡是使用 function 关键字或 Function 构造函数创建的对象都是函数对象。

    2.2.2 constructor 构造函数

    函数还有一种用法,就是把它作为构造函数使用。像 ObjectArray 这样的原生构造函数,在运行时会自动出现在执行环境中。此外,也可以创建自定义的构造函数,从而自定义对象类型的属性和方法。如下代码所示:

    function Person(name, age, job){
        this.name = name;
        this.age = age;
        this.job = job;
        this.sayName = function(){
            console.log(this.name);
        };
    }
    
    var person1 = new Person("alice", 28, "Software Engineer");
    var person2 = new Person("Sophie", 29, "English Teacher");
    

    在这个例子中,我们创建了一个自定义构造函数 Person(),并通过该构造函数创建了两个普通对象 person1person2,这两个普通对象均包含 3 个属性和 1 个方法。

    你应该注意到函数名 Person 使用的是大写字母 P。按照惯例,构造函数始终都应该以一个大写字母开头,而非构造函数则应该以一个小写字母开头。这个做法借鉴自其他面向对象语言,主要是为了区别于 JavaScript 中的其他函数,因为构造函数本身也是函数,只不过可以用来创建对象而已。

    2.2.3 new 操作符

    要创建 Person 的新实例,必须使用 new 操作符。以这种方式调用构造函数实际上会经历以下 4 个步骤:

    • 创建一个新对象;
    • 将构造函数的作用域赋给新对象(因此,this 就指向了这个新对象)
    • 执行构造函数中的代码(为这个新对象添加属性);
    • 返回新对象(return)

    将构造函数当作函数

    构造函数与其他函数的唯一区别,就在于调用它们的方式不同。不过,构造函数毕竟也是函数,不存在定义构造函数的特殊语法。任何函数,只要通过 new 操作符来调用,那他就可以作为构造函数;而任何函数,如果不通过 new 操作符来调用,那它跟普通函数也不会有什么两样。例如,前面例子中定义的 Person() 函数可以通过下列任何一种方式来调用。

    // 当作构造函数使用
    var person = new Person("alice", 28, "Software Engineer");
    person.sayName(); // "alice"
    
    // 作为普通函数调用
    Person("Sophie", 29, "English Teacher"); // 添加到 window
    window.sayName(); // "Sophie"
    
    // 在另一个对象的作用域中调用
    var o = new Object();
    Person.call(o, "Tommy", 3, "Baby");
    o.sayName(); // "Tommy"
    

    这个例子中的前两行代码展示了构造函数的典型用法,即使用 new 操作符来创建一个新对象。接下来的两行代码展示了不使用 new 操作符调用 Person() 会出现什么结果,属性和方法都被添加给 window 对象了。当在全局作用域中调用一个函数时,this 对象总是指向 Global 对象(在浏览器中就是 window 对象)。因此,在调用完函数之后,可以通过 window 对象来调用 sayName() 方法,并且还返回了 “Sophie”。最后,也可以使用 call() (或者 apply()) 在某个特殊对象的作用域中调用 Person() 函数。这里是在对象 o 的作用域中调用的,因此调用后 o 就拥有了所有属性和 sayName() 方法。

    2.2.4 构造函数的问题

    构造函数模式虽然好用,但也并非没有缺点。使用构造函数的主要问题,就是每个方法都要在每个实例上重新创建一遍。在前面的例子中, person1 和 person2 都有一个名为 sayName() 的方法,但那两个方法不是同一个 Function 的实例。因为 JavaScript

    function Person(name, age, job){
       this.name = name;
       this.age = age;
       this.job = job;
       this.sayName = new Function("console.log(this.name)"); // 与声明函数在逻辑上是等价的
    }
    

    从这个角度上来看构造函数,更容易明白每个 Person 实例都包含一个不同的 Function 实例(sayName() 方法)。说得明白些,以这种方式创建函数,虽然创建 Function 新实例的机制仍然是相同的,但是不同实例上的同名函数是不相等的,以下代码可以证明这一点:

    console.log(person1.sayName == person2.sayName);  // false
    

    然而,创建两个完成同样任务的 Function 实例的确没有必要;况且有 this 对象在,根本不用在执行代码前就把函数绑定到特定对象上面。因此,大可像下面这样,通过把函数转移到构造函数外部来解决这个问题。

    function Person(name, age, job){
       this.name = name;
       this.age = age;
       this.job = job;
       this.sayName = sayName;
    }
    
    function sayName(){
       console.log(this.name);
    }
    
    var person1 = new Person("alice", 28, "Software Engineer");
    var person2 = new Person("Sophie", 29, "English Teacher")
    

    在这个例子中,我们把 sayName()函数的定义转移到了构造函数外部。而在构造函数内部,我们将 sayName 属性设置成等于全局的 sayName 函数。这样一来,由于 sayName 包含的是一个指向函数的指针,因此 person1 和 person2 对象就共享了在全局作用域中定义的同一个 sayName() 函数。这样做确实解决了两个函数做同一件事的问题,可是新问题又来了,在全局作用域中定义的函数实际上只能被某个对象调用,这让全局作用域有点名不副实。而更让人无法接受的是,如果对象需要定义很多方法,那么就要定义很多个全局函数,于是我们这个自定义的引用类型就丝毫没有封装性可言了。好在,这些问题可以通过使用原型来解决。

    2.2.5 prototype 原型

    我们创建的每个函数都有一个 prototype(原型)属性。使用原型的好处是可以让所有对象实例共享它所包含的属性和方法。换句话说,不必在构造函数中定义对象实例的信息,而是可以将这些信息直接添加到原型中,如下面的例子所示:

    function Person(){}
    
    Person.prototype.name = "alice";
    Person.prototype.age = 28;
    Person.prototype.job = "Software Engineer";
    Person.prototype.sayName = function(){
        console.log(this.name);
    };
    
    var person1 = new Person();
    person1.sayName();   // "alice"
    
    var person2 = new Person();
    person2.sayName();   // "alice"
    
    console.log(person1.sayName == person2.sayName);  // true
    

    在此,我们将 sayName() 方法和所有属性直接添加到了 Person的 prototype 属性中,构造函数变成了空函数。即使如此。也仍然可以调用构造函数来创建新对象,而且新对象还会具有相同的属性和方法。但与前面的例子不同的是,新对象的这些属性和方法是由所有实例共享的。换句话说,person1 和 person2 访问的都是同一组属性和同一个 sayName() 函数。

    理解原型对象,在默认情况下,所有原型对象都会自动获得一个 constructor (构造函数) 属性,这个属性包含一个指向 prototype 属性所在函数的指针。就拿前面的例子来说,Person.prototype.constructor 指向 Person。而通过这个构造函数,我们还可继续为原型对象添加其他属性和方法。只有对象方法才会有 prototype。

    虽然可以通过对象实例访问保存在原型中的值,但却不能通过对象实例重写原型中的值。如果我们在实例中添加了一个属性,而该属性与实例原型中的一个属性同名,那我们就在实例中创建该属性,该属性将会屏蔽原型中的那个属性。

    更简单的原型语法,前面例子中每添加一个属性和方法就要敲一遍 Person.prototype。为减少不必要的输入,也为了从视觉上更好地封装原型的功能,更常见的做法是用一个包含所有属性和方法的对象字面量来重写整个原型对象,如下面的例子所示。

    function Person(){}
    
    Person.prototype = {
        name : "alice",
        age : 28,
        job: "Software Engineer",
        sayName : function () {
            console.log(this.name);
        }
    };
    

    在上面的代码中,我们将 Person.prototype 设置为等于一个以字面量形式创建的新对象。最终结果相同,但有一个例外:constructor 属性不再指向 Person 了。前面曾经介绍过,每创建一个函数,就会同时创建它的 prototype 对象,这个对象也会自动获得 constructor 属性。而我们在这里使用的语法,本质上完全重写了默认的 prototype 对象,因此 constructor 属性也就变成了新对象的 constructor 属性(指向 Object 构造函数),不再指向 Person 函数。此时,尽管 instanceof 操作符还能返回正确的结果,但通过 constructor 已经无法确定对象的类型了,如下所示:

    var friend = new Person();
    
    console.log(friend instanceof Object);        // true
    console.log(friend instanceof Person);        // true
    console.log(friend.constructor === Person);    // false
    console.log(friend.constructor === Object);    // true
    

    在此,用 instanceof 操作符测试 Object 和 Person 仍然返回 true,但 constructor 属性则等于 Object 而不等于 Person 了。如果 constructor 的值真的很重要,需要重新指定一下 constructor 的指向。

    2.3 原型的动态性

    由于在原型中查找值的过程是一次搜索,因此我们对原型对象所做的任何修改都能够立即从实例上反映出来,即使是先创建了实例后修改原型也照样如此。请看下面的例子:

    var friend = new Person();
    
    Person.prototype.sayHi = function(){
        console.log("hi");
    };
    
    friend.sayHi();   // "hi"(没有问题!)
    

    以上代码先创建了 Person 的一个实例,并将其保存在 person 中。然后,下一条语句在 Person.prototype 中添加了一个方法 sayHi()。即使 person 实例是在添加新方法之前创建的,但它仍然可以访问这个新方法。其原因可以归结为实例与原型之间的松散连接关系。当我们调用 person.sayHi() 时,首先会在实力中搜素名为 sayHi 的属性,在没找到的情况下,会继续搜索原型。因为实例与原型之间的连接只不过是一个指针,而非一个副本,因此就可以在原型中找到新的 sayHi 属性并返回保存在那里的函数。

    尽管可以随时为原型添加属性和方法,并且修改能够立即在所有对象实例中反映出来,但如果是重写整个原型对象,那么情况就不一样了。我们知道,调用构造函数时会为实例添加一个指向最初原型的 [[prototype]] 指针,而把原型修改为另外一个对象就等于切断了构造函数与最初原型之间的联系。请记住:实例中的指针仅指向原型,而不指向构造函数。看下面的例子:

    function Person(){}
    
    var friend = new Person();
    
    Person.prototype = {
        constructor: Person,
        name : "alice",
        age : 28,
        job : "Software Engineer",
        sayName : function () {
            console.log(this.name);
        }
    };
    
    friend.sayName();   // Uncaught TypeError: friend.sayName is not a function
    

    在这个例子中,我们先创建了 Person 的一个实例,然后又重写了其原型对象。然后在调用 friend.sayName() 时发生了错误,因为 friend 指向的是重写前的原型对象,其中并不包含以该名字命名的属性

    2.4 原型对象的问题

    原型模式也不是没有缺点。首先,它省略了为构造函数传递初始化参数这一环节,结果所有实例在默认情况下都将取得相同的属性值。虽然这会在某种程度上带来一些不方便,但还不是原型的最大问题。原型模式的最大问题是由其共享的本性所导致的。

    原型中所有属性是被很多实例共享的,这种共享对于函数非常合适。对于那些包含基本值的属性倒也说的过去,毕竟(如前面的例子所示),通过在实例上添加一个同名属性,可以隐藏原型中的对应属性。然而,对于包含引用类型值的属性来说,问题就比较突出了。来看下面的例子:

    function Person(){}
    
    Person.prototype = {
        constructor: Person,
        name : "alice",
        age : 28,
        job : "Software Engineer",
        friends : ["ZhangSan", "LiSi"],
        sayName : function () {
            console.log(this.name);
        }
    };
    
    var person1 = new Person();
    var person2 = new Person();
    
    person1.friends.push("WangWu");
    
    console.log(person1.friends);    // "ZhangSan,LiSi,WangWu"
    console.log(person2.friends);    // "ZhangSan,LiSi,WangWu"
    console.log(person1.friends === person2.friends);  // true
    

    在此,Person.prototype 对象有一个名为 friends 的属性,该属性包含一个字符串数组。然后,创建了 Person 的两个实例。接着,修改了 person1.friends 引用的数组,向数组中添加了一个字符串。由于 friends 数组存在于 Person.prototype 而非 person1 中,所以刚刚提到的修改也会通过 person2.friends (与 person1.friends 指向同一个数组)反映出来。假如我们的初衷就是像这样在所有实例中共享一个数组,那么对这个结果我没有话可说。可是,实例一般都是要有属于自己的全部属性的。

    2.5 构造函数和原型结合(寄生组合继承)

    function inheritPrototype(subType,superType){
      var prototype = Object.create(superType.prototype);
      prototype.constructor = subType;
      subType.prototype = prototype;
    }
    
    function A(name) {
      this.name = name;
    }
    
    A.prototype.getName = function () {
      console.log(this.name)
    }
    
    function B(name, age) {
      A.call(this, name);
      this.age = age;
      this.firends = ['前端', '资深'];
    }
    
    inheritPrototype(B,A)
    B.prototype.getFirends = function () {
      console.log(this.firends);
    }
    
    
    const instance1 = new B('jingcheng', 3);
    instance1.getName(); // jingcheng
    instance1.firends.push('React');
    console.log(instance1.firends);  //['前端', '资深','React']
    const instance2 = new B('yideng', 4);
    instance2.getName(); // yideng
    console.log(instance2.firends); // ['前端', '资深']
    
    console.log(instance1, instance2)
    

    在这个例子中,实例属性都是在构造函数中定义的,在子类继承父类的时候,将子类的 prototype.constructor 指向自己,子类再将自己的 prototype 指向父级的 prototype。方法 getName 是在原型上定义的。而修改了 instance1.friends (向其中添加一个新字符串),并不会影响到 instance1.friends,因为它们分别引用了不同的数组。

    这种构造函数与原型混成的模式,是目前在 JavaScript 中使用最广泛、认同度最高的一种创建自定义类型的方法。可以说,这是用来定义引用类型的一种默认模式。

    2.6 原型链

    JavaScript 中描述了原型链的概念,并将原型链作为实现继承的主要方法。其基本思想是利用原型让一个引用类型继承另一个引用类型的属性和方法。简单回顾一下构造函数、原型和实例的关系:每个构造函数都有一个原型对象,原型对象都包含一个指向构造函数的指针,而实例都包含一个指向原型对象的内部指针。

    展开全文
  • 金三银四的旺季即将拉开序幕,这段时间有朋友陆陆续续去面试,和她们沟通发现,最高频出现问题就是:谈谈你对原型和原型链的理解? 无形之刃 最为致命。 ——影流之主 一个看似最最最基础的面试题,但是这送分题,...

    金三银四的旺季即将拉开序幕,这段时间有朋友陆陆续续去面试,和她们沟通发现,最高频出现问题就是:谈谈你对原型和原型链的理解?

    无形之刃 最为致命。 ——影流之主

    一个看似最最最基础的面试题,但是这送分题,回答的不好可能就变成了送命题。接下来就让我们一起来探讨探讨~

    基于原型的语言?

    JavaScript 常被描述为一种基于原型的语言 (prototype-based language)——每个实例对象( object )都有一个私有属性(称之为 proto )指向它的构造函数的原型对象(prototype )。该原型对象也有一个自己的原型对象( proto ) ,层层向上直到一个对象的原型对象为 null。根据定义,null 没有原型,并作为这个原型链中的最后一个环节。

    几乎所有 JavaScript 中的对象都是位于原型链顶端的 Object 的实例。

    在传统的面向对象 中,首先定义“类”,此后创建对象实例时,类中定义的所有属性和方法都被复制到实例中。在 JavaScript 中并是这样,而是在对象实例和它的构造器之间建立一个链接(它是**proto属性,是从构造函数的prototype**属性派生的),之后通过上溯原型链,在构造器中找到这些属性和方法。

    注意: 理解对象的原型(可以通过Object.getPrototypeOf(obj)或者已被弃用的__proto__属性获得)与构造函数的prototype属性之间的区别是很重要的。前者是每个实例上都有的属性,后者是构造函数的属性。也就是说,Object.getPrototypeOf(new Foobar())Foobar.prototype指向着同一个对象。

    Object.getPrototypeOf(obj): 方法返回指定对象的原型, 如果没有继承属性,则返回 null

    __proto__: __proto__属性指向其构造函数的原型。(该特性已经从 Web 标准中删除,虽然一些浏览器目前仍然支持)


    以上描述很抽象;我们先看一个例子。

    在javascript中,每个函数都有一个特殊的属性叫作原型(**prototype**) ,正如下面所展示的。请注意,下面的代码是独立的一段(在网页中没有其他代码的情况下,这段代码是安全的)。为了最好的学习体验,你最好打开一个控制台 (在Chrome和Firefox中,可以按Ctrl+Shift+I来打开)切换到"控制台" 选项卡, 复制粘贴下面的JavaScript代码,然后按回车来运行

    function coolFn(){}
        console.log( coolFn.prototype );
      	 // 无论你怎么声明函数,函数总是有一个默认值的prototype属性
      	 
        var coolFn = function(){}; 
        console.log( coolFn.prototype );
    

    正如上面所看到的, coolFn 函数有一个默认的原型属性,它在控制台上面呈现了出来. 运行这段代码之后,控制台上面应该出现了像这样的一个对象.

        {
            constructor: ƒ coolFn(),
            __proto__: {
                constructor: ƒ Object(),
                hasOwnProperty: ƒ hasOwnProperty(),
                isPrototypeOf: ƒ isPrototypeOf(),
                propertyIsEnumerable: ƒ propertyIsEnumerable(),
                toLocaleString: ƒ toLocaleString(),
                toString: ƒ toString(),
                valueOf: ƒ valueOf()
            }
        }
    

    现在,我们可以添加一些属性到 coolFn 的原型上面,如下所示.

        function coolFn(){}
        coolFn.prototype.foo = "bar";
        console.log( coolFn.prototype );
    

    结果:

        {
            foo: "bar",
            constructor: ƒ coolFn(),
            __proto__: {
                constructor: ƒ Object(),
                hasOwnProperty: ƒ hasOwnProperty(),
                isPrototypeOf: ƒ isPrototypeOf(),
                propertyIsEnumerable: ƒ propertyIsEnumerable(),
                toLocaleString: ƒ toLocaleString(),
                toString: ƒ toString(),
                valueOf: ƒ valueOf()
            }
        }
    

    然后,我们可以使用 new 运算符来在现在的这个原型基础之上,创建一个 coolFn 的实例。正确使用 new 运算符的方法就是在正常调用函数时,在函数名的前面加上一个 new 前缀. 通过这种方法,在调用函数前加一个 new ,它就会返回一个这个函数的实例化对象. 然后,就可以在这个对象上面添加一些属性:

    	   function coolFn(){}
        coolFn.prototype.foo = "bar"; // 在原型prototype添加属性
        var coolInstancing = new coolFn();
        coolInstancing.prop = "some value"; // 在对象上添加一个属性
        console.log( coolInstancing );
    

    结果:

        {
            prop: "some value",
            __proto__: {
                foo: "bar",
                constructor: ƒ coolFn(),
                __proto__: {
                    constructor: ƒ Object(),
                    hasOwnProperty: ƒ hasOwnProperty(),
                    isPrototypeOf: ƒ isPrototypeOf(),
                    propertyIsEnumerable: ƒ propertyIsEnumerable(),
                    toLocaleString: ƒ toLocaleString(),
                    toString: ƒ toString(),
                    valueOf: ƒ valueOf()
                }
            }
        }
    

    就像上面看到的coolInstancing__proto__ 属性就是**coolFn.prototype.** 但是这又有什么用呢?

    好吧,当你访问 coolInstancing 的一个属性, 浏览器首先查找 coolInstancing 是否有这个属性. 如果 coolInstancing 没有这个属性, 然后浏览器就会在 coolInstancing__proto__ 中查找这个属性(也就是 coolFn.prototype). 如果 coolInstancing 的 __proto__ 有这个属性, 那么 coolInstancing 的 __proto__ 上的这个属性就会被使用. 否则, 如果 coolInstancing 的 __proto__ 没有这个属性, 浏览器就会去查找 coolInstancing 的 __proto____proto__ ,看它是否有这个属性.


    默认情况下, 所有函数的原型属性的 __proto__ 就是 window.Object.prototype. 所以 coolInstancing 的 __proto____proto__ (也就是 coolFn.prototype 的 __proto__ (也就是 Object.prototype)) 会被查找是否有这个属性. 如果没有在它里面找到这个属性, 然后就会在 coolInstancing 的 __proto____proto____proto__ 里面查找. 然而这有一个问题: coolInstancing 的 __proto____proto____proto__ 不存在. 最后, 原型链上面的所有的 __proto__ 都被找完了, 浏览器所有已经声明了的 __proto__ 上都不存在这个属性,然后就得出结论,这个属性是 undefined.

    function coolFn(){}
        coolFn.prototype.foo = "bar";
        var coolInstancing = new coolFn();
        coolInstancing.prop = "some value";
        console.log("coolInstancing.prop:      " + coolInstancing.prop);
        console.log("coolInstancing.foo:       " + coolInstancing.foo);
        console.log("coolFn.prop:           " + coolFn.prop);
        console.log("coolFn.foo:            " + coolFn.foo);
        console.log("coolFn.prototype.prop: " + coolFn.prototype.prop);
        console.log("coolFn.prototype.foo:  " + coolFn.prototype.foo);
    

    结果:

        coolInstancing.prop:      some value
        coolInstancing.foo:       bar
        coolFn.prop:           undefined
        coolFn.foo:            undefined
        coolFn.prototype.prop: undefined
        coolFn.prototype.foo:  bar
    

    理解原型对象

    让我们回到 Person() 构造器的例子。请把下面代码例子依次写入浏览器控制台。。

    本例中我们首先将定义一个构造器函数:

    function Person(first, last, age, gender, interests) {
      
     // 属性与方法定义
      
    };
    
    

    然后在控制台创建一个对象实例:

    var person1 = new Person('Bob', 'Smith', 32, 'male', ['music', 'skiing']);
    
    

    在 JavaScript 控制台输入 “person1.”,你会看到,浏览器将根据这个对象的可用的成员名称进行自动补全:

    img

    在这个列表中,你可以看到定义在 person1 的原型对象、即 Person() 构造器中的成员—— nameagegenderinterestsbiogreeting。同时也有一些其他成员—— watchvalueOf 等等——这些成员定义在 Person() 构造器的原型对象、即 Object 之上。下图展示了原型链的运作机制。

    原型链

    那么,调用 person1 的“实际定义在 Object 上”的方法时,会发生什么?比如:

    person1.valueOf()
    
    

    这个方法仅仅返回了被调用对象的值。在这个例子中发生了如下过程:

    • 浏览器首先检查,person1 对象是否具有可用的 valueOf() 方法。
    • 如果没有,则浏览器检查 person1 对象的原型对象(即 Person构造函数的prototype属性所指向的对象)是否具有可用的 valueof() 方法。
    • 如果也没有,则浏览器检查 Person() 构造函数的prototype属性所指向的对象的原型对象(即 Object构造函数的prototype属性所指向的对象)是否具有可用的 valueOf() 方法。这里有这个方法,于是该方法被调用。

    注意:必须重申,原型链中的方法和属性没有被复制到其他对象——它们被访问需要通过前面所说的“原型链”的方式。

    注意:没有官方的方法用于直接访问一个对象的原型对象——原型链中的“连接”被定义在一个内部属性中,在 JavaScript 语言标准中用 **[[prototype]]** 表示(参见 ECMAScript)。然而,大多数现代浏览器还是提供了一个名为 **__proto__** (前后各有2个下划线)的属性,其包含了对象的原型。你可以尝试输入 **person1.__proto__** **person1.__proto__.__proto__**,看看代码中的原型链是什么样的!

    prototype 属性

    那么,那些继承的属性和方法在哪儿定义呢?如果你查看 Object 参考页,会发现左侧列出许多属性和方法——大大超过我们在 person1 对象中看到的继承成员的数量。某些属性或方法被继承了,而另一些没有——为什么呢?

    原因在于,继承的属性和方法是定义在 prototype 属性之上的(你可以称之为子命名空间 (sub namespace) )——那些以 Object.prototype. 开头的属性,而非仅仅以 **Object**. 开头的属性。prototype 属性的值是一个对象,我们希望被原型链下游的对象继承的属性和方法,都被储存在其中。

    于是 Object.prototype.watch()、``Object.prototype.valueOf() 等等成员,适用于任何继承自 Object() 的对象类型,包括使用构造器创建的新的对象实例。

    Object.is()Object.keys(),以及其他不在 prototype 对象内的成员,不会被“对象实例”或“继承自 Object() 的对象类型”所继承。这些方法/属性仅能被 Object() 构造器自身使用。

    注意:这看起来很奇怪——构造器本身就是函数,你怎么可能在构造器这个函数中定义一个方法呢?其实函数也是一个对象类型**。**

    你可以检查已有的prototype 属性。回到先前的例子,在 JavaScript 控制台输入:

      Person.prototype
    
    
      2. 输出并不多,毕竟我们没有为自定义构造器的原型定义任何成员。缺省状态下,构造器的 `prototype`属性初始为空白。现在尝试:
    
     Object.prototype
    
    

    你会看到 Objectprototype 属性上定义了大量的方法;如前所示,继承自 Object 的对象都可以使用这些方法。

    JavaScript 中到处都是通过原型链继承的例子。比如,你可以尝试从 StringDateNumberArray 全局对象的原型中寻找方法和属性。它们都在原型上定义了一些方法,因此当你创建一个字符串时:

    var myString = 'This is my string.';
    
    

    myString 立即具有了一些有用的方法,如 split()indexOf()replace() 等。

    重要:prototype 属性大概是 JavaScript 中最容易混淆的名称之一。你可能会认为,this 关键字指向当前对象的原型对象,其实不是(还记得么?原型对象是一个内部对象,应当使用__proto__ 访问)。prototype 属性包含(指向)一个对象,你在这个对象中定义需要被继承的成员。

    create()方法

    Object.create() 方法可以创建新的对象实例。

    例如,在上个例子的 JavaScript 控制台中输入:

      ```
      var person2 = Object.create(person1);
      ```
    

    create()实际做的是从指定原型对象创建一个新的对象。这里以person1 为原型对象创建了person2 对象。在控制台输入:

      ```
      person2.__proto__
      ```
    

    结果返回对象person1

    constructor 属性

    每个实例对象都从原型中继承了一个constructor属性,该属性指向了用于构造此实例对象的构造函数。

    1. 例如,继续在控制台中尝试下面的指令:

      person1.constructor
      person2.constructor
      
      

      都将返回 Person() 构造器,因为该构造器包含这些实例的原始定义。

      一个小技巧是,你可以在 constructor 属性的末尾添加一对圆括号(括号中包含所需的参数),从而用这个构造器创建另一个对象实例。毕竟构造器是一个函数,故可以通过圆括号调用;只需在前面添加 new 关键字,便能将此函数作为构造器使用。

      在控制台中输入:

        var person3 = new person1.constructor('Karen', 
                      'Stephenson', 26,
                      'female', 
                      ['playing drums', 'mountain climbing']);
      

    ​   

    现在尝试访问新建对象的属性,例如:

        person3.name.first
        person3.age
        person3.bio()
    

    正常工作。通常你不会去用这种方法创建新的实例;但如果你刚好因为某些原因没有原始构造器的引用,那么这种方法就很有用了。

    此外,constructor 属性还有其他用途。比如,想要获得某个对象实例的构造器的名字,可以这么用:

    instanceName.constructor.name
    
    

    具体地,像这样:

    person1.constructor.name
    
    

    如何修改原型

    从我们从下面这个例子来看一下如何修改构造器的 prototype 属性。

    向构造器的 prototype 添加了一个新的方法:

    function Person(first, last, age, gender, interests) {
    
          // 属性与方法定义
    
        };
    
        var person1 = new Person('Tammi', 'Smith', 32, 'neutral', ['music', 'skiing', 'kickboxing']);
    
        Person.prototype.farewell = function() {
          alert(this.name.first + ' has left the building. Bye for now!');
    }
    
    

    但是 farewell() 方法仍然可用于 person1 对象实例——旧有对象实例的可用功能被自动更新了。这证明了先前描述的原型链模型。这种继承模型下,上游对象的方法不会复制到下游的对象实例中;下游对象本身虽然没有定义这些方法,但浏览器会通过上溯原型链、从上游对象中找到它们。这种继承模型提供了一个强大而可扩展的功能系统。

    你很少看到属性定义在 prototype 属性中,因为如此定义不够灵活。比如,你可以添加一个属性:

    Person.prototype.fullName = 'Bob Smith';
    
    

    但这不够灵活,因为人们可能不叫这个名字。用 name.firstname.last 组成 fullName 会好很多:

    Person.prototype.fullName = this.name.first + ' ' + this.name.last;
    
    

    然而,这么做是无效的,因为本例中 this 引用全局范围,而非函数范围。访问这个属性只会得到 undefined undefined。但这个语句若放在 先前定义在 prototype 上的方法中则有效,因为此时语句位于函数范围内,从而能够成功地转换为对象实例范围。你可能会在 prototype 上定义常属性 (constant property) (指那些你永远无需改变的属性),但一般来说,在构造器内定义属性更好。

    **:关于** **this** 关键字指代(引用)什么范围/哪个对象,这个问题超出了本文讨论范围。事实上,这个问题有点复杂,如果现在你没能理解,也不用担心。

    事实上,一种极其常见的对象定义模式是,在构造器(函数体)中定义属性、在 **prototype** **属性上定义方法。**如此,构造器只包含属性定义,而方法则分装在不同的代码块,代码更具可读性。例如:

    // 构造器及其属性定义
    
    function Test(a,b,c,d) {
          // 属性定义
        };
    
        // 定义第一个方法
    
        Test.prototype.x = function () { ... }
    
        // 定义第二个方法
    
        Test.prototype.y = function () { ... }
    // 等等……
    
    

    参考资料

    • 继承与原型链-MDN
    • 之前看过的一篇外国友人的基于JS原型的理解,由于久远链接遗失

    结尾

    以上就是关于JS原型、原型链的理解,不足之处请大家指出,一起交流,希望对大家有所帮助。

    快要过年了,同学们应该都开始陆陆续续的回家啦,祝大家一路顺风,新的一年暴富、脱单~

    我曾踏足山巅,也曾跌落谷底,两者都让我受益良多。

    展开全文
  • JS的原型和原型链算是前端面试题的热门题目了,也是参加了几场面试,感觉好多次都被问到对原型和原型链的理解,所以今天也是索性把他给整理出来,一方面方便自己以后复习查看,另一方面也是给大家分享一下我对原型和...

    JS的原型和原型链算是前端面试题的热门题目了,也是参加了几场面试,感觉好多次都被问到对原型和原型链的理解,所以今天也是索性把他给整理出来,一方面方便自己以后复习查看,另一方面也是给大家分享一下我对原型和原型链的理解。

    ES6之前中并没有引入类(class)的概念,JavaScript并非通过类而是直接通过构造函数来创建实例。

    什么是原型

    每一个javascript对象(除null外)创建的时候,就会与之关联另一个对象,这个对象就是我们所说的原型,每一个对象都会从原型中“继承”属性。

    这么说可能会有点抽象,举个例子吧

    function Person(age) {
        this.age = age       
    }
    Person.prototype.name = 'kavin'
    var person1 = new Person()
    console.log(person1.name)  //kavin

     就是说在通过构造函数创建实例的时候,如果去实例中找寻某个属性值的时候,如果他有的话就会直接输出,如果没有的话,就会去它的原型对象中去找。

    prototype:每个函数都有一个prototype属性,这个属性指向函数的原型对象。

    __proto__:这是每个对象(除null外)都会有的属性,叫做__proto__,这个属性会指向该对象的原型。

    constructor:每个原型都有一个constructor属性,指向该关联的构造函数。

    原型相关的知识考点一般就是围绕这张图,那怎么去验证这张图的正确性呢?

    console.log(Person===Person.prototype.constructor)  // true
    console.log(person.__proto__ == Person.prototype) // true
    console.log(person.__proto__.constructor == Person) // true
    console.log(person.constructor == Person) // true

     估计大家看到底四个的时候会有一些疑问了,为什么person.constructor == Person,其实还是前面所说到的,因为person没有这个属性的情况下,会从它的原型中去继承,这个时候第三个式子和第四个式子就是一样的了。

    什么时候会用到原型

    下面说一下什么时候会用到原型,还是像上边一样,举个简单的例子:

    function Person(name, age, gender) {
            this.name = name
            this.age = age
            this.gender = gender
            this.sayName = function () {
                console.log("我的名字叫" + this.name);
            }
        }

    这和上边看起来是没什么区别的,但是小差别就在这个sayName ,就是说每创建一个Person构造函数,在Person构造函数中,为每一个对象都添加了一个sayName方法,也就是说构造函数每执行一次就会创建一个新的sayName方法。

    一个还好,如果创建了一百个实例,一千个甚至上万个呢,这时候就体现出原型的好处了,我们可以把sayName方法放到构造函数的prototype上,这时候只需要创建一个,而且每一个实例都可以访问到。

    什么是原型链

    单的回顾一下构造函数、原型和实例的关系:每个构造函数都有一个原型对象,原型对象都包含一个指向构造函数的指针,而实例都包含一个指向原型对象的内部指针。那么假如我们让原型对象等于另一个类型的实例,结果会怎样?显然,此时的原型对象将包含一个指向另一个原型的指针,相应地,另一个原型中也包含着一个指向另一个构造函数的指针。假如另一个原型又是另一个类型的实例,那么上述关系依然成立。如此层层递进,就构成了实例与原型的链条。

    简单的表达就是:上边说道如果实例没有一个属性,它会去它的原型中去找,但是如果它的原型中也没有这个属性呢,会停止寻找么,不一定,因为它的原型可能也有自己的原型,这个时候他就会去它的原型的原型中去寻找,这个时候会停下么,还是不一定,要看他原型的原型有没有原型,这样就形成了一条原型链。

    直到最后一个找不到原型时返回null

    展开全文
  • 原型和原型链的理解

    千次阅读 多人点赞 2019-09-09 09:39:47
    原型和原型链的理解:(面试题) 原型:每个函数都有 prototype 属性,该属性指向原型对象;使用原型对象的好处是所有对象实例共享它所包含的属性和方法。 原型链:主要解决了继承的问题;每个对象都拥有一个原型对象...
  • Function.prototype.a = 'a'; Object.prototype.b = 'b'; function Person(){}; var p = new Person(); 问:p是否可以访问到 a 和...所以下决心要把原型链的相关知识恶补一下。 首先看JS高设中对原型链的描述: ...
  • 面试题原型链

    2018-11-15 20:17:00
    简单理解就是原型组成链,对象__proto__是它原型,而原型也是一个对象,也有__proto__属性,原型__proto__又是原型原型,就这样可以一直通过__proto__想上找,这就是原型链,当向上找找到Object.prototype...
  • 说明: 在JavaScript中,对象都有__proto__属性(隐式原型),指向构造该对象构造函数的原型。...接下来是我整理几个 题目1: function F() { this.a = 1; } var obj = new F(); console.log(o...
  • 面试中的重要问题,我遇到过的面试问题中,原型链被问到的几率特别大,所以整理一个笔记记录下来我对原型链的理解。 每个构造函数(constructor)都有一个原型对象(prototype),原型对象都包含一个指向构造函数的指针...
  • 面试题----原型的理解

    2018-05-02 14:51:00
    小伙伴们,不知道大家是怎么对面向对象、闭包、原型、原型链理解的怎么样,这一块臭臭一直以来理解的不够透彻,有理解透彻小伙伴可以跟臭臭讲解一下哟~~虽然臭臭理解的不深,但是还会整理这些面试题哟,接下来我们...
  • 涉及面试题:如何理解原型?如何理解原型链? 当我们创建一个对象时 let obj = { age: 25},我们可以发现能使用很多种函数,但是我们明明没有定义过它们,对于这种情况你是否有过疑惑? 当我们在浏览器中打印 obj ...
  • 原型和原型链的理解:(面试题) 原型:每个函数都有 prototype 属性,该属性指向原型对象;使用原型对象的好处是所有对象实例共享它所包含的属性和方法。 原型链:主要解决了继承的问题;每个对象都拥有一个原型对象...
  • 前言:js基础中很重要一部分内容,前端面试必问题目。主要从以下几个方面讲讲原型和继承(讲很浅薄,... 原型、原型链、原型对象 继承六种方式 一、构造函数和普通函数 说到原型,一定离不开构造函数...
  • 最近博主在学习原型以及原型链的相关内容,把高级程序设计里第六章看完之后,感觉还是挺好理解的,于是有些飘飘然,仿佛得到了全世界但是最近的一道牛客面试题,让我半天摸不到头脑,于是深受打击的我开始痛定思痛,...
  • 写在前面,JavaScirpt 是通过原型链来实现面向对象语言中的...实践中理解一般开发中很少去关注原型链(开发框架除外),面试问的比较多点一个面试题开始如果是已经理解了原型和原型链的一眼就可以看出来结果原型链关...
  • 码字不易,有帮助的同学希望能关注一下我的微信公众号:Code程序人生...下面为大家简单阐述我对原型和原型链的理解,若是觉得有说的不对的地方,还望直接把页面关闭了,别在我这篇文章上继续浪费时间。(逃) 四个规.
  • 下面为大家简单阐述我对原型和原型链的理解,若是觉得有说的不对的地方,还望直接把页面关闭了,别在我这篇文章上继续浪费时间。(逃) “我自己是一名从事了6年web前端开发的老程序员,今年年初我花了一个月整理...
  • 以上是原型链的基本概念, 但很多面试题都不会这么简单的去考这些, 下面贴出一些面试题并写出自己的分析: 例子1: function Class(){} Class.prototype.__proto__.prop = 1; var new_pro = new Class; var ...
  • JS闭包的理解及常见应用场景,JS原型、原型链、对象详解, JS数据类型,JS数据类型判断,JS继承,JS数组方法(ES5,ES6) var,let,const区别,generator函数,promise对象,垃圾回收机制和内存泄漏,async,await 事件...
  • JavaScript原型链

    2017-09-11 17:04:55
    都知道在JavaScript中原型链是非常重要的,而且各大公司招聘的笔试题以及面试题中对于原型链的考察也经常会涉及到,就个人而言,我觉得完全理解了下面这张图,原型链就基本理解了。 关于原型链的讲解我就不再这里...
  • 面试题(头条) 利用原型和原型链相关知识,画出 Object、 Function、Object....小编对它也是日常迷惑,每次都需要查资料、重新理逻辑,近日重新换了个角度思考,对原型和原型链有了更加深刻认识和理解。 ...
  • JS原型和原型链

    2019-06-18 18:03:01
    原型和原型链问题本身而言并不难,主要是考察对JavaScript基本概念的理解,但说明白就比较难了。纠结了很久还是决定转发下文,感觉自己写的那篇不如这篇好理解。 原文链接:...
  • 原文链接 Javascript前端开发:腾讯JS面试题让你深入了解原型与继承 个人理解 核心就是说:f是对象,只能在Object原型链上查找,而F是Function实例,它可以在Function原型链上查找。
  • 前端开发之原型与原型链

    千次阅读 2018-11-12 12:37:33
    原型和原型链估计做前端开发人都听说过,而且这个是一道很常见的面试题,但是想要真正弄清楚“什么是原型”,”什么是原型链“”,”他们又有什么用“”,“适合在哪些实际场景中使用”这些问题还真不是一件很容易...
  • 写在前面,JavaScirpt 是通过原型链来实现面向对象语言中的...实践中理解一般开发中很少去关注原型链(开发框架除外),面试问的比较多点一个面试题开始如果是已经理解了原型和原型链的一眼就可以看出来结果原型链关...
  • 1.JS原型链 JS里万物皆对象,对象又分为普通对象和函数对象。每当定义一个对象时,对象中都会包含一些预定义属性。其中每个对象都有一个_proto_属性,这个属性用来指向创建它构造函数原型对象;每个函数对象...
  • 以下内容仅供参考,成年...原型的值是一个对象有自己的原型,这样就串联起来了一条原型链,原型链的链头是object,它的prototype比较特殊,值为null。 原型链的作用是用于对象继承,函数A的原型属性(prototype prop...
  • 面试题

    2021-01-25 18:12:38
    原型链的理解 什么是闭包 BFC是什么 要加快一个网站的打开速度可以从哪些方面进行优化 请写出以下代码的执行结果 var foo={n:1}; var bar = foo; foo.x = foo = {n:2}; console.log('bar',bar); console.log('...

空空如也

空空如也

1 2 3 4 5 ... 8
收藏数 155
精华内容 62
关键字:

原型链的理解面试题