一. 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. cgdb使用方法

    cgdb --args [exe_name] [arg1] [arg2] [arg3] [...] 进入代码窗口 按ESC键 进入调试窗口 按i键 调试命令 r 运行 n 单步执行(不进入函数) s ...

  2. nghttp2 交叉编译

    touch run.sh chmod 755 run.sh mkdir build cd build ../run.sh run.sh #!/bin/bash #cd build ../configu ...

  3. Java入门系列之线程池ThreadPoolExecutor原理分析思考(十五)

    前言 关于线程池原理分析请参看<http://objcoding.com/2019/04/25/threadpool-running/>,建议对原理不太了解的童鞋先看下此文然后再来看本文, ...

  4. SwiftUI - 一步一步教你使用UIViewRepresentable封装网络加载视图(UIActivityIndicatorView)

    概述 网络加载视图,在一个联网的APP上可以讲得上是必须要的组件,在SwiftUI中它并没有提供如 UIKit 中的UIActivityIndicatorView直接提供给我们调用,但是我们可以通过 ...

  5. AJ学IOS(25)UI之触摸事件

    AJ分享,必须精品 iOS中的事件 在用户使用app过程中,会产生各种各样的事件;iOS中的事件可以分为3大类型: 响应者对象–UIResponder 在iOS中不是任何对象都能处理事件,只有继承了U ...

  6. AJ整理问题之:内存堆栈

    内存 数据在内存中的存放 在计算机中,运行的应用程序的数据都是保存在内存中的. 不同类型的数据,保存的内存区域不同,其中包括: 1:栈区(stack)由编译器自动分配并释放,一半存放函数的参数值,局部 ...

  7. webWMS开发过程记录(六)- 详细设计之系统管理

    一.功能说明 1. 权限管理 (参考“权限管理-百度百科") 定义:一般指根据系统设置的安全规则或安全策略,用户可以访问而且只能访问自己被授权的资源,不多不少. 分类:从控制力度来看,通常分 ...

  8. U - Obtain a Permutation CodeForces - 1294E 思维

    题解: 注意每一列与每一列之间互不影响,所以贪心地求出没一列的最小操作值,然后累加起来. 怎么求没一列的最小值呢?维护一个数组same表示其中same[i]=j表示将该序列向上翻滚i次有j个元素归位, ...

  9. 《并发编程的艺术》阅读笔记之Lock与AQS

    Lock接口 在jdk1.5之后,并发包下新增了一个lock接口,lock接口定义了锁的获取,锁的释放,等方法,需要用户手动设置.与关键字不同的是,lock具有可操作性,比如,可以中断线程,设置超时时 ...

  10. IO多路复用小故事

    背景故事 小王住在某城市, 生活并长大. 最近, 小城引进了一个企业, 邮局. 这个邮局可了不得, 只要你花上几角钱, 就可以将一封信送到千里之外的朋友手中. 小王也趁机体验了一把, 得劲. 这天, ...