【JS对象】打败JS原型、原型链大恶魔方法详解


打败恶魔第一步,我们先要了解什么是 面向对象编程
(1)什么是对象?
(2)什么是面向对象?

什么是对象?

对象是无序键值对的集合,其属性可以包含基本值、对象或者函数。

简单来说,我们知道数组是有序键值对的集合,那对象是无序键值对这个好理解吧。然后属性里面包含基本值(指的就是简单的数据类型),对象对象里面也可以嵌套对象呀),函数(其实就是方法

每个对象都是基于一个引用类型创建的,这些类型可以使系统内置的原生类型,也可以是开发人员自定义的类型。

系统内置的原生类型
在这里插入图片描述
开发人员自定义的类型(就是我们自己new出来的一个构造函数)
在这里插入图片描述

什么是面向对象?

面向对象编程-Object Oriented Programming 简称OOP,是一种编程的开发思想

在面向对象的程序开发思想当中,每个对象都是功能中心,具有明确分工,可以完成接受信息、处理数据、发出信息等任务。因此,面向对象编程具有灵活代码可复用高度模块化等特点,容易维护和开发,比起由一系列函数或指令组成的传统式的过程式编程(procedural programming),更适合多人合作的大型项目。

上面一串文字让人看了很头痛,别慌,我们只要大概知道面向对象编程的好处有哪些,下面我们一起来对比下过程式编程和面向对象编程,可能会更好地理解这个概念。

面向对象与面向过程:

  • 面向过程就是 亲力亲为,事无巨细,当然面向过程也是一种编程思想(但是偏向于员工的角度)
    它的关注点在于解决问题的一个过程(我要先干嘛,然后干嘛,再干嘛)

  • 面向对象就是找一个对象,让她去做这件事情(老板的角度)
    它的关注点在找到解决问题的对象上面

  • 面向对象并不是面向过程的替代,而是面向过程的一个封装

面向对象编程的三大特征

  • 封装性:用对象封装,封装的更彻底
  • 继承性:子承父业
  • 多态性:这个JS不支持(因为JS是一门弱类型的语言)

打败恶魔第二步 了解创建对象的方式有哪些?

创建对象的方式

1.new一个对象
缺陷:比较麻烦,每次添加属性都需要使用点语法

    var obj = new Object()
    obj.name = 'xh'
    obj.age = 13
    obj.sayHello = function () {
        console.log('sayHello');
    }
    console.log(obj);

2.对象字面量{}
缺陷:每次 只能创建一个对象,不能批量地创建

 var obj = {
        name: 'xh',
        age: 12,
        sayHi: function () {
            console.log("sayHi");
        }
    }
    console.log(obj);

3.工厂函数

 function creatObj(name, age) {
        var obj = {
            name: name,
            age: age,
            sayHi: function () {
                console.log('i am 帅哥');
            }
        }
        //   这里千万要注意return 出去,工厂函数才可以批量调用
        return obj
    }
    // 这里需要找个变量进行接收,因为对象是函数里面return出来的
    var lw = creatObj('lw', 37)
    var xh = creatObj('xh', 15)
    console.log(lw, xh);

缺陷:创建出来的对象具体类型无法识别(就只能知道是一个对象而已)
在这里插入图片描述
4.自定义构造函数
特征:首字母大写(规范)、构造函数需要配合new一起使用

 function Person(name, age) {
        // 构造函数中的this指向了新创建出来的对象
        // this.xxx=yyy; 的形式来给新创建的对象添加属性和方法
        this.name = name;
        this.age = age
    }
    //new出来的对象 赋值给p1的是一个内存地址
    var p1 = new Person('xh', 25)
    console.log(p1);

这里面的new做了四件事情:

  • 创建了一个新的对象
  • 把构造函数里面的this指向了新对象
  • 执行构造函数里面的代码
  • 把创建的新对象给返回出去

那这里面的构造函数做了什么事情?
-回答:存储了代码,给this(新构造的对象)添加了属性和方法

自定义构造函数可以解决工厂函数创建对象造成的对象无法识别对象类型的问题
在这里插入图片描述
这些专业的术语你要知道
实例对象:构造函数创建出来的对象,实例对象可以有多个
实例化:创建实例对象的过程
成员:指的是对象的属性和方法

那自定义构造函数就没有自己的缺陷了吗?
回答:是有的,请看以下代码:

    function Person(name, age) {
        this.name = name;
        this.age = age;
        this.sayHello = function () {
            console.log("Hello");
        }
    }
    var p1 = new Person('xh', 25)
    var p2 = new Person('xm', 11)
    console.log(p1 == p2);  /*false*/
    console.log(p1.sayHello == p2.sayHello);  /*false*/

很奇怪的是p1和p2的sayHello 方法明明是同一个,为什么对比出来的结果却是false呢?
其实就是内存地址的问题 他们进行对比,对比的都是内存地址,但是他们的内存地址都是不一样的
在这里插入图片描述
上述的图片说明了,假如我创建了一千个自定义构建函数,那我是不是有一千个地址,但是我的方法的作用却是相同的,那么是不是存在着一个内存浪费的问题
那么我们要怎么去解决呢?
思路:让内存地址当中只有一份sayHello方法
下面只是一个过渡的方法

     // 将方法里面的函数移除外面来,让内存保证只有一个
    var tools = {
        fn1: function () {
            console.log("Hellow");
        }
    }

    function Person(name, age) {
        this.name = name;
        this.age = age;
        this.sayHello = tools.fn1;
    }
    var p1 = new Person('xh', 25)
    var p2 = new Person('xm', 11)
    console.log(p1.sayHello == p2.sayHello);  /*true*/

但最好的解决方法是:通过原型来解决构造函数中的内存浪费问题
那么问题来了,原型是什么?

原型是什么?

打败恶魔第三步,了解原型是什么?
别急,我们慢慢来~我们先理一下

  • 函数都有prototype属性,从侧面说函数也是一个对象
    function Person() { }
    console.dir(Person);

在这里插入图片描述

  • 函数prototype的属性值是个对象,我们把这个对象称之为原型(原型对象)
   function Person() { }
    console.log(Person.prototype);

在这里插入图片描述
原型对象的作用
通过构造函数构造出来的对象可以直接访问构造函数的prototype属性上的任意成员
看以下的代码来理解

    function Person() { }
    // 给构造函数添加一个新的属性
    Person.prototype.color = 'pink'

    // 创建一个实例对象
    var p1 = new Person()
    // 实例对象可以直接访问构造函数的prototype属性上的任意成员
    console.log(p1.color); /*pink*/

回到我们之前所提及的自定义构建函数造成的内存浪费问题,原型是怎么解决的呢?

 function Person(name, age) {
        this.name = name;
        this.age = age;
    }
    //直接在原型上面添加方法
    Person.prototype.sayHello = function () {
        console.log("Hello");
    }
    var p1 = new Person('xh', 25)
    var p2 = new Person('xm', 11)
    p1.sayHello()
    p2.sayHello()
    //两个实例对象都可以调用到方法,而且内存地址也是一致的
    console.log(p1.sayHello == p2.sayHello);  /*true*/

图解构造函数、原型对象和实例对象之间的关系

__proto__属性

1.每个对象都有__proto__属性
2.每个对象的__proto__属性指向构造函数的prototype(原型对象)


    function Person() {

    }
    var p1 = new Person()
    console.log(p1.__proto__);
    console.log(Person.prototype);
    console.log(p1.__proto__ == Person.prototype);  /*true*/

在这里插入图片描述
要想访问到原型对象,有两种的途径:
1.通过构造函数的prototype来访问
2.通过实例对象的__proto__来访问

在这里插入图片描述__ proto__属性的注意点:

  • 不是个标准的属性,存在兼容问题,IE678不识别该属性
  • 注意最好别在线上代码中使用该属性

推荐做法
从构造函数的prototype访问原型对象,并且为它添加属性

constructor属性

  • 原型对象上自带的constructor属性
  • 原型对象的该属性值指向构造函数
 function Person() {

    }
    var p1 = new Person()
    console.log(Person.prototype.constructor);

在这里插入图片描述
在这里插入图片描述
接下来,我们要打终极恶魔了。
原型链究竟是什么呢?

原型链

先抛出一个问题

 function Person() {

    }
    //实例对象p是怎么调用toString()方法的
    var p1 = new Person()
    p1.toString()
    console.log(p1);
    console.log(Person.prototype);

实例对象p是怎么调用toString()方法的?
明明 实例对象自己没有这个方法,原型对象也没有啊?
在这里插入图片描述
带着这个问题,我们一起来学习原型链。

原型链:任何对象都是有__proto__属性,指向他的原型对象,原型对象也是对象,那么原型对象也是有__proto__属性,指向的是原型对象的原型对象,这样形成的一个链式结构叫做原型链

那么我们的首要任务就是 先找到原型对象的原型对象,那么要怎么找呢?
以我们刚才一直举的例子为例:

1.我们先把原型对象看成一个大类
2.那么 原型对象其实是可以根据__proto__来找到属于他自己的原型对象
为了简单起见,这里把第一个原型对象,当做大头儿子,第二个原型对象的原型对象,当做小头爸爸

   function Person() {

    }
    var p1 = new Person()
    console.log(Person.prototype.__proto__);

但是log出来的是这样一个东西,我们看不懂
在这里插入图片描述
但我们可以看到log出来里面有个constructor
然后自然可以联想到
我们可以换一个角度去想,既然我们不知道小头爸爸(原型对象的原型对象)具体叫什么名字?
那我们可以想想,小头爸爸是不是也是一个原型对象,一个原型对象是不是有constructor属性?指向的是??
就是 Object对象这个构造函数,那么我们是不是就可以知道小头爸爸的名字了?Object.prototype

然后顺着刚才的思路,我们继续再往上面去找,那小头爸爸有没有自己的爸爸呢?我们再通过__proto__来试试,会发现最后的结果是:
在这里插入图片描述
画个图来一起理解下:
在这里插入图片描述
(上图忘记写原型链的顶端了 那就是null)
说了那么多,其实实例对象p的原型链长的是咋样的呢?

p => Person.prototype => Object.prototype=>null

那回到我们刚才的问题
在这里插入图片描述
我们从刚才的原型链可知,其实是p从Object.prototype中拿到的属性toString()
另外补充一句,属性的查找原则:就是往上找

ok,我们的原型链还没完呢?
大家有没有感到好奇,其实函数也是一个对象啊?那函数的原型链是怎样的呢?

打败恶魔第四步,了解函数的原型链
我们离完整的原型链越来越近啦~

函数的定义类型有哪些?

函数的三种定义类型

 // 1.函数声明
    // 为什么fn()放在前面可以执行,因为函数声明会有预解析
    fn()
    function fn() {
        console.log('fn')
    }


    // 2.函数表达式
    // 为什么 fn2()放前面不执行,也是因为预解析,预解析只会提升变量,而不会提升赋值
    fn2()  /*执行  会报错  需要把fn2()放到后面去*/
    var fn2 = function () {
        console.log("fn2");
    }

    // 3.函数也是对象,对象是被new出来的
    var fn3 = new Function('n1', "n2", 'bodyFn')
    // 参数有若干个
    // 参数都是字符串类型
    // 这里new出来的对象,前面的参数都是函数的形参
    // 最后一个参数,是函数的身体,也就是这个函数的内容
    //举个栗子:
    var fn4 = new Function('a', 'b', 'alert(a + b)')
    // 这个fn4其实里面是什么
    // var fn4 = function (a, b) {
    //     alert(a + b)
    // }
    fn4(1, 2)

我们可以知道,最后一种是很不日常的,我们很少去用,但是对于我们今天的原型链的思考却大有用处。

函数也是一个对象

我们先一起来绘制下关于函数的原型链
假如接着最上面的


function Person(){}

//var Person=new Function()
这个就是上面那个函数的底层

那么: 因为 Person是构造函数Function new出来的,所以Person是实例对象,而构造函数又可以通过.prototype访问实例对象,所以Function.prototype是Person实例对象的原型对象,而实例对象又可以通过__proto__访问原型对象。

所有的函数都是Function的实例
在这里插入图片描述
我们再通过代码,log一下,看下他们的表现形式
Function.prototype在js中原型对象当中唯一类型为函数的(但是函数也是对象呀,所以不冲突)

在这里插入图片描述
在这里插入图片描述可以看到Function.prototype.proto log 出来的对象的constructor是 f Object,所以可以知道我们Function.prototype他的原型对象 也是Object.prototype

所以我们又可以完善一下我们的绘图
在这里插入图片描述
但是以上的原型链都是不完整的,
接下来,我们一步一步地将他完善

完整的原型链

共有五部曲:
1.把函数当成函数看,具体可以当做构造函数来看
构造函数:Person
原型对象:Person.prototype
实例对象:p

function Person() {

    }
    var p = new Person()

绘制图如下
在这里插入图片描述
2.把函数当对象看,具体是看成实例对象
构造函数:Function
原型对象:Function.prototype
实例对象:Person

var Person=new Function()

在这里插入图片描述
3.把对象当函数看,具体当做一个构造函数

构造函数:Object
原型对象:Object.prototype
实例对象:obj

 var obj = new Object()
 

在这里插入图片描述
4.把对象当成一个实例对象
因为所有函数都是Function的实例对象(因为底层都是Function new出来的)so:-
构造函数:Function
原型对象:Function.prototype
实例对象:Object

var Object=new Function()

在这里插入图片描述

5.把函数当成是一个实例对象(最难理解)
因为函数也是一个对象(然后底层是把它设计成也是个实例对象)
构造函数:Function
原型对象:Function.prototype
实例对象:Function
在这里插入图片描述
最完整的图
在这里插入图片描述
完整原型链小结:

  1. 所有对象的原型链上面都有Object.prototype
  2. 所有函数的原型链上面都有Function.prototype
  3. 所有的对象都有__proto__属性 、所有的函数都有prototype属性,又因为函数也是对象,所以:
    函数既有prototype属性,也有__proto__属性

原型链测试题

    console.log(Object.__proto__ === Function.prototype);
    console.log(Function.prototype === Object.prototype);
    console.log(Object.prototype.__proto__ === Object.prototype);
    console.log(Object.__proto__.__proto__ === Object.prototype);

answer:
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值