让前端飞JavaScript 进阶营

关于Android工程师转vue的三两事儿(10)--原型与原型

2018-06-20  本文已影响14人  KlivitamJ

说起原型和原型链接,着实让我这个前端菜鸟胡搞了好一阵子。虽然有点绕口的缘故,但是更多的还是自己比较浮躁带来的后果,这一块据说是前端的基础,看了很多遍才差不多有点头目。分享一下我领悟到的武林秘籍,希望能给您带来一点启迪,如果存在任何问题,请及时指正我,谢谢。😊😊😊😊

一、 浅谈数据属性和访问器属性

1. 创建对象:

通常创建对象一般都会有两种方法:

//利用object来创建对象
var person = new Person();
person.name = "klivitam";
person.age  = 23;

person.sayName = function(){
  alert(this.name)
}
// 对象字面量法,推荐使用这种方法
var person = {
  name:"klivitam",
  age:23,
  
  sayName: function(){
    alert(this.name);
  }
}

2. 属性类型

在javascript中,对象的属性一共分为两种:数据属性和访问器属性。

// "use strict"
var worker = {}
Object.defineProperty(worker, "job", {
    writable: false,
    value: "码农"
})
console.log(worker.job)
worker.job = "教师"
console.log(worker.job)
writable=false

当把writable的属性改成true的时候,


writable = true 从上面的代码可以看出来writable是用来控制是否能修改属性的值。另外当writable为false的时候,并且使用严格模式下,会发生: 严格模式下,writable为false的时候修改值会报错
// "use strict"
var worker = {}
Object.defineProperty(worker, "job", {
    // writable: false,
    configurable:false,
    value: "码农"
})
console.log(worker.job)
delete(worker.job)
console.log(worker.job)

当configurable为false的时候,使用delete方法会失效,并且在严格模式下,delete会报错。同理改成true的时候,则为undefined,说明删除成功了。

configurable为false configurable为true 注意:当value的值没初始化的时候,默认放置undefined

至于最后一个我觉得就没必要代码进行演示了,同理可得。


特征默认值
var worker = {
    _job:"码农",
    age: 23
}
Object.defineProperty(worker,"job",{
    get:function(){
        return this._job;
    },
    set:function(newJob){
        if(newJob!==this._job){
            this._job = newJob;
            this.age ++
        }
    }
})
console.log(Object.getOwnPropertyDescriptor(worker,"job"));
console.log(worker.job)
worker.job = "教师"
console.log(worker.job)
console.log("更换职业就变老一年,5555~:"+worker.age)
现实的实例

二、 js设计模式

1、 工厂模式

工厂模式是一个很基础的一个模式吧,反正我在学java (android)的时候经常会遇到这种模式,主要是抽象了创建对象的具体过程。具体的代码如下:

//屌丝程序员,只能偶尔意淫一哈 = l =,别喷我 
function addBeatiGrilWx(name,age,job){
    var gril = new Object();
    gril.name = name;
    gril.age = age;
    gril.job = job;
    gril.sayHi = function(){
        console.log("hi! " + this.name)
    };
    return gril
}
var lyf = addBeatiGrilWx("liuyifei",18,"actor");
lyf.sayHi();
console.log(lyf);
效果如下 工厂模式效果图

工厂模式虽然解决了创建多个相似对象的问题,但是却没有解决对象识别的问题(即不清楚一个对象的类型),于是出现了构造函数模式。

2、 构造函数模式

构造函数可以用来创造特定类型的对象。具体的代码如下

function BeautiGril(name,age,photo){
    this.name = name;
    this.age = age;
    this.photo = photo;
    this.sayHi = function(){
        console.log("hi! "+this.name)
    }
}
var lyf = new BeautiGril("liuyifei",18,"baidu");
lyf.sayHi();
console.log(lyf)
console.log(lyf.constructor == BeautiGril)
console.log(lyf instanceof BeautiGril)
console.log(lyf instanceof Object)
具体的效果如下: 效果图

创建自定义的构造函数意味着将来可以将它的实例标识为一种特定的类型,这也是前面提到他相较于工厂模式的优势。
构造函数模式看似很好,但是也存在一个问题,就拿上面的代码来说如果我创建多个实例,不止lyf(毕竟还是想多要几个小姐姐的 咳咳)一个。而此时sayHi这个公共的方法就会被多次重复创建。这样其实不太可取的,如果把sayHi方法放置出去,

function BeautiGril(name,age,photo){
    this.name = name;
    this.age = age;
    this.photo = photo;

}
function sayHi(){
    console.log("hi! "+this.name)
}

那么相当新建了一个全局方法,这样岂不是更加的没有必要了么?此时就要引入到原型模式了。

3. 原型模式

我们创建的函数都有prototype(原型)属性,这个属性是指针,指向一个对象,而这个对象的用途是包含由特定类型的所有实例所共享的属性和方法,使用原型对象就可以让所有实例对象均包含这些属性及方法。

function Worker(){}
Worker.prototype.name = "programmer";
Worker.prototype.work = "programming...";
Worker.prototype.heartSound = function(){
    console.log(this.name+" want rest,but he still "+this.work)
}

var xiaoZhang = new Worker();
xiaoZhang.heartSound();

var xiaoWang = new Worker();
xiaoWang.heartSound();

console.log(xiaoZhang.heartSound ==xiaoWang.heartSound)

这里我还是说一下,我将heartSound()方法和所有的属性直接添加到了Woker的原型属性中,然后通过new创建对象,在原型模式中这些属性和方法对于所有的实例是共享的。
但是这里存在有点问题--那就是并不是所有的worker都是程序员。这就引发了最后一种模式的混用。

3. 原型模式+构造器模式

这个模式在我的理解上来说,主要是为了避免单独用原型模式所带来弊病,就拿上一份代码来说,并不是所有的worker都是程序员,如果想一个医生想去复用这个类的时候,就必须改变其原型上的值,如果改变其原型的值,那么整个都会乱套了。于是我想到构造器模式

function Worker(name,work){
    this.name = name;
    this.work = work;
}
Worker.prototype.heartSound = function(){
    console.log(this.name+" want rest,but he still "+this.work)
}

var xiaozhang = new Worker("programmer","programmer....");
xiaozhang.heartSound(); // programmer want rest,but he still programmer....

//医生也需要休息
var xiaomei = new Worker("doc","sos");
xiaomei.heartSound(); // doc want rest,but he still sos

三、 原型链

前面也差不多谈到了原型这个概念,什么叫原型呢?其实我有一个不太好,但是又很恰当的例子来描述这些个概念(看嗯哼家小狗、小猫想到的):

其实有了上面的一个基本的了解之后,我们再来一步一步写代码就会比较容易了。

{
    function Dog(name){
        this.name = name
    }
    Dog.prototype.action = function(){
        console.log(this.name+" wang..");
    }

    let xiaogou1 = new Dog("xiaogou1");
    xiaogou1.action(); // xiaogou1 wang..

    let xiaogou2 = new Dog("xiaogou2")
    xiaogou2.action(); // xiaogou2 wang..

    let xiaogou3 = new Dog("xiaogou3");
    xiaogou3.action(); // xiaogou3 wang..

}

如上面所示 xiaogou1、xiaogou2、xiaogou3被称为对象实例而Dog被称为这群小狗的原型。可以通过构造方法来创建出1,2,3三只小狗。

{
    function Cat(name){
        this.name = name;
    }
    Cat.prototype.action=function(){
        console.log(this.name+" miao!!!")
    }

    function Dog(name){
        this.name = name
    }
    Dog.prototype.action = function(){
        console.log(this.name+" wang..");
    }
    let xiaogou = new Dog("xiaogou");
    xiaogou.action(); // xiaogou wang..

    let xiaomao = new Cat("xiaomao");
    xiaomao.action(); // xiaomao miao!!!
}

上面的代码中可以瞧出来:小狗能继承小狗原型上面的action方法去“wang...”,小猫会继承小猫的原型方法“miao!!!”。

{
    function Dog(name){
        this.name = name
    }
    Dog.prototype.action = function(){
        console.log(this.name+" wang..");
    }
    let taidi= new Dog("taidi");
    taidi.action = function(){
        console.log(this.name+ " miao!!!");
    }
    taidi.action() // taidi miao!!!

    let others = new Dog("other dog");
    others.action(); // other dog wang..

}

看上面的代码可以看出:当我们去强行让taidi的action方法改变的话,我们再进行访问的时候会先访问到实例上面的属性“ taidi miao!!!”,但是此时我们再用原型去创造实例的时候,我们并不会改变新增实例的action方法,这个说明了实例属性改变会覆盖原型属性,但是不会原型上面的额属性。

    delete taidi.action

    taidi.action() // taidi wang..

如上所示,当我们将泰迪action删除掉,再访问action方法则会重新显示原型上面的方法。如果我们重复调用上面的方法,却发现无法删除action方法,这进一步说明对象属性不能改变原型的属性。

四、 原型链的继承和彻底了解原型链

谈到面向对象呢?首先就会想到的是继承。我在这里呢?也就来触类旁通,希望用继承起手彻底搞清楚这一个东西。

{
    //这个是java入门继承的最好的例子,拿来讲解一哈
    function Animal(name){
        this.name = name
    }
    Animal.prototype.action = function(){
        console.log(this.name+" have running...")
    }
    Animal.prototype.need = function(){
        console.log(this.name+" need breathing")
    }

    function Fish (name){
        Animal.call(this,name)
    }
    Fish.prototype = Object.create(Animal.prototype);
    // Fish.prototype = new Animal() // 如果构造函数有值的时候,这里就不知道该填写什么了,就很尴尬
    // Fish.prototype = Animal.prototype; // 如果Fish想重写父类方法的时候,父类方法也会变化
    Fish.prototype.constructor = Fish;
    Fish.prototype.action = function(){
        console.log(this.name+" have Swimming...")
    }
    let fish = new Fish("fish");
    fish.name = "鲤鱼";
    console.log(fish.name) // 鲤鱼
    fish.action();
    fish.need();
    fish.best(); // es5:undefined,es6:报错
    console.log(fish.toString())

}

上面是我手写的一个js继承,结合我下面手绘的一张结构图来看一下(我找了好多画图工具,并没有发现好用,希望读者能推荐一款好用的mac画图工具)。

四、说在最后

其实这篇文章写了很久,不知道是因为最近状态低迷的缘故 还是时间唤起了我的懒散。我原本是想着上周末的时候就写完这篇文章,然后去专门来搞ts的,结果上周日自己很蠢的看了两场世界杯,然后买赢的德国输了、买赢的巴西平了。
诶,尽管身边一个朋友提醒我:世界杯有人在操盘。但是还是不能泯灭我当一个伪球迷的热情。算了,不说了 不说了,
日本都赢球了,你还有什么理由怨天尤人--致将去洗澡的我

上一篇下一篇

猜你喜欢

热点阅读