面向对象、工厂模式、构造函数、原型、原型链都不懂?看这一篇就够了!
本文是博主自己归纳曾学到的知识,以及分享工厂模式、构造函数、以及原型链之间的用途;有很多面试者都会遇到这些问题,但是一些小白来说,还是不清楚,这些知识点到底在技术中有什么用途,到底是干什么的?今天我就简单说一说,本篇博客会从基本开始介绍,如果你对今天的内容所致甚少,并且你有耐心看完,相信你会有超值的收获!!!
小白们都对原型链迷迷糊糊,经常查阅相关文章,却还是缕不清到底是干什么的。因为你不知道它到底有什么用,又是如何会用到它,所以在直接查阅原型链的时候,忽略了它之前很重要的知识,所以你不懂。这个很重要的知识,就是构造函数!!!
本篇博客要一环扣一环,决定帮大家捋清思路,所以我们从基本开始说,从头到尾请跟住博主的思路。
面向对象:
对象?无论在javascipt当中,还是java当中,或其他语言中,所提及的对象,绝对不是男女朋友的对象,所以我们凡人先忘掉感情视野,再体会一下这个词:“对象”。
所谓对象,任何事物都可以称作是对象。
比如:
医生给病人治疗,病人对于医生来说,就是治疗的对象。
我们去商场购买苹果,苹果对于我们来说,就是要购买的对象。
博主写的博客,你们就是我要展示博客的对象。
科学家要研制火箭,火箭对于科学家来说,就是要研制的对象
……
所以我们这些都是对象,有句英文说的好,Everything is object(万物皆对象)。
知道对象了,那为什么要“面向”呢?“面向对象又是什么?”
这就是一种编程思想,面向对象编程。在我们的开发世界中,把所有的方法都封装成一个一个的对象,大的功能都是由一个又一个的对象实现的,所以就叫面向对象编程思想。在程序员眼中,一切功能都是被拆分成小的单独对象,组装起来的。
在开发中,我们很容易可以创造出一个对象,例如:
var person = {}; // 字面量创建一个对象 var person1 = new Object(); // 构造一个对象
两种方法,但都是创造了一个对象。对象都有自己的属性,比如人可以吃饭,睡觉,恋爱。这些都是我们人(对象)的属性,同样,我们也可以在上面的例子中,加入属性:
var person = { name: 'villin', age: 18, exes: ["111", "222", "333"] };
我们也可以删除里边的属性,比如把年龄删除:
delete person.age
这些就是对象的含义,我们在开发中,也可以创建一个函数,这个函数就看作是一个对象,里边些不同的方法。面向对象很简单,反复的看几遍,就能把这个“对象”一词理解明白。
this:
this,是属于一个指向的关键字,一般是在函数内使用,谁调用了这个函数,this指向谁。这个不太好解释,只能体会的去理解。看下边这个例子,在person中定义sayHi这个函数,所以this指向了person对象中的属性。
var person = {} person.name = 'villin'; person.sayHi = function () { console.log(this.name) } person.sayHi(); // villin
this重点:
1、在函数内,函数定义时无法确定this指向,所以记住:谁调用,指向谁!
2、函数如果以普通方式进行调用,this指向window
3、每个函数都有自己的this,只要进入一个函数,this就可能发生变化
那么如果在函数中还不想让this指向window,还想使用传过来的参数,我们习惯于将参数赋值:
function aaa(name){ this.name = name }
工厂方法(factory):
我们把上边刚才的例子,放在一个函数中,并返回这个值,就是工厂模式。具体为啥叫这个名字,我也不知道。我们封装一个简单的(工厂模式)函数,并且进行调用。这个函数很简单,我标注的很详细,仔细看,这一环很重要。
// 创建一个函数,接收两个参数,一个name,一个age (函数可以接收很多参数,具体几个看你们自己的心情) function person(name,age){ // 1.创建一个对象 var a = {}; // 2.在对象上添加属性和方法,将参数赋值给它 a.name = name; a.age = age; // 3.和上边一样,这个对象a中定义一个函数sayHi a.sayHi = function(){ console.log("你好,我叫"+this.name+",我今年"+this.age+"岁") } // 4.创建的对象必须要返回这个对象a,才能打印出函数中的内容; return a; }
好了,这个工厂模式的函数,就封装好了,就可以多次调用这个封装的方法了,例如:
// 定义三个p,分别传入两个参数,一个name,一个age var p1 = person("villin",25) var p2 = person("xiaoming",26) var p3 = person("xiaoli",18) // 分别调用三个方法 p1.sayHi() p2.sayHi() p3.sayHi()
打印结果如下:
我们有个方法instanceof
,是可以检测一个对象是否为一个构造函数的实例,刚才写的我们可以检测一下:
console.log(p1 instanceof person) // false
返回为false,说明p1并不是person函数的实例,那么如果我在创建一个其他的函数,也是检测不出来的,这样在项目中,如果很多个函数,我们就无法得知,这个对象是哪个函数的实例,所以工厂模式的缺点,就是无法判断生成的对象,是哪个构造函数的实例。
解决办法:使用构造函数的方法来创建对象!!!
延伸:实例和继承。 什么是实例?举个例子:我们刚才说过,万物皆对象,那么动物就是个对象,我们再具体一点,猫、狗、老鼠这些就是动物的实例。所以person函数就是一个对象,那么p1、p2、p3是否是person的实例,就用instanceof来验证; 那么猫,狗等都继承了动物的特征,比如动物是活的,有眼睛耳朵鼻子等,猫狗也有这些,这就是继承,继承了动物的特征。但是不能反过来说动物也继承了猫狗的特征,这是不对的,因为猫有毛,但是鱼没有。 万物皆对象,所以实例(像猫和狗)也是对象,但是对象未必是实例(例如动物是个大的框架,没有被细分成实例)。
构造函数(constructor):
我们接着上边的案例来说,同样我们创建一个函数person,但是这个函数名首字母要大写,代表构造函数,但并非规定。
// 函数名大写 function Person(name,age){ // 省略了创建对象 (和工厂模式对比) // 省略了添加属性和方法 (和工厂模式对比) // 1.赋值指针this this.name = name; this.age = age; this.sayHi = function () { console.log("你好,我叫"+this.name+",我今年"+this.age+"岁") } // return返回对象,也省了 (和工厂模式对比) }
接下来我们重新构造(new)这个Person对象,并传入相应的参数:
p1 = new Person("villin",25) // 不要忘记大写 p2 = new Person("xiaoming",26) p3 = new Person("xiaoli",18) // 分别调用三个方法 p1.sayHi() p2.sayHi() p3.sayHi()
打印结果,同样如此:
我们再次尝试用instanceof
验证一下
console.log(p1 instanceof Person) // true
显然,可以被验证了,这也说明了,构造函数p1为Person的实例。如果项目中不光有Person函数,或其他,也都可以验证。(自己要回头去看一下工厂模式和构造函数的区别)
不过构造函数依然伴有缺点:
细心发现,我们刚才构造了三个函数p1~p3,分别构造了三次,这样每次构造一次,就会生成一个sayHi的函数(好奇者可以分别打印p1、p2、p3在控制台看一下,会发现生成了三次相同的函数),并且这些函数对象内的方法,其代码是一模一样的,都是(你好,我的名字是,年龄)等,这样重复太多,复用性不高,太浪费资源。
想解决这个问题,我们就需要把这个sayHi公用的函数存放到一个位置,这个位置要确保每个对象都能访问到。
那么这个位置就是:prototype(原型)。
原型(prototype):
1、prototype中文含义就是“原型”,只要是函数,都有自己的原型prototype。
2、当用构造函数创建对象(new Xxx())时,浏览器在新创建的对象上添加了一个属性__proto__(前后是双下划线。不要直接使用)。
所以要记住,对象中有一个属性是__proto__。函数也是对象,所以它也有__proto__,但是函数还多了一个prototype属性。
这个对象到底长什么样,我们打印一下这个对象p1(刚刚的p1)看一下。
function Person(name,age){ // 省略了创建对象 (和工厂模式对比) // 省略了添加属性和方法 (和工厂模式对比) // 1.赋值指针this this.name = name; this.age = age; this.sayHi = function () { console.log("你好,我叫"+this.name+",我今年"+this.age+"岁") } // return返回对象,也省了 (和工厂模式对比) } p1 = new Person("villin",25) console.log(p1) // 打印p1
可以看到,我们没有打印函数,仅仅是打印出的p1,那为什么也会有name、age、以及sayHi属性?这就是继承,因为new构造出来的是Person函数的实例,所以它继承了它构造的函数Person中的属性。
我们可以看到这个对象p1是带有自己的一个属性__proto__的,并且,这个对象的__proto__指向了它的构造函数的prototype(每个函数都是有prototype的属性的,只是没打印而已,自己印在自己的脑海里),并且__proto__也会继承prototype中的属性。
这些就是概念性的东西,要试着去理解,如果不理解多看几遍,一定要慢慢的读,博主写这些,也是慢慢的写的。
那么既然每个构造函数都会继承它原来函数中prototype的属性,那么就利用这一点,写一个可以公用的方法。
让他们的实例公用这个sayHi函数,我们开始利用这个原型prototype去更改构造函数的不足之处:
function Person() {} //单独设一个空的函数,它有一个属性是prototype // 公共的部分 // 我们可以直接更改Person的原型prototype Person.prototype.name = 'villin'; Person.prototype.age = 26; Person.prototype.sayHi = function () { console.log("你好,我叫"+this.name+",我今年"+this.age+"岁") } var p1 = new Person(); var p2 = new Person(); p1.sayHi(); p2.sayHi(); // 你好,我叫villin,我今年26岁 // 你好,我叫villin,我今年26岁
可以看到,虽然还是创建了两个函数,但是p1和p2都复用了prototype中的属性,所以打印的东西都是一样的。
原型链:
疑问:同学可能会发现,我并没有在p1和p2中定义任何的属性,直接调用p1.sayHi为什么可以把原型prototype中的sayHi打印出来?
p1中并没有sayHi这个属性。这就引入了一个词叫:原型链。
当访问一个对象时,会在对象内进行查找这个属性,比如之前定义过的this.sayHi属性。像本次,对象内没有定义sayHi属性,那么就会进入p1的原型__proto__中查找,因为__proto__指向了Person中prototype,并继承了其中的属性,所以prototype中有啥,__proto__就有啥,所以就找到了。这一条链式查找就是原型链。我们打印一下p1看一下:
不过,这个方法还是有些缺点,因为p1和p2都继承了相同的属性,打印出来的结果都是一样的,这并不是我们想要的,我们想要的是,公用一些方法外,可以传递想要打印的参数,想打印出什么值,就打印出什么值。
所以,最后的解决方案:组合(构造+原型)
构造函数+原型:
如果以上的几个方法都看明白了,这个就很简单,只需要在原型中定义想要得到的公用方法,在函数中定义this指向,并接收参数即可:
// 需要定义两个函数,一个是构造函数Person,一个是在Person的prototype中定义公用方法sayHi函数。 function Person(name, age, exes) { //将所有的参数属性,放在构造方式中 this.name = name; this.age = age; this.exes = exes; } Person.prototype.sayHi = function () { // this会指向Person内 console.log("你好,我叫"+this.name+",我今年"+this.age+"岁") }
然后照常传递参数,正常打印:
p1 = new Person("villin",25) p2 = new Person("xiaoli",18) // 你好,我叫villin,我今年25岁 // 你好,我叫xiaoli,我今年18岁
这样既有公用的继承函数方法,又可以传递参数,可以获得自己想要的数据。
我们最后可以在验证一下是否是Person的实例问题。
console.log(p1 instanceof person) // true
这就是今天所写的内容,在最后,补充一下知识,也能会成为面试的考点:
问题:
构造函数在new的时候,JS引擎做了那些事情?(new之后发生了什么?)
- 创建了一个对象
- 将构造函数内的this,指向这个对象
- 执行构造函数
- 返回这个对象
博主联系方式:
e-mail:avillin@163.com
weChat:VillinWeChat
欢迎提出宝贵意见
低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
前端学习笔记(4) - JavaScript宏观与微观任务
宏观和微观任务 在 ES3 和更早的版本中,JavaScript 本身还没有异步执行代码的能力,这也就意味着,宿主环境传递给 JavaScript 引擎一段代码,引擎就把代码直接顺次执行了,这个任务也就是宿主发起的任务。 但是,在 ES5 之后,JavaScript 引入了 Promise,这样,不需要浏览器的安排,JavaScript 引擎本身也可以发起任务了。 由于我们这里主要讲 JavaScript 语言,那么采纳 JSC 引擎的术语,我们把宿主发起的任务称为宏观任务,把 JavaScript 引擎发起的任务称为微观任务。 宏观任务的队列就相当于事件循环。在宏观任务中,JavaScript 的 Promise 还会产生异步代码,JavaScript 必须保证这些异步代码在一个宏观任务中完成,因此,每个宏观任务中又包含了一个微观任务队列。 有了宏观任务和微观任务机制,我们就可以实现 JavaScript 引擎级和宿主级的任务了,例如:Promise 永远在队列尾部添加微观任务。setTimeout 等宿主 API,则会添加宏观任务。 如何分析异步执行的顺序 首先我们分析有多少个宏任...
- 下一篇
JAVA8 MAP新增方法详解
1、compute default V compute(K key,BiFunction<? super K,? super V,? extends V> remappingFunction)Attempts to compute a mapping for the specified key and its current mapped value (or null if there is no current mapping). 尝试通过Lambda表达式重新计算给定KEY的映射值并更新MAP(值为null则删除KEY,否则重新写入)。 Map<String, String> tMap = new HashMap<String, String>() { { put("A", "AAA"); put("B", "BBB"); } }; tMap.compute("A", (k, v) -> v == null ? "AAA" : v.concat("AAA"));//KEY存在VALUE不为空且Lambda计算结果不为空,更新KEY Syst...
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
-
Docker使用Oracle官方镜像安装(12C,18C,19C)
- Springboot2将连接池hikari替换为druid,体验最强大的数据库连接池
- CentOS8编译安装MySQL8.0.19
- Docker快速安装Oracle11G,搭建oracle11g学习环境
- SpringBoot2配置默认Tomcat设置,开启更多高级功能
- MySQL8.0.19开启GTID主从同步CentOS8
- CentOS7,8上快速安装Gitea,搭建Git服务器
- Jdk安装(Linux,MacOS,Windows),包含三大操作系统的最全安装
- SpringBoot2编写第一个Controller,响应你的http请求并返回结果
推荐阅读
最新文章
- Springboot2将连接池hikari替换为druid,体验最强大的数据库连接池
- Docker使用Oracle官方镜像安装(12C,18C,19C)
- CentOS6,CentOS7官方镜像安装Oracle11G
- SpringBoot2整合Redis,开启缓存,提高访问速度
- Jdk安装(Linux,MacOS,Windows),包含三大操作系统的最全安装
- SpringBoot2配置默认Tomcat设置,开启更多高级功能
- SpringBoot2整合MyBatis,连接MySql数据库做增删改查操作
- Hadoop3单机部署,实现最简伪集群
- MySQL8.0.19开启GTID主从同步CentOS8
- SpringBoot2编写第一个Controller,响应你的http请求并返回结果