关闭

Javascript:原型编程思想带来的2个好处

标签: javascriptprototype
519人阅读 评论(3) 收藏 举报
分类:

作者:vuefine


需要思考的问题
JavaScript最重要的一个属性非prototype莫属了,那么prototype在JavaScript中到底起了什么作用呢?没有它,不行吗?在传统的基于Class的语言如Java、C#、C++中,继承的本质是扩展一个Baseclass,得到新的Subclass。由于这类语言严格区分实例,继承实际上是出现新的类型。JavaScript没有严格区分类和实例,那么它是如何做到继承呢?

1解答prototype在JavaScript中到底起了什么作用呢?没有它,不行吗?
首先看一下原型编程思想,如果不采用原型编程思想,JavaScript又是什么样子呢?

//定义Animal函数:2属性,1个方法
function Animal(props) {
    this.name = props.name;
    this.birthYear = props.birthYear;
    //得到Animal的年龄
    this.getAge = function () {
        return new Date().getFullYear() - this.birthYear;
    }
}

let birdLiu=new Animal({name:'刘鸟',birthYear:'2006'}); //采用new表明是一个函数(‘类’)对象
console.log(birdLiu.getAge());
let sheepGuo=new Animal({name:'郭洋',birthYear:'2010'});
console.log(sheepGuo.getAge());

做一个测试:

birdLiu.getAge===sheepGuo.getAge; //false

这表明是什么意思呢?birdLiu和sheepGuo各自的getAge是一个函数,但它们是两个不同的函数,虽然函数名称和代码都是相同的! 如果我们通过new Animal()创建了很多对象,这些对象的getAge函数实际上只需要共享同一个函数就可以了,这样可以节省很多内存

那么如何解决这个问题呢? JavaScript采取了原型编程的思想去解决这个问题。简单说,在原型对象上赋值属性prototype,面向这个prototype编写原型方法等,然后通过new()出来的对象都统一走这个prototype上的定义的方法。

下面这行代码,测试了刚才的原理。

birdLiu.__proto__.getAge===sheepGuo.__proto__.getAge; //true:birdLiu和sheepGuo都指向了同一个getAge

这样,我们把上面的直接在Anmial中写的getAge()放到JavaScript提供的Prototype这个原型属性上,

//定义Animal函数(构造函数):2属性
function Animal(props) {
    this.name = props.name;
    this.birthYear = props.birthYear;
}

//Animal的原型prototype上定义获得Animal的年龄方法:getAge()
Animal.prototype.getAge=  function () {
        return new Date().getFullYear() - this.birthYear;
    }

再测试2个新建对象是不是指向了共同的getAge():

//采用new表明是一个函数(‘类’)对象
let birdLiu=new Animal({name:'刘鸟',birthYear:'2006'}); 
let sheepGuo=new Animal({name:'郭洋',birthYear:'2010'});
let b1 =birdLiu.getAge===sheepGuo.getAge; //true

为什么将getAge放到prototype上,两个new出来的对象指向了同一个getAge方法了呢?这个是如何做到的呢?

谈一下我对这个问题的一点理解,因为Animal(props)是一个构造函数,在创建birdLiu和sheepGuo时,通过操作new()之后,birdLiu指向了内存地址address1,地址内容分别是name:刘鸟,birthYear:2006;并且在内存地址address3上创建了prototype,保存了getAge函数地址,sheepGuo指向了内存地址address2,地址内容分别是name:郭洋,birthYear:2010;并且通过Animal.prototype找到了address3,进一步定位到了getAge函数地址。这样birdLiu和sheepGuo共享了getAge()。

2 JavaScript没有严格区分类和实例,那么它是如何做到继承呢?
现在想继承Animal,得到一个Bird对象。先定义一个Bird对象。

function Bird(props){
    Animal.call(this,props);
    this.flySpeed=props.flySpeed; //扩展一个鸟的飞行速度这个属性
}

实现对Animal的继承,创建一个纽带函数F,将纽带函数的prototype属性赋值为Animal.prototype,Bird的prototype属性重新赋值。

//纽带函数
function F(){

}

//bird继承于Animal的实现:
F.prototype=Animal.prototype;//纽带函数的prototype属性赋值为Animal.prototype
//Bird的prototype属性重新赋值,值为一个F对象,
//并且F对象的原型为Animal.prototype,同时也等于Animal.prototype。
Bird.prototype = new F(); 

//bird原型上扩展一个飞行的方法
Bird.prototype.fly= function(){
    console.log(this.name+ ' : 飞行速度为'+this.flySpeed);
}

//注意观察,Bird的prototype=new F()后,
//Bird.prototype.constructor也被破坏了,因此需要修复一下:
Bird.prototype.constructor=Bird;

//这样Bird完成了对Animal的继承

测试Animal上的getAge()是不是被Bird拿过来了,同时自己的个性方法是不是也有了:

let maque=new Bird({name:'麻雀张',birthYear:'2016', flySpeed:'15'});
console.log(maque.getAge()+'岁');
console.log(maque.fly());

测试结果:

1岁
麻雀张 : 飞行速度为15

如果把继承这个动作:

//bird继承于Animal的实现:
//纽带函数的prototype属性赋值为Animal.prototype
F.prototype=Animal.prototype;
//Bird的prototype属性重新赋值,值为一个F对象
//并且F对象的原型为Animal.prototype,同时也等于Animal.prototype。
Bird.prototype = new F(); 
//注意观察,Bird的prototype=new F()后
//Bird.prototype.constructor也被破坏了,因此需要修复一下:
Bird.prototype.constructor=Bird;
//这样Bird完成了对Animal的继承

封装到一个inherits()函数中,还可以隐藏F的定义

function inherits(Child, Parent) {
var F = function () {};
F.prototype = Parent.prototype;
Child.prototype = new F();
Child.prototype.constructor = Child;
}

完整的代码实现为:

function Animal(props) {
    this.name = props.name;
    this.birthYear = props.birthYear;
}

//得到Animal的年龄
Animal.prototype.getAge=  function () {
        return new Date().getFullYear() - this.birthYear;
    }

function Bird(props){
    Animal.call(this,props);
    this.flySpeed=props.flySpeed; //扩展一个鸟的飞行速度这个属性
}

function inherits(Child, Parent) {
    var F = function () {};
    F.prototype = Parent.prototype;
    Child.prototype = new F();
    Child.prototype.constructor = Child;
}

inherits(Bird,Animal);

//bird原型上扩展一个飞行的方法
Bird.prototype.fly= function(){
    console.log(this.name+ ' : 飞行速度为'+this.flySpeed);
}

//测试一下:

let maque=new Bird({name:'麻雀张',birthYear:'2016', flySpeed:'15'});
console.log(maque.getAge()+"岁");
console.log(maque.fly());

第2部分小结

JavaScript的原型继承实现方式就是:

定义新的构造函数,并在内部用call()调用希望“继承”的构造函数,并绑定this;

借助中间函数F实现原型链继承,最好通过封装的inherits函数完成;

继续在新的构造函数的原型上定义新方法。

JavaScript的对象模型是基于原型实现的,特点是简单,缺点是理解起来比传统的类-实例模型要困难。

好消息是ES6正式将关键字class引入进来,使得定义类更简单。

因此将代码(完整的代码实现部分)改写后:

class Animal {
    constructor(props) {
        this.name = props.name;
        this.birthYear = props.birthYear;
    }
    getAge() {
        return new Date().getFullYear() - this.birthYear;
    }
}

class Bird extends Animal {
    constructor(props) {
        super(props);
        this.flySpeed = props.flySpeed; //扩展一个鸟的飞行速度这个属性
    }
    fly() {

        console.log(this.name + ' : 飞行速度为' + this.flySpeed);
    }
}

的确是简单了很多,那么它们是不是又重新设计另一个思想呢,不是的,新引入的class等实际上是封装了上面说的比较麻烦的那种实现,class在后台解析后还是麻烦的样子,只不过我们用class就看不到那些多余的代码了。

测试一下:


let maque = new Bird({ name: '麻雀张', birthYear: '2016', flySpeed: '15' });
console.log(maque.getAge() + "岁"); //1岁
console.log(maque.fly());//麻雀张 : 飞行速度为15

总结:

JavaScript采用原型编程,所有对象都能共享原型上的方法,节省内存;同时基于原型这一实现思想,JavaScript通过找对原型链,方便地实现了继承。这就是原型编程带来的2个最大好处!!!

2
0

查看评论
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
    Leetcode Solutions in Github

    Click me

    Leetcode solutions in Github, including tags: Array, HashTable, Stack, Queue, LinkedList, Tree, Graph,...

    leetcode-manager

    Click me

    Current Leetcode manager: it's used by Entity Framework, WinForms, Sql Server to manage leetcode questions that we have done. Functions include tags creation, revise and deletion, and questions creation, edit and deletion.

    个人资料
    • 访问:126187次
    • 积分:5037
    • 等级:
    • 排名:第5927名
    • 原创:311篇
    • 转载:0篇
    • 译文:0篇
    • 评论:53条
    博客专栏