一. JS的对象

1.1 创建对象的几种方式

1.1.1 通过字面量创建对象

在js中,一对{} 其实就是一个对象

var person = {
name: "tom",
age: 23,
read: function () {
console.log(name, ": read book")
}
}

1.1.2 通过系统的构造函数

通过系统的构造函数创建一个空的对象,然后用js动态语言的特性,如果一个对象没有某个属性或者方法,那么我们点一下再附上值就好了

  			var person2 = new Object()
person2.name = "jerry"
person2.age = 23
person2.say = function () {
console.log(person2.name, ": say hello")
}

1.1.3 通过自定义构造方法

自定义构造方法一般都是首字母大写的函数

			 function Person(name, age, sex) {
this.name = name
this.age = age
this.sex = sex
this.say = function () {
console.log(this.name, " :say hello")
}
} // 创建对象时,使用 new 关键字
p = new Person("tom", 23, "man")
console.log(p instanceof Person)

自定义的构造方法创建对象,会经历如下几个步骤

  1. 开辟空间
  2. 将this设置成当前对象
  3. 初始化属性和方法
  4. 将this返回

1.1.4 工厂模式创建对象

     function Person(name,age,sex) {
// new Object 作为当前的返回值
var obj = new Object()
obj.name = name
obj.age = age
obj.sex = sex
obj.say = function () {
console.log(this.name," :say hello")
}
// 手动将对象返回出去
return obj
}
// 工厂模式创建对象,不需要使用new 关键字
var p = Person("tom",23,"man")
console.log(p instanceof Person) // false

1.2 构造函数与实例对象

看下面的例子:

   			// 构造函数和实例的关系
function Person(name) {
this.name = name
this.say = function () {
console.log(this.name," :say hello")
}
} // 对象p是通过 自定义的构造函数Person创建出来的
var p = new Person("tom") console.dir(p)
console.dir(Person)

打印的结果如下:

  • 实例对象的__proto__属性中有constructor属性,上面记录着自己的构造方法。
  • Person是构造方法,也是对象,我们直接打印Person得到的结果中有个属性prototype,它里面也有个属性叫做 constructor。里面记录着构造方法就是自己本身。
  • 结合上面的例子,我们其实可以得到这样的推断,实例对象的原型属性 和 构造函数的原型属性中的constructor都指向了同一个构造方法 ,然后可以进一步推断 p是Person类型

__prototype__实际上就是原型对象,在下文中会详细的说

还是上面的例子,看如下的输出也就能理解了

 			  console.log(p.constructor === Person) // true
console.log(p.__proto__.constructor == Person) // true
console.log(p.__proto__.constructor == Person.prototype.constructor) // true
// 由此推断出,p === Person
console.log(p instanceof Person) // true

其实有个小问题,看上面代码的第一行console.log(p.constructor === Person) 我们通过上面的代码也看不到实例对象p constructor属性啊,怎么就能用,也不报错undefined呢?

其实这就是牵扯到js对象的原型链了,(下面的章节会说),总的来说,就是js的对象会优先使用构造方法中的属性和方法,如果构造函数中不存在我们使用的属性和方法的话,就尝试去这个对象所对应的构造方法中的原型对象中的属性和方法,再没有就会报错。

二. JS的原型

2.1 引入原型的必要性

为什么会突然再来看js的原型呢?

因为看到了vue的源码中,大量的方法都被添加再vm的原型上,所以,回顾一下原型肯定是躲不过去了。

一般我们使用原型就是为了节省空间。

想理解节省了什么空间? 那就看看下面这个不节省空间的例子。

  			// 构造函数创建对象带来的问题
function Person(name) {
this.name = name
this.say = function () {
console.log(this.name,": say hello")
}
} var p1 = new Person("tom")
var p2 = new Person("jerry") p1.say() // tom : say hello
p2.say() // jerry : say hello // todo 返回false, 表示说,p1和p2的say方法,并不是同一份, 其实这并不是一件好事
console.log(p1.say == p2.say)

上面的p1 和 p2 都是通过一个构造函数创建出来的不同对象,他们里面都有say这个函数,当我们输出 p1.say == p2.say 时,返回了false,说明每个对象中都有一份say方法,那假设有1000个对象,岂不是就有1000个say方法了? 这肯定是浪费空间的。

那么有没有办法可以让每次new出来的对象都使用一份say方法呢?

当然,如下:

			 // 共享函数,引出原型
function Say() {
console.log(this.name, ": say hellp")
} function Person(name) {
this.name = name
this.say = Say
} var p1 = new Person("tom")
var p2 = new Person("jerry") p1.say()// tom : say hellp
p2.say()// jerry : say hellp // 这样的话,确实能实现节省空间,但是容易出问题
console.log(p1.say == p2.say) // ture

现在确实实现了我们的需求,但是不够优雅,而且统一出现问题,js是动态类型的语言,那我们像下面这样,假设不知道已经有Say这个函数了,然后将var Say = "hello"放置在第Say函数之后,就会产生覆盖。

2.2 认识原型

看下的例子:我们往构造方法的原型对象上添加一个say方法。

其实这块也不是不好理解,你想啊,js的对象通过构造方法创建出来,我们把公共的方法,属性放在构造方法的原型对象中,是不是就可以让他们共享这些方法和属性呢?

				function Person(name) {
this.name = name
} // 在原型上添加方法
// 为什么可以说原型是对象呢? 想想js中一个对象可以通过 点 , 动态点添加属性和方法?
Person.prototype.say = function () {
console.log(this.name,":say hello")
} var p1 = new Person("tom")
var p2 = new Person("jerry") p1.say()//tom :say hello
p2.say()//jerry :say hello
console.log(p1.say == p2.say) // true

通过console.dir()打印下上面的实例对象和构造函数,得到如下图:

   		  console.dir(p1)
console.dir(p2)
console.dir(Person)

通过上图可以看到,可以得到下面的结论:

  • 实例对象中的直接拥有的标准属性,比如name, 这些都是直接出现在构造方法中的属性,而且这些属性是js对象所私有的。
  • 上图中实例对象有个属性叫做:__proto__ , 这个属性是用来给浏览器使用的,而不是给程序员使用,所以我们称它为非标准属性。 此外谷歌浏览器是支持这个属性的,但是在IE8浏览器中,我们执行这句console.log(p1.__proto__) 会报错,说undefined

2.3 原型,实例对象,构造函数之间到底是什么关系呢?

  1. 实例对象是通过 new 构造函数创建出来的,所以构造函数是创建实例对象的模版。

  2. 构造函数就是那个首字母大写的函数,在js里面我们能直接console.log(构造函数) 因为这个构造函数其实也是个对象。

  3. 原型的作用我们说了,就是为了将公共的方法抽取出来,全部存放在构造函数的原型对象中,而实现数据的共享,节省内存。

  4. 实例对象的·__proto__, 是个非标准属性,也是个对象,这个对象指向了 构造方法的prototype 属性。

  5. 构造方法的 prototype属性是个标准属性, 同时也是个对象,我们对通过 构造方法.prototype.属性/方法 = XXX 的方式为其添加属性和方法。

  6. 我们通过 对象.属性/方法时, 会优先从对象的构造方法中查找,如果找不到的会再尝试从原型中查找,这也是为什么会出现一个明明没有为一个对象添加相应的属性或者方法但是对象却能点出来,并且能正常使用。当然如果原型中也不存在的话,就会报错说 undefined

2.4 关于this对象

  • 看下面的第一个例子

下面出现的this并不难理解, 就是我们new 出来的对象本身

 			  function Person(name) {
// 考虑一下,这个this是谁?
this.name = name
console.log(this)
} var p = new Person("tom")
  • 看下面的第二个例子

我们在构造方法的原型对象上添加一个方法say,在这say方法中使用的this对象指的同样是 我们new 出来的对象本身。即方法的调用者。

      	function Person(name) {
// 考虑一下,这个this是谁?
this.name = name
console.log("n10: ",this)
} Person.prototype.say = function () {
// todo 这里的this指的是谁呢?
// 首先,方法是添加在原型对象上, 那么this指的是原型对象吗?
// 通过控制台可以看到,this.name ,其实不是原型对象,而是say()方法的调用者(实例对象)
console.log("n16: ",this.name,": say hello")
} var p1 = new Person("tom")
var p2 = new Person("jerry") p1.say()
p2.say()
  • 看下面的第三个例子:

下面在给构造方法的原型对象添加方法时,不仅出现了this, 还出现了that。

this对象依然是我们手动new出来的对象本身。

that同样是指向了我们new出来的对象本身,之所以需要中转一下,是因为在按钮的点击事件里面,this指向的是按钮本身。

    // 用面向对象的方式封装构造函数
function ChangeStyle(btnId, dvId, color) {
this.btnObj = document.getElementById(btnId)
this.dv = document.getElementById(dvId)
this.color = color
} // 在构造方法的原型上添加方法
ChangeStyle.prototype.init = function () {
// 这里面的this表示的是 调用init方法的实例对象
var that = this
this.btnObj.onclick = function () {
// todo 为什么原型中的函数中,就不能使用this,而是that呢???
// todo 或者问下,当前函数中的this是谁呢?
that.dv.style.backgroundColor = that.color
}
}

2.5 其他原型的写法

  • 最常见的写法就是像下面这样,在当前原型的基础上添加属性或者方法
 		function Person(name) {
this.name = name
} // 前面的例子中我们都是像下面这样写代码, 这其实是对原来的 原型对象属性的累加
// 原来的原型对象中有个属性,叫做consturctor
Person.prototype.say = function(){
//todo
}
  • 也可以像下面这样

这样设置原型的话,实际上是对原来的原型对象的覆盖,所以说需要像下面这样重新添加constructor的指向。

当然我也试了一下,如果说覆盖原来的原型对象,且不添加contructor的指向,我们使用 instanceof 判断实例对象是否是对应的构造函数类型时,还是能得到正确的结果。

   Person.prototype = {
constructor:Person, // 手动修改构造器的指向
height:"20",
weight:"20",
say:function () {
// todo
}
}

2.6 方法之间的相互访问

  • 构造函数中的成员方法是可以相互访问的。
   function Person(name) {
this.name = name
this.say = function () {
console.log("say")
// 通过这个例子,可以看到,对象的方法中可以直接调用对象的方法
this.eat()
}
this.eat = function () {
console.log("eat")
}
}
  • 原型中的方法也是可以相互访问的。
    function Person(name) {
this.name = name
} Person.prototype.say = function(){
console.log("say")
// 原型中的方法也可以相互访问
this.eat()
} Person.prototype.eat = function(){
console.log("eat")
} var p1 = new Person("tom")
p1.say()

2.7 覆盖内置对象原型中的方法

像这样就可以实现对原型中的方法进行覆盖的操作。

当然可以通过在原型上添加方法实现对原有封装类的拓展。

	  // 在现有的js封装类上干这件事,也算是在修改源码
String.prototype.myReverse = function () {
for (var i = 0; i < this.length; i++) {
console.log("发生倒叙")
}
}
var str = "123"
str.myReverse()

JS对象与原型的更多相关文章

  1. js对象,原型,call,apply浅析

    //对象直接量,创建对象最简单的方式是在js里使用对象直接量 var book = { "main title": "js", //属性里有空格,要用引号 &q ...

  2. JavaScript学习(二)——深入学习js对象的原型与继承

    了解对象  什么是对象?   …… 这个就不说了 对象的声明的两种方式 var person = new Object(); person.name="linchen"; pers ...

  3. 深入理解JS对象和原型链

    函数在整个js中是最复杂也是最重要的知识 一个函数中存在多面性: 1.它本身就是一个普通的函数,执行的时候形成的私有作用域(闭包),形参赋值,预解释,代码执行,执行完 成后栈内存销毁/不销毁. 2.& ...

  4. JS对象与原型链

    每个函数都存在一个prototype的属性,然后这个属性值为一个对象,我们称之为原型对象 每个对象都存在着一个隐藏的属性"__proto__" 这个属性引用了创建这个对象的函数的p ...

  5. js对象4-js原型--杂志

    提问:在js中什么是原型 prototype 每个学js的人都有自己的解释,网上一大堆的解释与应用,但是看了他们的解释表示还是不理解怎么办?(原因是他们说的太天花乱坠了) 官方手册解释:prototy ...

  6. JS对象、原型链

    忘记在哪里看到过,有人说鉴别一个人是否 js 入门的标准就是看他有没有理解 js 原型,所以第一篇总结就从这里出发. 对象 JavaScript 是一种基于对象的编程语言,但它与一般面向对象的编程语言 ...

  7. 前端开发JS——对象与原型

    27.创建对象 ①工厂模式批量创建对象  缺点:无法对象识别,即所有对象都是Object类型;方法内存空间浪费/封装不太完善 function sayName(){    //可以有效节省内存空间 c ...

  8. JS对象、原型、this学习总结

    1.对象是函数创建的,而函数却又是一种对象.(属性的集合) 2.每个函数都有一个属性叫做prototype.这个prototype的属性值是一个对象,默认的只有一个constructor的属性,指向这 ...

  9. js 对象细节

    原型和原型链 在对象自身身上找不到指定属性时,就会到这个对象的原型__proto__上找,原型也是指向一个对象,在这个对象上还找不到对应属性,则继续到原型上来找...以上过程形成原型链. 访问对象的原 ...

随机推荐

  1. matplotlib TransformedBbox 和 LockableBbox

    TransformedBbox 和 LockableBbox 都是BboxBase的子类.TransformedBbox支持使用变换来初始化bbox, LockableBbox可实现锁定bbox的边不 ...

  2. ASP.NET Core 3.1+MySQL 部署到docker上面使用docker-compose+DockerFile

    一.新建DockerFile文件 选择Linux版本 FROM mcr.microsoft.com/dotnet/core/aspnet:3.1-buster-slim AS base WORKDIR ...

  3. 大数据篇:Spark

    大数据篇:Spark Spark是什么 Spark是一个快速(基于内存),通用,可扩展的计算引擎,采用Scala语言编写.2009年诞生于UC Berkeley(加州大学伯克利分校,CAL的AMP实验 ...

  4. 如果我选择IT行业,会不会在几年,或者几年后被社会给淘汰??

    IT互联网各行业薪资占比,你能拿到多少?随着移动互联网时代的发展,IT行业的需求量也越来越大,而且每年都会新增,当然也会有淘汰. 人生如此之短,都不喜欢自己虚度光阴,也不希望自己所努力的东西成为历史, ...

  5. Missing artifact net.sf.json-lib:json-lib:jar:2.2.3

    <!-- https://mvnrepository.com/artifact/net.sf.json-lib/json-lib --><dependency> <gro ...

  6. JavaScript基础笔记1220

    JavaScript笔记1.JavaScript关键词2.JavaScript标识符 必须以字母,下划线(_)或美元符($)开始. 后续的字符可以是字母.数字.下划线或者美元符 (数字是不允许作为首字 ...

  7. vue2.x学习笔记(一)

    使用vue开发项目已经过了一段时间了,对其中的很多东西还是一知半解,于是想要系统学习一下.主要内容是参照官方中文网站https://cn.vuejs.org/v2/guide/,然后加上一些自己的理解 ...

  8. vue如何添加jquery?

    1.首选通过npm安装jquery? 2.在build/webpack.base.conf文件当中引入jquery <pre>module.exports = { ... resolve: ...

  9. 图解Knative核心组件Serving基础设计

    最近闲下来,打算把Knative的核心组件Serving给学习下,会继续采用k8s源码学习的方式,管中窥豹以小击大,学习serving的主要目标: 可观测性基础设施.自动伸缩.流量管理等核心组件的设计 ...

  10. Exercise

    """ 问:执行完下面的代码后, l,m的内容分别是什么? """ def func(m): for k,v in m.items(): m ...