JavaScript由浅及深敲开原型链(一)

一.什么是js对象

1.简单理解js对象

在了解原型链之前,我们先要弄清楚什么是JavaScript的对象,JavaScript对象又由哪些组成。有人说一个程序就是一个世界,那么我们可以把对象称之为这个世界的组成类型,可以是生物,植物,生活用品等等。我们在java中管这些类型叫做,但是在JavaScript中没有类的说法,当然ES6新标准中开始出现了类。但是在此之前,我们都管这些类型叫做对象。那么对象创建出来的实例就是就是组成该世界的各个元素,如一个人、一只小狗、一棵树等等。这些就称之为对象的实例。那么每种类型都有它不同的属性和方法,同样的JavaScript对象也是由对象属性和对象方法组成。当然了每个实例还可以存在与对象不一样的方法与属性。

var person = {
    name:"xiaoming",    //对象属性
    sayName:function(){    //对象方法
        console.log(this.name);
    }
}

2.js对象属性的特性

在JavaScript对象中,每个属性都有其各自的特性,比如你的性别具有不可修改的特性。那么下面简单粗略介绍一下这几个特性。这些特性在JavaScript中是不能直接访问的,特性是内部值。

  • [[Configurable]]: 表示能不能删除重新定义属性,能不能修改属性等 默认true
  • [[Enumerable]]: 表示能不能通过for-in遍历等 默认true
  • [[Writeable]]: 表示能不能修改属性值 默认true
  • [[Value]]: 表示属性的值,写入到这里,读从这里读 默认undefined

如果要修改属性的默认特性,可以使用Object.defineProperty()方法,当然在这里就不再继续展开了。接下来我们开始介绍对象的创建

二.创建JavaScript对象

1.工厂模式

function createPerson(name,sex){
    let obj = new Object();
    obj.name = name;
    obj.sex = sex;
    obj.sayName = function(){
        console.log(this.name);
    }
    return obj;
}

let p1 = new createPerson("小明","男");

这就是工厂模式,在函数内创建对象,然后在函数内封装好后返回该对象。但是该方法有个缺点就是看不出该对象的类型,于是乎构造函数模式应运而生。

2.构造函数模式

function Cat(name,color){
    this.name = name;
    this.color = color;
    this.sayName = {
        console.log("我是"+name+"猫");
    }
}

let Tom = new Cat("Tom","灰白");
let HelloKity = new Cat("HelloKity","粉红");  

构造函数模式工厂模式的区别在于,构造函数模式没有用return语句,直接把属性赋给了this语句,并且没有显式的创建对象。当然,如果细心的朋友应该会发现函数名首字母大写了,这是约定在构造函数时将首字母大写。

用构造函数创建新实例时,必须要用new操作符。同时,每个由构造函数创建的实例都会有一个constructor指向该构造函数

Tom.constructor == Cat  //true

这时候我们就会想一个问题,我们在创建不同的Cat实例时,我们就会创建多个不同sayName函数,但是他们执行的功能都是一样的,这时候我们就会想要一种更优化的方法。这时,我们需要引入原型属性(prototype)的概念了

3.原型模式

我们创建的每个函数里面都会有个prototype属性,这个就是原型属性,这个属性是个指针,指向一个该函数的原型对象。我们可以捋一捋对象,对象原型,实例这三者的关系,简单来说,我们可以把对象想象成爸爸,那么对象原型就是爷爷,实例的话好比是儿子。爷爷有的东西(属性、方法),每个儿子都会遗传到的,当然如果爸爸把爷爷的东西修改了一下,那么到儿子手上的就是爸爸修改过的东西了(方法重写)。当然,儿子也算是爷爷骨肉嘛,那么儿子就会有个指针[[prototype]]指向爷爷,在Chrome、Firefox等浏览器上面可以用属性__proto__可以访问到。

  • 那么prototype__proto__区别在哪?

这么说,简单的说prototype是指向各自的爸爸,__proto__是指向各自的爷爷。当然这说法只是为了更好理解这两者是有区别的。接下来我给大家做一个图让大家更好的理解这两者的区别。

未命名文件 (3).png

这大概也是明白为什么对象实例存在个constructor指针指向对象了,因为对象原型上面存在这个属性指向该对象,而且原型最初只包含该constructor属性。而实例寻找属性值的时候会向上找,先在实例中搜索该属性,没有的话向对象原型寻找。所以最后找到并返回该值。这样就能很清楚的分开prototype__proto__的区别了。prototype是对象的属性,而__proto__是对象实例的属性。

那么我们基本了解prototype属性以后,我们就可以给大家说说原型模式了。

function Cat(){
    
}

Cat.prototype.name = "Tom";
Cat.prototype.color = "灰白";
Cat.prototype.sayName = function(){
    console.log(this.name);
}

let cat1 = new Cat();
let cat2 = new Cat();

cat1.sayName();     //"Tom" 
cat2.sayName();     //"Tom"

console.log(cat1.color);      //"灰白"
console.log(cat2.color);      //"灰白"

//因为对象原型是共享属性与方法,所以所有实例都可以访问到

//接下来玩点更复杂的

Cat.sayName = function(){
    console.log("我是Cat");
}

cat1.sayName = function(){
    console.log("我是cat1");
}

let cat3 = new Cat();

cat1.sayName();     //"我是cat1"
cat2.sayName();     //"Tom"
cat3.sayName();     //"Tom"
Cat.sayName();      //"我是Cat"

这时候很多人就懵了,为什么cat3说的是"Tom",而不是输出"我是Cat"。这是因为 Cat.sayName 这个函数是类方法,我们要注意一点,Cat也是一个函数,函数就是一个对象,可以为其添加方法和属性。所以我们在实例中调用sayName并不是调用该类方法。我们还需要分清类方法与对象方法的区别。

function Person(){      //通过对象实例调用
    this.say = function(){
        console.log("我是Person对象方法");
    }
}

Person.say = function(){        //只能通过Person调用
    console.log("我是Person类方法");
}

Person.prototype.say = function(){      //通过对象实例调用
    console.log("我是Person对象原型方法");
}

到这里,也许还是会有点懵,为什么后面的cat1.sayName(); //"我是cat1"因为对象实例方法会屏蔽掉原型的方法。我们之前说过,当代码读取对象的某个属性时,它会先从该对象实例开始搜索,如果找不到再往上搜索。所以当你定义了对象实例的方法时,如果跟对象原型中的同名,那么该对象实例的方法就会屏蔽掉对象原型中的方法。所以cat1第二次输出的是我是cat1

到这里,我再总结一下对象原型,对象与对象实例之间的关系。

  • 对象原型内的方法与属性可以供所有的对象实例访问实现共享性

  • 对象的prototype属性可以找到对象原型而对象实例的[[proto]]可以找到对象原型

  • 对象实例可以重写对象原型方法使其屏蔽对象原型的方法

  • 对象原型一开始只有constructor属性该属性指向该对象

  • 分清对象原型方法对象方法对象实例方法类方法区别类方法不需要通过实例化对象去访问而其他的都要对象实例去访问

那么到这里我们已经弄懂了对象原型,对象与对象实例之间的关系。下面我再介绍一种简单的原型语法。

function Cat(){
    
}
Cat.prototype = {
    name:"Tom",
    color:"灰白",
    sayName:function(){
        console.log(this.name);
    },
}

这样我就以字面量的形式创建了新对象,但是有个不一样的地方就是constructor属性不指向Cat,因为我们创建一个函数就会创建它的原型对象,原型对象里面自动获得constructor属性,那么我们再这样的情况下,重写了整个原型对象。所以此时的constructor属性指向了Object。那么我们如果非要这个属性怎么办?很好办,我们自己给它加上就好。

function Cat(){
    
}
Cat.prototype = {
    constructor:"Cat",
    name:"Tom",
    color:"灰白",
    sayName:function(){
        console.log(this.name);
    },
}

最后我们讲一下原型模式的缺点,原型模式的缺点也很明显,就是它的共享性。成也共享败也共享。这让我突然想起共享单车。废话不多说,直接撸码上来

function Cat(){
    
}

Cat.prototype.name = "Tom";
Cat.prototype.color = "灰白";
Cat.prototype.catchMouse = ["Jerry"];
Cat.prototype.sayName = function(){
    console.log(this.name);
}

let cat1 = new Cat();
let cat2 = new Cat();

cat1.catchMouse.push("Mickey");

console.log(cat1.catchMouse);       //["Jerry", "Mickey"]
console.log(cat2.catchMouse);       //["Jerry", "Mickey"]

因为原型上面的属性是所有实例都可以访问的,那么当添加往catchMouse属性添加一个值时,所有实例皆可以访问到该属性。这就让人们很头疼了,每个实例的属性应该都是不一样的才对,起码正常来说,但是这样弄得大家都一样的话,就有问题了。这时候,聪明的人应该都可以想到,将构造函数模式和原型模式组合起来就可以了。

4.组合构造函数模式和原型模式

将其组合起来,结合他们两的优点,是普遍认同度最高的对象创建模式

function Cat(name,color){
    this.name = name;
    this.color = color;
    this.catchMouse = [];
}

Cat.prototype.sayName = function(){
    console.log(this.name);
}

let cat1 = new Cat("Tom","灰白");
let cat2 = new Cat("HellowKity","粉红");

cat1.catchMouse.push("Jerry");

cat1.sayName();     //"Tom"
cat2.sayName();     //"HellowKity"
console.log(cat1.catchMouse);       //["Jerry"]
console.log(cat2.catchMouse);       //[]

最后

本篇介绍了对象与对象的创建方法。同时引入并介绍了对象原型的概念。解析了对象原型,对象与对象实例间的关系。我们在下一篇将会解析原型链的概念以及对象的继承与原型链的关系,带大家敲开原型链的奥秘。

附:《JavaScript由浅及深敲开原型链(二)》

原创文章,转载请注明出处

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,496评论 6 501
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,407评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,632评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,180评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,198评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,165评论 1 299
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,052评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,910评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,324评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,542评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,711评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,424评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,017评论 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,668评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,823评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,722评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,611评论 2 353

推荐阅读更多精彩内容