js 的对象比较
由于 js 是解释执行的语言, 那么再代码中出现函数与对象如果重复执行, 会创建多个副本
1.在代码中重复执行的代码容易出现重复的对象
2传统的构造方法的定义方式会影响性能, 容易造成多个对象有多个方法副本. 应该将方法单独抽取出来. 让所有的对象共享该方法.
3.可以考虑将方法全部放到外面但是有安全隐患
- 在开发中会引入各种框架或库. 自定义的成员越多, 出现命名冲突的几率越大
- 可能在开发中会有多个构造函数. 每一个构造函数应该有多个方法. 那么就会变得不容易维护.
4.任意一个对象都会默认的链接到它的原型中
- 创建一个函数. 会附带的创建一个特殊的对象. 该对象使用 函数.prototype 引用. 称其为函数的原型属性.
- 每一个由该函数作为构造函数创建的对象, 都会默认的连接到该对象上.
- 在该对象访问某一个方法或属性的时候, 如果该对象中没有, 就会到这个特殊的对象中去查找
传统构造函数的问题
function Foo() { this.sayHello = function () { } }
1.由于对象是调用 new Foo()
所创建出来的. 因此每一个对象在创建的时候, 函数 sayHello 都会被创建一次
2.那么每一个对象都含有一个独立的, 不同的, 但是功能逻辑一样的函数. 比如:{} == {}
3.在代码中方法就会消耗性能. 最典型的资源就是内存.
4.这里最好的办法就是将函数体放在构造函数之外. 那么在构造函数中只需要引用该函数即可
function sayHello() {} function Foo() { this.say = sayHello; }
5.传统构造函数会在开发中变得困难: 引入框架危险, 代码繁冗不好维护. 解决办法就是外面的函数如果不占用名字. 而且在函数旗下就好了.
6.每一个函数在定义的时候, 有一个特殊的对象被创建出来.
7.每一个由构造函数创建的对象都会默认的连接到该特殊的对象上
var f1 = new Foo(); var f2 = new Foo(); f1.sayHello(); // 如果 f1 没有sayHello方法, 那么就会在 Foo.prototype 中去找 f2.sayGoodBye(); // 如果 f2 没有sayGoodBye方法, 那么就会在 Foo.prototype 中去找
8.由构造函数创建出来的众多对象共享一个对象, 就是 构造函数.prototype
9.只需要将共享的东西, 重复会多占用内存的东西放到 构造函数.prototype 中, 那么所有的对象就可以共享了.
function Foo() {} Foo.prototype.sayHello = function () { console.log( ... ); }; var f1 = new Foo(); f1.sayHello(); var f2 = new Foo(); f2.sayHello(); f1.sayHello === f2.sayHello
原型相关概念
关于面向对象的概念:
- 类 class: 在 js 中就是构造函数
- 在传统的面向对象语言中, 使用一个叫做类的东西定义模板. 然后使用该模板创建对象.
- 在构造方法中也具有类似的功能. 因此称其为类
// 在 java 中, 最小的代码单位是 类 class Program { // 成员 }
- 实例 ( instance ) 与对象 ( object )
- 实例一般是指某一个构造函数创建出来的对象. 我们成为 xxx 构造函数的实例
- 实例就是对象. 对象是一个泛称.
- 实例与对象是一个近义词
- 键值对与属性和方法
- 在 js 中键值对的集合称为对象
- 如果值为数据( 非函数 ), 就称该键值对为属性 property
- 如果值为函数( 方法 ), 就称该键值对为方法 method
- 父类与子类
- 传统的面向对象语言中使用类来实现继承. 那么就有父类, 子类的概念
- 父类又称为基类, 子类又称为派生类
- 在 js 中常常称为父对象, 子对象. 基对象, 派生对象.
原型相关的概念
- 构造函数.prototype针对构造函数称为 "原型属性"
- 构造函数.prototype就是构造函数的原型属性,简称原型
- 构造函数.prototype与构造函数所创建出来的对象也有一定关系
- 构造函数.prototype针对构造函数创建出来的对象称为 "原型对象",简称原型
- 对象继承自其原型
- 构造函数创建的对象 继承自 构造函数的原型属性
- 构造函数创建的对象 继承自 该对象的原型对象
- 构造函数所创建出来的对象与构造函数的原型属性表示的对象是两个不同的对象
- 原型中的成员, 可以直接被实例对象所使用
- 也就是说实例对象直接 "含有" 原型中的成员
- 因此 实例对象 继承自 原型
- 这样的继承就是 "原型继承"
proto
以前要访问原型, 必须使用构造函数来实现. 无法直接使用实例对象来访问原型. 火狐最早引入属性 __proto__
表示使用实例对象引用原型. 但是早期是非标准的. 通过该属性可以允许使用实例对象直接访问原型
function Person() {} // 神秘对象就是 Person.prototype // 那么只有使用 构造函数 才可以访问它 var o = new Person(); // 以前不能直接使用 o 来访问神秘对象 // 现在有了 __proto__ 后 // o.__proto__ 也可以直接访问神秘对象( 两个下划线 ) // 那么 o.__proto__ === Person.prototype
- 构造函数.prototype中默认都有一个属性
constructor
, 翻译为 构造器. 表示该原型是与该构造函数联系起来的. __proto__
的作用- 可以访问原型
- 由于在开发中除非特殊要求, 不要使用实例去修改原型的成员. 因此属性开发时使用较少
- 但是再调试过程中非常方便, 可以轻易的访问原型进行查看成员
- 如果在 早期的浏览器中使用 实例需要访问原型如何处理?
- 可以使用实例对象访问 构造器, 然后使用构造器访问原型
var o = new Person(); o.constructor.prototype
- 可以使用实例对象访问 构造器, 然后使用构造器访问原型
- 如果给实例继承自原型的属性赋值
function Foo() {} Foo.prototype.name = 'test'; var o1 = new Foo(); var o2 = new Foo(); o1.name = '张三'; // 不是修改原型中的 name 而是自己增加了一个 name 属性 console.log( o1.name + ', ' + o2.name );
对象的原型链
凡是对象就有原型, 那么原型又是对象, 因此凡是给定义一个对象, 那么就可以找到他的原型, 原型还有原型. 那么如此下去, 就构成一个对象的序列. 称该结构为原型链.
原型链的结构
凡是使用构造函数创建出对象, 并且没有利用赋值的方式修改原型, 就说该对象保留默认的原型链.
默认原型链:
function Person() {} var p = new Person();// p 具有默认的原型链
默认的原型链结构:
当前对象 -> 构造函数.prototype -> Object.prototype -> null
在实现继承的时候,有时候会利用替换原型链结构的方式实现原型继承,那么原型结构就会发生改变
function MyCollection() {} MyCollection.prototype = []; var arr = new MyCollection(); // arr -> [] -> Array.prototype -> Object.prototype -> null // var arr = new Array();
什么是原型式继承
所谓的原型式继承就是利用修改原型链的结构( 增加一个节点, 删除一个节点, 修改节点中的成员 ), 来使得实例对象可以使用整条链中的所有成员.
函数的构造函数 Function
在 js 中 使用 Function 可以实例化函数对象. 也就是说在 js 中函数与普通对象一样, 也是一个对象类型. 函数是 js 中的一等公民.
- 函数是对象, 就可以使用对象的动态特性
- 函数是对象, 就有构造函数创建函数
- 函数是函数, 可以创建其他对象
- 函数是唯一可以限定变量作用域的结构
函数的原型链结构
任意的一个函数, 都是相当于 Function 的实例. 类似于 {} 与 new Object() 的关系
- 函数应该有
__proto__属性
- 函数的构造函数是 Function
- 函数应该继承自
Function.prototype
Fucntion.prototype
继承自Object.protoype