文章目录

  • 什么是对象?
  • 什么是面向对象?
  • 创建对象的方式
  • 原型是什么?
  • __proto__属性
  • constructor属性
  • 原型链
  • 函数的定义类型有哪些?
  • 函数也是一个对象
  • 完整的原型链

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

什么是对象?

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

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

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

系统内置的原生类型

开发人员自定义的类型(就是我们自己new出来的一个构造函数)

什么是面向对象?

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

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

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

面向对象与面向过程:

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

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

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

面向对象编程的三大特征

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

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

创建对象的方式

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

    var obj = new Object()obj.name = 'xh'obj.age = 13obj.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:

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

  1. 计算机蓝屏代码0x000000ED,蓝屏代码0x000000ed的4大解决方法详解!蓝屏0x000000ed的原因和解决方法!...

    说到电脑问题,就不得不提蓝屏的问题.最近有位朋友的电脑开机的时候,并没有进入正常的启动程序,反而进入了蓝色界面,显示代码0x000000ed,不知道为什么会这样,也不知道如何去解决.下面就来看看蓝屏0 ...

  2. 汇编语言典型例子详解_数据分析常用的7大思维方法详解

    今天老李继续给大家讲解数据分析经典的思维模型,上篇为大家介绍了目标思维.假设思维.溯源思维.逆向思维4个思维:数据分析必备7大经典模型详解!建议收藏!(上) 今天继续给大家介绍结构思维.演绎推理思维. ...

  3. Xen虚拟机两大迁移方法详解

    一.迁移的准备工作 在进行迁移之前,有一些准备工作要做,主要是目的主机和源主机及其网络方面.下面分别加以介绍. 首先,在源主机和目的主机方面,两者必须都运行有Xen和xend守护进程.必须确保目的主机 ...

  4. 数据分析常用的7大思维方法详解

    结构思维 很多人在分析的时候没有思路,不知道从何下手,这就是缺少结构化思维的表现 不如我们就直接看一下下面这个例子,看看大家是否具有结构化思维: 一家线下零售企业最近某个产品的销售额下降了,让你找一下 ...

  5. JSON转JS对象,JS对象转JSON

    JSON转JS对象,JS对象转JSON </h1><div class="clear"></div><div class="po ...

  6. matlab合并有序数组,《数组合并》JS合并两个数组的3种方法详解

    这篇文章主要介绍了JS合并两个数组的3种方法详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 一个包解决你所有的JS问题,点击获取 需要将两个数组 ...

  7. Js中apply方法详解说明

    Js apply 方法 详解 我在一开始看到JavaScript的函数apply和call时,非常的模糊,看也看不懂,最近在网上看到一些文章对apply方法和call的一些示例,总算是看的有点眉目了, ...

  8. Js apply 方法 详解

    Js apply方法详解 我在一开始看到javascript的函数apply和call时,非常的模糊,看也看不懂,最近在网上看到一些文章对apply方法和call的一些示例,总算是看的有点眉目了,在这 ...

  9. Js apply方法详解

    Js apply方法详解 主要解决一下几个问题: apply和call的区别在哪里 什么情况下用apply,什么情况下用call apply的其他巧妙用法(一般在什么情况下可以使用apply) 首先从 ...

最新文章

  1. delphi回调函数
  2. 毒瘤题No.006-byFHS
  3. 内嵌WEB服务器加载原理
  4. 十九、 彻底掌握金融量化交易库Talib
  5. axure 8 表格合并_搞定LaTeX论文中的表格
  6. AUTH password
  7. 剑指offer 第1题
  8. vue 设置每个页面的title
  9. Tomcat Script(python)
  10. 为何字节跳动、腾讯、阿里都要用Python?CSDN都要对它下手了!
  11. 如何调整mysql严谨度_如何管理 MySQL 的 binlog 收藏
  12. 2018年最好用的百度网盘资源搜索神器排行
  13. 2022年上半年系统集成项目管理工程师上午真题及答案解析
  14. Linux----SSH远程连接服务
  15. 【转】常用的版本控制软件
  16. androidのBack返回键,home键,menu键监听使用
  17. 我优化了李笑来的MarkdownHere,附css样式代码,文章排版再也不用愁了
  18. HIDS常见功能要求
  19. python十个实例-有趣的十个Python实战项目,让你瞬间爱上Python!
  20. vue3—reactive如何更改属性

热门文章

  1. PCIe之DMA (一)
  2. mysql 查询慢_MySQL查询缓慢的N种原因,以及N+1种解决方法
  3. oracle12c r2 dg,oracle 12cR2 DG 多租户之switchower切换
  4. ACWING347. 野餐规划(最小生成树)
  5. 《光遇》收集翅膀技巧攻略:如何才能快速收集翅膀?
  6. centos7修改网卡名
  7. 如何将CAD转换成PDF文档?教你2招完美转换
  8. mysql 设置主键命令_mysql用命令行如何设置主键
  9. ESXI6.7网卡驱动封装之离线封装-(转载)
  10. 如何将设计理论与实践相结合