浅谈JavaScript构造函数和原型

 

1.说到原型我们先来看看什么是构造函数

function Fn(name) {
    this.name = name;
    this.fn1 = function () {
        console.log("哈哈");
    }
}

这是一个很简单的构造函数,

来看看怎么创建一个实例

var a1 = new Fn("张三");

就是使用这个new来将构造函数实例化

那么new做了什么?

var obj = {};
obj.name = "张三";

上面是一个obj对象,我们打印出来看看

再来看看构造函数创建的那个实例对象

是不是长得很像?

可以简单理解为这个new就是创建了一个obj对象 ,然后再赋值给了构造函数中的this,所以可以采用this点xxx的方式赋值,最后返回这个obj对象。说到返回值便拿出来说一说注意点

    function Fn(name) {
        this.name = name;
        this.fn1 = function () {
            console.log("哈哈");
        };
        return "啦啦啦";
    }
    var a1 = new Fn("张三");
    console.log(a1);

上面添加了一个return返回“啦啦啦”,但是打印出来的a1还是和上面一个所有返回值内行没有影响

    function Fn(name) {
        this.name = name;
        this.fn1 = function () {
            console.log("哈哈");
        };
        return {
            age:"18"
        };
    }
    var a1 = new Fn("张三");
    console.log(a1);

这次返回的是一个引用类型,返回值直接就改变了,所以:

在JavaScript构造函数中:
如果return值类型,那么对构造函数没有影响,实例化对象返回空对象;
如果return引用类型(数组,函数,对象),那么实例化对象就会返回该引用类型;

 

 

上面的构造函数是不是觉得还行?但是我们用这个构造函数创建实例就会反复创建fn1这个方法造成浪费

function Fn(name) {
    this.name = name;
    this.fn1 = function () {
        console.log("哈哈");
    }
}

var a1 = new Fn("张三");
var a2 = new Fn("李四");
var a3 = new Fn("王五");
var a4 = new Fn("陈六");

改良上面代码

function Fn(name) {
    this.name = name;
    this.fn1 = fn1;
}

var fn1 = function () {
    console.log("哈哈");
};

这次改良后的代码解决了反复创建fn1的浪费问题,但是如果这个构造函数需要添加很多方法的时候就会造成全局变量污染

function Fn(name) {
    this.name = name;
    this.fn1 = fn1;
    this.fn2 = fn2;
    this.fn3 = fn3;
    this.fn4 = fn4;
}

var fn1 = function () {
    console.log("哈哈");
};

var fn2 = function () {
    console.log("呵呵");
};

var fn3 = function () {
    console.log("哦哦");
};

var fn4 = function () {
    console.log("嗯嗯");
};

 

所以我们使用原型再次改良代码

function Fn(name) {
    this.name = name;
}

Fn.prototype.fn1 = function () {
    console.log("哈哈");
};

Fn.prototype.fn2 = function () {
    console.log("呵呵");
};

Fn.prototype.fn3 = function () {
    console.log("哦哦");
};

Fn.prototype.fn4 = function () {
    console.log("嗯嗯");
};

 

2.什么是原型

我们先理解为,每一个构造函数都有一个原型(免费送的不要都不行),构造函数有一个prototype属性指向这个原型

构造函数的实例有一个内部属性也指向原型对象,实例对象能够访问原型对象上的所有属性和方法

来看一个小例子,直接改变prototype的指向会发生什么?

<script>
    function Fun(name) {
        this.name = name;
    }
    fun.prototype.age = '18';
    var a1 = new Fun("张三");
    console.log("改之前a1:",a1.age);
    fun.prototype = {
        age:'20'
    }
    var a2 = new Fun("李四");
    console.log("改之后a1:",a1.age);
    console.log("改之后a2:",a2.age);
</script>

运行结果

改之前a1: 18
改之后a1: 18
改之后a2: 20

为什么改之后a1还是18?我们不是改变了prototype的指向了吗?来看看图分析

得出结论:

替换之前的实例还是指向的以前的原型,只有替换之后创建的实例才指向新原型

原型可以直接替换,那么能用实例直接更改原型中的值吗?

<script>
    Function fun(name) {
        this.name = name;
    }
    fun.prototype.age = '18';
    var a1 = new Fun("张三");
    console.log("改之前a1:",a1.age);
    a1.age = "20";
    var a2 = new Fun("李四");
    console.log("改之后a1:",a1.age);
    console.log("改之后a2:",a2.age);
</script>

运行结果

改之前a1: 18
改之后a1: 20
改之后a2: 18

我们不是通过a1.age = "20"改变了原型中的属性值吗,怎么下面a2.age还是以前的18?

1.实例先再自己身上找有没有age这个属性,如果没有就去原型中找
2.点语法,在没有这个点后的属性时是创建。

根据第二点可知,a1.age = '20',其实是在实例a1中创建了一个新的属性并赋值为20,并没有更改原型中的属性值,所以后面创建的a2的age还是为18
根据第一点可知,实例先在自身上找,上面添加了age属性所有已经有了,就根本不会去原型中寻找,所以a1.age的值为20

画图解释

结论

无法通过实例更改原型中的值类型属性

我们将上诉例子改为引用类型又会发生什么啦?

<script>
    function Fun(name) {
        this.name = name;
    }
    Fun.prototype.quote = {
        age:"18"
    };
    var a1 = new Fun("张三");
    console.log("改之前a1:",a1.quote.age);
    a1.quote.age = "20";
    var a2 = new Fun("李四");
    console.log("改之后a1:",a1.quote.age);
    console.log("改之后a2:",a2.quote.age);
</script>

运行结果

改之前a1: 18
改之后a1: 20
改之后a2: 20

我们发现原型中引用类型的值修改成功了??

先上图解释

结论

通过实例不能更改原型中的值类型的属性值,但是却可以更改原型中引用类型中属性的属性值

注意是改变引用类型属性的属性值,而不是直接改变引用类型的值,如果直接改变引用类型的值还是和上面一样会直接在实例中添加这个新的属性

a1.quote = {
        age:"20"
    };

并不会改变原型中的值,只会在实例中添加一个新的,只有a1.quote.age = "20"才能直接改变原型中引用类型属性的属性值

上面的只是例子,一般情况下我们不会将属性放到原型对象中,原型中只会放置需要共享的方法

 

如何访问原型

1.通过构造函数访问原型

Fun.prototype

2.通过构造函数访问原型

a1.__proto__

__proto__是一个非标准的属性,尽量只在调试中使用,不要直接出现在代码中

 

更新一下指向图

 

constructor

原型在创建出来的时候,会默认的有constructor这个属性

这个属性指向原型对应的构造函数

 打印一下prototype看看有没有constructor

    function Fun(name) {
        this.name = name;
    }
    Fun.prototype.quote = {
        age:"18"
    };
    console.log(Fun.prototype);

结果

来看看constructor究竟是什么

    function Fun(name) {
        this.name = name;
    }
    Fun.prototype.quote = {
        age:"18"
    };
    //打印constructor
    console.log(Fun.prototype.constructor);
    //正常创建实例
    var a1 = new Fun("张三");
    //用constructor创建
    var a2 = new Fun("李四");
    console.log("a1:",a1.name);
    console.log("a2:",a2.name);

结果

我们发现constructor就是构造函数

我们在第一个例子中改变了prototype的指向,现在来看看到底改变了什么

    function Fun(name) {
        this.name = name;
    }
    Fun.prototype.age = "18";
    console.log("改变前");
    console.log(Fun.prototype);
    console.log(Fun.prototype.constructor);
    Fun.prototype = {
       age:"20"
    };
    console.log("改变后");
    console.log(Fun.prototype);
    console.log(Fun.prototype.constructor);

结果

结论

改变前prototype中有constructor属性
改变后prototype没有constructor属性

改变前constructor指向默认的构造函数
改变后constructor指向Object

采用这种方式添加更改原型属性,为了保证整个的合理性,我们应该手动添加上constructor属性

Fun.prototype = {
        constructor:Fun,
        age:"20"
    };

结果

什么是原型:
在构造函数创建出来的时候,系统会自动创建并关联一个对象,这个对象就是原型对象
默认的原型对象中会有一个属性constructor指向构造函数

原型的作用:原型中的对象可以被它指向的构造函数所创建出来的实例共享

注意:替换原型对象后创建的实例指向和替换前的指向不同

 

  • 9
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值