• JavaScript学习日志(二):面向对象的程序设计


    关于原型、继承等相关的知识点,请访问最新的一篇随笔,讲得更细致直白一些。

    传送门:https://www.cnblogs.com/yanchenyu/p/10069370.html

    =======

    1,ECMAScript不像其他面向对象的语言那样有类的概念,它的对象与其他不同。

    2,ECMAScript有两种属性:数据属性和访问器属性。([[]]这种双中括号表示属性为内部属性,外部不可直接访问)
    1.数据属性:[[ Configurable ]]:表示能否通过delete删除属性,能否修改属性的特性,能否将属性修改为访问器属性,默认为true。
    [[ Enumerable ]]:表示能否通过for-in循环返回属性,默认为true。
    [[ Writable ]]:表示能否修改属性的值,默认为true。
    [[ Value ]]:包含这个属性的数据值,默认undefined。
    要修改上述属性,必须用Object.defineProperty()方法,接收三个参数:属性所在的对象,属性的名字和描述符对象(就是上述4个属性名)

    2.访问器属性:[[ Configurable ]]:表示能否通过delete删除属性,能否修改属性的特性,能否将属性修改为数据属性,默认为true。
    [[ Enumerable ]]:表示能否通过for-in循环返回属性,默认为true。
    [[ Get ]]:在读取属性时调用的函数,默认为undefined。
    [[ Set ]]:再写入属性时调用的函数,默认为undefined。
    也是必须用Object.defineProperty()方法来定义

    3,创建对象的几种模式:
    1.工厂模式:

    function createPerson(name, age, job){
        var o = new Object();
        o.name = name;
        o.age = age;
        o.job = job;
        o.sayName = function(){
            alert(name)
        };
        return o
    }
    
    var person = createPerson(“Nical”, 24, “Engineer”);

    缺点:①,无法确定对象的类型(都是Object);
        ②,创建的多个对象之间没有联系

    2,构造函数模式:

    function Person(name, age, job){
        this.name = name;
        this.age = age;
        this.job = job;
        this.sayName = function(){
            alert(name)
        }
    }
    
    var person = new Person(“Nical”, 24, “Engineer”);

    缺点:①多个实例重复创建方法,无法共享。
       ②多个实例都有sayName方法,但均不是同一个Function的实例。

    3,原型模式:

    function Person(){}
        Person.prototype.name = 'Nical';
        Person.prototype.age = 24;
        Person.prototype.job = 'Engineer';
        Person.prototype.sayName = function(){
            alert(this.name);
    }
    
    var person = new Person();

    缺点:①无法传入参数,不能初始化属性值。
       ②如果包含引用类型的值时,改变其中一个实例的值,则会在所有实例中体现。

     

     

    ===========================================================================

    (理解原型对象:

    只要创建了一个函数(Person),就会为这个函数(Person)自动创建一个prototype属性,这个属性指向该函数的原型对象。这个原型对象会自动获得一个constructor(构造函数)属性,指向该函数(Person)。(初来理解:这里我们可以猜想函数的原型对象构造了函数,但是下面写了一个函数的实例也有一个构造函数属性指向了函数,那这个说法就不通了)(第二次理解:再读的时候发现可以理解通,其实总结起来很简单,一句话,被构造的东西.constructor === 构造它的东西,比如说下面的第一行代码,Person.prototype哪里来的,就是Person在创建的时候自动构造出来的,也就是说Person.prototype是Person这个函数构造出来的,所以第一行成立!第二三行,person1是怎么出来的,是Person这个函数构造出来的,所以person1.constractor===Person)

    Person.prototype.constructor === Person; //true
    var person1 = new Person();
    person1.constructor === Person; //true


    (这里我不太理解第一次为什么理解错了,person1这个实例就是由Person这个构造函数构造出来的,那么person1的constructor属性就是指向的Person这个构造函数啊,这里也可以理解成实例person1的属性应该是和它的构造函数的原型对象的属性一样,由于继承的关系,所以它构造函数的原型对象是指向构造函数Person的,所以它也指向Person)

    constructor属性指向的是Person构造函数,不管是它的原型对象还是它的创建的实例,它们的constructor属性都会指向Person。

    字符表示:
    Person.prototype ——> Person Prototype(原型对象),Person Prototype(原型对象).constructor ——>Person

    这里我的理解就是,‘指向’的意思其实就是代表‘是’,Person.prototype就是Person的原型对象,所以原型对象的constructor属性指向函数,就是
    Person.prototype.constructor === Person; //true


    构造函数创建之后,它的原型对象只有一个constructor属性,其他属性都是从Object那里继承来的。此时,用构造函数创建一个实例,比如
    var person1 = new Person();
    此时实例的内部包含一个指针(内部属性),指向构造函数的原型对象,注意,不是构造函数Person,而是Person的原型对象(Person Prototype),在FireFox,Safari,chrome浏览器里,这个指针叫__proto__,再三注意,这个连接是实例与构造函数的原型对象之间的,不是实例与构造函数之间的。所以当我们想为实例添加属性的时候,不用在构造函数里添加,而是直接在原型对象里添加,就是

    Person.prototype.name = 'Nical';
    Person.prototype.age = 24;
    Person.prototype.job = 'Engineer';
    // 此时person1.name = “Nical”

    判断一个原型对象是否是某个实例的原型,用isPrototypeOf(),括号里面传实例,原型写在方法前面,这个方法可以确定该实例是否存在内部属性,指向该原型

    Person.prototype.isPrototypeOf(person1); // true

    一个实例的原型,如何知道,用Object.getPrototypeOf(),返回值就是原型对象,括号里面传实例。

    Object.getPrototypeOf(person1); //Person.prototype

    以上两个方法传的参数都是实例。

    代码在读取某个对象的属性值的时候,先查找该实例对象是否包含该属性,如果有就返回该属性值,如果没有,直接查找该实例所对应的构造函数的原型对象是否包含该属性,如果有就返回该属性值。如果实例中存在和原型对象中同名属性,则会自动屏蔽原型中的属性,但不会修改原型中的属性。

    查属性,沿着原型链;查标识符,沿着作用域链。

    如何判断对象的某个属性是否是对象自身添加的还是原型中的?用hasOwnProperty():
    person1.hasOwnProperty(“name”); //false

    注意:如果在实例中新定义了一个和原型中同名的属性,那么hasOwnProperty会返回true。

    如何判断通过对象能够访问某个属性?用in方法:
    “name” in person1; //true

    如何判断某个对象的属性是否只是他的原型中的,而不是实例中的?

    function hasPrototypeProperty( object , name ){
        return (!object.hasOwnProperty(name))&& (name in object)
    }

    注意,如果新定义了一个和原型中同名的属性,则改值会返回false

    几种访问对象属性的方法,注意他们的区别:
    1,for-in循环返回的是能通过对象访问的、可枚举的属性。
    2,Object.keys()返回的是仅仅是当前对象的自身属性、并且是可枚举的所有属性的字符串数组。
    3,Object.getOwnPropertyNames()返回的是仅仅是当前对象的自身属性、并且是可枚举和不可枚举的所有属性的字符串数组。
    这个一定要用例子来说明:

    function Person(){};
    Person.prototype.name = ‘Nical’;
    var person1 = new Person();
    person1.age = 12;
    
    for(var a in person1){
        console.log(a)
    };
    // age 
    // name
    
    for(var b in Person){
        console.log(b)
    };
    //
    
    for(var c in Person.prototype){
        console.log(c)
    }
    // name
    
    Object.keys(person1);
    // [‘age’];
    
    Object.keys(Person);
    // [];
    
    Object.keys(Person.prototype)
    // [“name"]
    
    Object.getOwnPropertyNames(person1);
    // [‘age’]
    
    Object.getOwnPropertyNames(Person);
    // ["length", "name", "arguments", "caller", "prototype"]
    
    Object.getOwnPropertyNames(Person.prototype)
    // [“constructor", "name"]

    关于原型对象的重写问题,下面我用一个较长的例子来解释一下,这里面有些坑要注意:

    function Person(){};
    Person.prototype = {
        name: ‘Nical’
    };

    //这里重写了Person的原型对象,此时,Person原型对象的构造函数属性已经不再指向Person了,同样,由Person新建的实例(注意,这里是新建的,也就是说是在重写之后新建的,如果是在重写之前就已经建立了新的实例,那么构造函数属性依然指向Person)的构造函数属性也不再指向Person了,二者的constructor属性都指向Object了。

    Person.prototype.constructor === Person; //false
    Person.prototype.constructor === Object; //true
    var person1 = new Person();
    person1.constructor === Person; //false
    person1.constructor === Object; //true

    ======

    2018年3月1日补充:

    重新看到这里,我理解一下为什么person1的constructor属性不指向Person了,按照原来的逻辑,虽然Person的原型对象的constructor不指向Person了,但是它创建的实例确实是来自它这个构造函数本身,所以应当实例的constructor指向创建它的那个。

    简单来说,这个实例是否指向它的构造函数,实际上和它和构造函数没什么关系,关键在于它的构造函数的原型对象是否指向那个构造函数。所以我觉得,一个实例的constructor属性指向应该是和它的__proto__指向一致,也就是它的构造函数的原型对象保持一致。

    ======

    那么如果我要让constructor属性指向Person呢,很简单,在原型对象里面强行添加constructor属性,并赋给Person,接着上面的例子:

    Person.prototype = {
        constructor: Person,
        name: ‘Tom’
    }
    var person2 = new Person();
    Person.prototype.constructor === Person; //true
    person2.constructor === Person;//true

    但是此时的person1和person2的原型已经不是同一个了,所以

    person1.name; // ‘Nical’

    上面直接在原型中添加constructor属性会导致他的[[Enumerable]]特性为true,但是原生的constructor属性是不可枚举的,所以我们要用Object.defineProperty()方法来修改constructor属性,具体的传参在上面第6章第2节里面有:

    Object.defineProperty( Person.prototype , ’constructor’, {
        enumerable: false,
        value: Person
    });

    如果实例新建后,再给实例的构造函数的原型对象添加属性或方法,实例还是可以访问到新增的属性或方法,原因是实例与原型之间的松散连接关系,他们之间的连接是一个指针(__proto__),而不是副本。但是如果原型对象是重写的,那么这个指针就指不到新的原型对象了,所以那些新增的属性或方法在实例中就不可访问了(这里的实例是指重写之前建立的实例,他们访问的还是最初的原型,如果实例是在重写之后,那么还是可以访问到的)。


    ===========================================================================


    回到原型模式的问题,他的缺点我用一个实例来说明:

    function Person(){};
    Person.prototype = {
        name: ’Nical’,
        friends: [‘a’ , ‘b’, ‘c’]
    }
    var person1 = new Person();
    var person2 = new Person();
    person1.name = ‘Sam’;
    person1.friends.push(‘d’);
    
    person2.name; //’Nical’;
    person2.friends; //[‘a’ ,’b’, ‘c’, ‘d’];

    如果一个实例修改了属性是基本类型的,那么不会影响到其他实例,但是如果一个实例修改了属性是引用类型的,那么其他的也会受影响。这里的属性都是指的是原型中的属性,不是构造函数中的属性(补充一下,如果是构造函数中的属性,不管修改的是什么类型,其他实例都不会受影响,具体案例看下面那个),实例中没有的。如果是实例本身的属性或者是与原型对象中同名的属性,那么无论修改的属性是什么类型,都不会受影响。


    4,组合使用构造函数模式和原型模式

    function Person(name , age, job){
        this.name = name;
        this.age = age;
        this.job = job;
        this.friends = [‘a’, ‘b’, ‘c’];
    }
    
    Person.prototype = {
        constructor: Person,
        sayName: function(){
            alert(this.name)
        }
    };
    
    var person1 = new Person(‘Nical’, 29, ‘Engineer’);
    var person2 = new Person(‘Greg’, 27, ‘Doctor’);
    
    person1.friends.push(“d”);
    person1.friends;    // [‘a’, ‘b’, ‘c’, ‘d’];
    person2.friends; // [‘a’, ‘b’, ‘c’];

    因为这里friends的属性是在构造函数里定义的,所以某个实例改变它,其他实例不改变。


    5,动态原型模式

    function Person(name , age, job){
        this.name = name;
        this.age = age;
        this.job = job;
        if( typeof this.sayName != “function” ){
            Person.prototype.sayName = function(){
                alert(this.name)
            }
        }
    }    


    可以通过检查某个应该存在的方法是否有效,来决定是否需要初始化原型。

    6,寄生构造函数模式

    function Person(name, age, job){
        var o = new Object();
        o.name = name;
        o.age = age;
        o.job = job;
        o.sayName = function(){
            alert(this.name)
        };
        return o
    }
    
    var person = new Person(“Nical”, 24, “Engineer”);
    
    //不推荐使用

    7,稳妥构造函数模式

    function Person(name, age, job){
        var o = new Object();
    
        //在这边定义私有变量和函数
    
        o.sayName = function(){
            alert(name)
        };
    
        return o;
    }

    这种方法能保证除了sayName方法外,没有别的方式可以访问到其他数据成员。

    =========

    2018.3.1补充:

    关于同一个构造函数生成多个实例,如果某个实例修改了它的属性或方法,而这个属性或方法是构造函数中而非原型上的,那么其他实例不受影响;但是如果这个属性或方法是构造函数的原型对象上的,那么就要分情况了:

    1,如果修改的这个属性是基本类型的值(number,string,boolean,undefined,null),那么修改的话其它实例就不受影响;

    2,如果修改的属性是引用类型的值(对象或方法),那么修改的话就会影响到别的实例,跟着它一起变。

    ==========

    4,继承

    ECMAScript只支持实现继承,主要是依靠原型链实现继承。

    =============

    1,原型链

    每一个构造函数都有一个prototype属性,指向他的原型对象,原型对象又有一个constructor属性,指向构造函数,构造函数生成的实例,又有一个内部属性(__proto__),指向它的构造函数的原型对象。如果这时候将原型对象作为另一个构造函数的实例,则这个实例(原型对象)又会有一个指针(__proto__)指向该实例(原型对象)的原型对象,而这个实例(原型对象)的原型对象又会有一个constructor属性,指向该实例(原型对象)的构造函数,该实例(原型对象)又会有一个prototype属性指向实例(原型对象)的原型对象。原型链就是实例和原型的链条。

    function Person1(){};
    var person1 = new Person1();
    function Person2(){};
    Person1.prototype = new Person2();
    Person1.prototype.__proto__ === Person2.prototype; // true 等同于person1.__proto__.__proto__ === Person2.prototype; //true
    Person1.prototype.constructor === Person1; //false,因为Person1的prototype重写了,Person1的原型对象被当作了Person2的实例,所以应该指向Person2,即Person1.prototype.constructor === Person2
    person1.constructor === Person1; // true,因为此时Person1的原型对象已经不是最初的了,所以person1不会继承新的原型对象,所以它的constructor还是指向Person1;如果这个person1实例是在原型对象重写之后新建的话,那么constructor就不会指向Person1了。

    这是跟写法有关的,如果我们换一种写法:

    function A(){};
    function B(){};
    A.prototype = new B();
    var a = new A();
    
    A.prototype.constructor === B; //true
    a.constructor === B; //true
    a.__proto__ === A.prototype; //true
    A.prototype.__proto__ === B.prototype; //true
    a.__proto__.__proto__ === B.prototype; // true

    此时的A.prototype已经被重写了,所以a和A原型对象的constructor属性都指向B,而不是A(这里不用看a,只看A的原型对象的constructor指向哪个,a是跟它一致的);所谓的原型继承就是A的原型对象会继承构造函数B的原型对象的所有方法和属性。原型继承都是原型对象继承原型对象。

    默认的原型:
    所有引用类型都继承了Object的方法,这也是通过原型链来实现的,所有函数的原型对象默认都是Object的实例,当然也会继承,所以上面的代码可以接着写:

    B.prototype.__proto__ === Object.prototype; // true;
    var b = new B();
    b.__proto__.__proto__ === Object.prototype; //true;
    a.__proto__.__proto__.__proto__ === Object.prototype; // true
    
    b.constructor === B; // true
    B.prototype = new Object();
    b.constructor === B; // true
    B.prototype.constructor === B; //false
    B.prototype.constructor === Object; //true
    
    var c = new B();
    c.constructor === Object; //true

    这里的结果解释同上,如果重写了prototype,则直接指向上一层原型

    原型链的两个问题:
    1,之前我们提到了如果原型属性中有引用类型的值被实例修改了,那么所有的实例都会被改变,但是如果是构造函数里面定义的属性,不管是引用类型还是基本类型,怎么修改都不会改变。但是如果是通过原型来继承,那么构造函数里的属性就会变成下一个原型的属性,所以如果修改它,还是会影响到下一个原型的实例。一个例子说明:

    function SuperType(){
        this.colors = [‘red’,’blue’,’yellow’];
    }
    function SubType(){
    }
    SubType.prototype = new SuperType();
    var instance1 = new SubType();
    instance1.colors.push(‘black’);
    instance1.colors; //‘red’,’blue’,’yellow’,’black’
    var instance2 = new SubType();
    instance2.colors; //‘red’,’blue’,’yellow’,’black’


    上面的instance1修改的属性虽然是SuperType的构造函数里的属性,但是此时已经继承给了SubType的原型了,所以相当于修改了原型的引用类型的属性,因此所有的实例都会被修改

    2,在创建子类型的实例的时候,无法向超类型的构造函数传递参数,因为会影响到所有的实例。主要是引用类型的属性的修改

    =============


    2,借用构造函数
    针对上面的两个问题,用一种借用构造函数的方法可以暂时避免:

    function SuperType(){
        this.colors = [“red”,”blue”,”green”]
    }
    function SubType(){
        SuperType.call(this);
    }
    var instance1 = new SubType();
    instance1.colors.push(‘black’);
    instance1.colors; //‘red’,’blue’,’green’,’black’
    var instance2 = new SubType();
    instance2.colors; //‘red’,’blue’,’green’

    这里的instance2没有收到影响,因为这里相当于把SuperType里的属性继承过来(复制),每一个新生成的实例都有一个colors的副本,所以互不影响。

    但是也有一个问题,所有定义的属性都要在构造函数SubType里定义,这样就没有函数的可复用性了,看下一个例子:

    function SuperType(name){
        this.name = name
    }
    function SubType(){
        SuperType.call(this, ‘Nical’);
        this.age = ’29’
    }
    var instance = new SubType();
    instance.name; //Nical
    instance.age; //29

    在这里我无法给SubType传参,返回的name和age都是统一的,如果要修改只能在构造函数里修改,这样太麻烦了,所以我们很少用这个方法


    ==============

    3,组合继承
    针对上面的问题,我们将原型链和借用构造函数两个方法结合,可以得到我们想要的:

    function SuperType(name){
        this.name = name;
        this.colors = [“red”,”blue”,”green”]
    }
    SuperType.prototype.sayName = function(){
        alert(this.name)
    }
    function SubType(name, age){
        SuperType.call(this, name);
        this.age = age;
    }
    SubType.prototype = new SuperType();
    SubType.prototype.constructor = SubType;
    SubType.prototype.sayAge = function(){
        alert(this.age)
    }
    var instance1 = new SubType(‘Nical’, 29);
    instance1.colors.push(‘black’);
    instance1.colors; //‘red’,’blue’,’green’,’black’
    instance1.sayName; // ‘Nical’
    instance1.sayAge; // 29
    var instance2 = new SubType(‘Greg’, 21);
    instance2.colors; //‘red’,’blue’,’green’
    instance2.sayName; // ‘Greg’
    instance2.sayAge; // 21

    这个方法是最常用的⭐️⭐️⭐️⭐️⭐️⭐️⭐️⭐️

    2018年2月9号补充:

    这里为什么把sayName挂到prototype原型上会更好,而不是仅仅跟上面的属性一样,使用this.sayName = function(){},这样看起来好像生成的每个实例,引用sayName的时候函数体都是一样的,但实际上每次生成一个新实例,就会新生成一个sayName方法去占用内存,(不信可以生成两个实例,然后看一下二者的sayName方法是否全等),所以如果生成多个实例,就会占更多的内存,因此必须挂到prototype上去,这也是为什么像String类型的那么多方法属性会挂在原型上,而不是直接挂在构造器内,不然内存就炸了。为什么原型上面挂方法,生成的实例不会重复占内存呢,因为指向的是同一个内存地址。

    2018年3月1号补充:

    再次理解继承我发现了一个问题:

    如果使用原型继承,就是上面第一个例子,使用A.prototype = new B(),不使用call方法,缺点很明显,如果B中的属性有引用类型的,此时用A生成一个实例a,再多这个a修改这个引用类型的属性,那么就会影响到其它实例的该属性,因为相当于直接修改了原型上的属性,但是前提是这个属性是引用类型的,如果不是引用类型,则不变,这个在上面某一段的补充也提到了。

    解决引用类型的属性可能会被修改,作者提出了构造函数继承,就是不包括prototype,仅仅靠call方法来实现继承:function A(){ B.call(this) },但是这个继承仅仅是将构造函数B的属性复制了一遍,不管你属性是基本类型还是引用类型,用A生成的实例a可以随意修改,都不会影响到其它实例,因为影响不到它的原型。

    看到这里我挺惊讶的,如果是这样的话,那为什么要用prototype呢,我都用call方法不是很简单么,于是我看到了楼上的补充,如果构造函数内所有的引用类型的属性都用构造函数模式来定义,那么每次调用的时候,实际上都会生成一个内存地址,举个很简单的例子:

    function A(){
        this.name = 'aa';
        this.arr = [1,2];
    };
    
    var a = new A(),
         b = new A();
    
    a.name === b.name;        // true
    a.arr === b.arr;        // false
    

    因此我们必须用prototype来避免这种情况的发生,楼上那个补充还不完善,准确的说,所有的引用类型的属性都必须挂到prototype上,包括数组这种,防止生成众多的多存。

    考虑到避免上面这种性能损耗,我们就不能仅仅使用call方法了,因为只用call方法只能复制构造函数内的属性,不能复制到它的原型对象上挂载的那些引用类型的属性,因此我们必须将两者结合,基本类型的属性,我们用call复制的方式实现继承,而引用类型的属性,我们就用prototype原型继承,这才是最佳方案!!!

    写到这儿,不得不佩服作者的造诣以及JavaScript语言的魅力。  

    ==========

    4,原型式继承

    function object(o){
        function F(){};
        F.prototype = o;
        return new F();
    }


    ES5有一个方法:Object.create()方法,等同于上面这个,这里的o是一个原型对象。

    ==========

    5,寄生式继承

    function createAnother(original){
        var clone = object(original);
        clone.sayHi = function(){
            alert(“hi”)
        };
        return clone
    }    

    ==========

    6,寄生组合式继承
    组合继承的缺点是,有两次调用超类型构造函数:第一次,SubType.prototype = new SuperType()会调用一次;第二次,在子类型的构造函数里写SuperType.call(this, name)。调用两次的后果是每一次调用都会重新创建实例属性,自动屏蔽原来的属性,具体的说,就是第一次调用的时候,会生成SuperType构造函数的属性,比如有N个,而第二次调用的时候,又会生成N个属性,而这两次的N个属性实际上是一样的,但是后者却覆盖了前者,这完全是没必要重复生成的。所以用寄生组合式继承会避免这种情况,(说白了,只是为了避免第二次重复定义属性,解决方案两种,一种是去掉第一个,一种是去掉第二个,综合考虑,去掉第一次new SuperType那次)

    
    
    function object(o){
        function F(){};
        F.prototype = o;
        return new F();
    }
    function inheritPrototype(subType, superType){
        var prototype = object(superType.prototype);    //创建对象,这里要注意object()是上面提到的原型式继承,用于复制超类原型的副本,这边也可以用Object.create()
        prototype.constructor = subType;    //增强对象
        subType.prototype = prototype;    //指定对象
    }

     其实上面的inheritPrototype函数也可以这么写

    function inheritPrototype(subType, superType) {
        var prototype = Object.create(superType.prototype);
        subType.prototype = prototype;
        subType.prototype.constructor = subType;
    }

    当然还是以书上为主,我这么写是便于理解,书上的写法更规范点,其实就是在子类的原型上添加一个constructor属性,并指向它自己,所以一样。

    例如:

    function SuperType(name){
        this.name = name;
        this.colors = [“red”,”blue”,”green”]
    }
    SuperType.prototype.sayName = function(){
        alert(this.name)
    }
    function SubType(name, age){
        SuperType.call(this, name);
        this.age = age;
    }
    
    inheritPrototype(SubType, SuperType);
    
    SubType.prototype.sayAge = function(){
        alert(this.age)
    }

    寄生组合式继承是引用类型最理想的继承范式。⭐️⭐️⭐️⭐️⭐️

    =============================

    2018年7月9日补充:

    想起之前一道面试题,一个基本类型的值,有属性吗?我如果给它强行赋一个属性会怎样?既然没有属性,为什么我能用toString或者valueOf呢?

    首先,基本类型的值是没有属性的,如果给它赋值,不会报错,但是无法再次访问到该属性,之所以能访问到这些方法是因为下面

    var a= 123;
    
    a.toString();    // '123'
    
    a.constructor;    // ƒ Number() { [native code] }
    
    a.constructor.prototype === Number.prototype;    // true
    
    Number.prototype.__proto__ === Object.prototype;    // true
    
    a.__proto__.__proto__ === Object.prototype;        // true 

    可见,a的构造函数是Number,Number的原型又是继承Object的原型的,因此Number构造函数所创建的实例上面会有Object原型上的方法,我们打印一下Object的原型:

    看到了,有toString和valueOf方法,因此a上面可以直接使用该方法。

    ===============

     

  • 相关阅读:
    RDD的基本命令
    python 数据类型
    RDD基础
    sql优化
    python文件操作
    Python之xlsx文件与csv文件相互转换
    ValueError: Some of types cannot be determined by the first 100 rows, please try again with sampling
    python
    python操作dataFrame
    python 列表,元祖,字典
  • 原文地址:https://www.cnblogs.com/yanchenyu/p/7404661.html
Copyright © 2020-2023  润新知