资讯专栏INFORMATION COLUMN

JavaScript由浅及深了解原型链(一)

YorkChen / 2580人阅读

摘要:但是该方法有个缺点就是看不出该对象的类型,于是乎构造函数模式应运而生。当然,如果细心的朋友应该会发现函数名首字母大写了,这是约定在构造函数时将首字母大写。这时候,聪明的人应该都可以想到,将构造函数模式和原型模式组合起来就可以了。

一.什么是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__是指向各自的爷爷。当然这说法只是为了更好理解这两者是有区别的。接下来我给大家做一个图让大家更好的理解这两者的区别。

这大概也是明白为什么对象实例存在个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);       //[]
最后

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

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

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/94530.html

相关文章

  • JavaScript由浅及深敲开原型(二)

    摘要:不然原型链会断开。喵喵喵这样会使上一条语句失效,从而使原型链断开。这是在原型链里面无法做到的一个功能。属性使用借用构造函数模式,而方法则使用原型链。 一、对象的继承 1.了解原型链 在上一篇我们讲过关于原型对象的概念,当然如果不了解的建议去翻看第一篇文章,文末附有连接。我们知道每个对象都有各自的原型对象,那么当我们把一个对象的实例当做另外一个对象的原型对象。。这样这个对象就拥有了另外一...

    Carbs 评论0 收藏0
  • SegmentFault 技术周刊 Vol.16 - 浅入浅出 JavaScript 函数式编程

    摘要:函数式编程,一看这个词,简直就是学院派的典范。所以这期周刊,我们就重点引入的函数式编程,浅入浅出,一窥函数式编程的思想,可能让你对编程语言的理解更加融会贯通一些。但从根本上来说,函数式编程就是关于如使用通用的可复用函数进行组合编程。 showImg(https://segmentfault.com/img/bVGQuc); 函数式编程(Functional Programming),一...

    csRyan 评论0 收藏0
  • 区块概念 That You Must Know 第四期(2)

    摘要:下图给出一个简单的列表图什么是哈希和哈希值为理解挖矿的代码机制,首先解决几个概念。第一个就是哈希。哈希值为十六进制表示的数,且长度固定。也正是哈希值的这些特点,赋予了其加密信息时更高的安全性。 第四期 挖矿的相关算法(2) 卡酷少Wechat:13260325501 看过(1)篇,相信你一定对挖矿的机制有了一点了解。那么本篇,我们来一起看一下挖矿中涉及的算法。 在本篇文章中,如果在...

    Sourcelink 评论0 收藏0
  • JavaScript 进阶之深入理解数据双向绑定

    摘要:当我们的视图和数据任何一方发生变化的时候,我们希望能够通知对方也更新,这就是所谓的数据双向绑定。返回值返回传入函数的对象,即第一个参数该方法重点是描述,对象里目前存在的属性描述符有两种主要形式数据描述符和存取描述符。 前言 谈起当前前端最热门的 js 框架,必少不了 Vue、React、Angular,对于大多数人来说,我们更多的是在使用框架,对于框架解决痛点背后使用的基本原理往往关注...

    sarva 评论0 收藏0
  • JavaScript 异步

    摘要:从最开始的到封装后的都在试图解决异步编程过程中的问题。为了让编程更美好,我们就需要引入来降低异步编程的复杂性。写一个符合规范并可配合使用的写一个符合规范并可配合使用的理解的工作原理采用回调函数来处理异步编程。 JavaScript怎么使用循环代替(异步)递归 问题描述 在开发过程中,遇到一个需求:在系统初始化时通过http获取一个第三方服务器端的列表,第三方服务器提供了一个接口,可通过...

    tuniutech 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<