构造函数、原型

构造函数

构造函数是一种特殊的函数,主要用来初始化对象,即为对象成员赋初始值,总是与new一起使用。我们可以把对象中一些公共的属性和方法抽取出来,然后封装到这个函数里面。

构造函数采用大驼峰命名规则,即首字母也要大写。

构造函数中成员(属性和方法)分为两种:

  • 静态成员:构造函数本身添加的成员,只能通过构造函数访问。(sex)
  • 实例成员:构造函数内部通过this添加的成员,只能通过实例对象访问。(name、age、say)
    function Person(uname,age){  //定义构造函数
        this.uname = uname;
        this.age = age;
        this.say = function(){
            console.log('hey~');
        }
    }
    var tom = new Person('tom',18);
    var andy = new Person('andy',19);
    console.log(tom.age); //18
    andy.say(); //hey~
    Person.sex = 'male';
    console.log(Person.sex); //male
    console.log(tom.sex);  //undefined 不能通过实例访问静态成员
    console.log(Person.uname); //undefined 不能通过构造函数访问实例成员

new在执行使发生了什么:

  1. 在内存中创建一个新的空对象;
  2. 让this指向这个对象;
  3. 执行构造函数内的代码,为这个新对象添加属性和方法;
  4. 返回这个对象(所有构造函数里面不需要return);

若在构造函数内部添加return;或者return 简单数据;会自动忽略,但若return 对象;这个对象内的成员就会替换构造函数内的成员。比如,上述代码中构造函数内添加return {age:6};那么它所有的实例成员都只有一个属性age=6;

空间浪费
在通过上述构造函数创建两个实例的过程中,因为每个实例都有一个方法,所有会为各自单独开辟一块空间来存放这个方法,问题就是造成了空间的浪费,因为方法是相同的,却有两个地址,占用了两块空间。

  console.log(tom.say === andy.say); //false

这个问题的解决,就用到构造函数原型对象 prototype


构造函数原型对象 prototype

prototype属性指向另一个对象,这个prototype本身也是一个对象,它的所有属性和方法都被构造函数所拥有,也就是可以被构造函数的实例访问。prototype的主要作用是共享方法

为prototype添加方法

构造函数.prototype.方法名 = function() {...}

上面的代码就可以变成:

    function Person(uname,age){
        this.uname = uname;
        this.age = age;
    }
    Person.prototype.say = function(){ //将方法添加到prototype中实现共享
        console.log('hey~');
    }
    var tom = new Person('tom',18);
    var andy = new Person('andy',19);
    console.log(tom.age); //18
    console.log(tom.say === andy.say); //false
    andy.say(); //hey~

这时再来验证:

    console.log(tom.say === andy.say); //true

可见方法已经实现共享,每个实例都可以访问这个方法。

那实例对象是如何访问到prototype上方法的呢?

因为在每个对象身上都有一个 _proto_ 属性,指向构造函数的原型对象prototype,所有可以使用原型对象的方法。

    console.log(tom.__proto__ === Person.prototype); //true

方法查找规则:
现在自身查找是否有这个方法,有则调用自身方法,没有则沿着 去_proto_指向的prototype身上去查找。


constructor属性

_proto_ 和 prototype 中都有这个属性,它指向构造函数本身,记录实例对象是通过哪个构造函数创建的。

经常需要手动利用constructor属性指回原来的构造函数。

为prototype添加方法还有第二种形式,适用于方法较多时

构造函数.prototype = {方法1:...,方法2:... }

但这种形式会有一个问题:

  • 第一中方法输出tom.\__proto__.constructor/Person.prototype.constructor
    {say: ƒ, constructor: ƒ}
    say: ƒ ()
    constructor: ƒ Person(uname,age)
    _proto_: Object
  • 第二种方法输出tom.\__proto__.constructor/Person.prototype.constructor
    {say: ƒ}
    say: ƒ ()
    _proto_: Object

因为对象赋值的形式会覆盖掉prototype中原有的属性方法,包括指向构造函数的constructor属性也没有了,所有手动添加 constructor属性使其指向原来的构造函数。

原型链


数组也是对象,Array.prototype.proto === Object.prototype。
绝大多数对象的最终都会继承自Object.prototype,一个例外是:
通过Object.create(null);创建的对象。

总结:

  1. 构造函数都有一个原型对象,如Array.prototype,原型对象上的方法可以被构造函数的实例对象共享
  2. 构造函数的实例对象上都有一个指针proto,指向构造函数的原型对象,如:[].proto === Array.prototype
  3. 原型对象上都有一个指针可以指回构造函数,如:Array.prototype.constructor === Array,[].constructor === Array
  4. 构造函数本身是一个函数,是Function构造函数的实例对象,如:Array.proto === Function.prototype,然后你会发现 Function.proto === Function.prototype 也是true;
  5. 数组或是函数都是对象,所以:Array.prototype.proto === Object.prototype, [].proto.proto === Object.prototype
    这就是原型链,好像还挺神奇的
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。