JavaSrcipt红宝书阅读导图(三)
-
五、面向对象
对象是无序属性的集合,属性可以包含基本值、对象或者函数
1. 属性类型
1.1 数据属性
[[Configurable]]
-
- 能否通过
delete
函数而重新定义属性
- 能否通过
-
- 能否修改属性特性
[[Enumerable]]
-
- 能否
for-in
- 能否
[[Writable]]
-
- 能否修改
[[Value]]
-
- 数据值
- 修改特性
-
Object.defineProperty(objName,propName,desciptor)
-
Configurable
修改为false
之后无法恢复
1.2 访问器属性
[[Configurable]]
[[Enumerable]]
[[Get]]
-
- 读取时调用函数
[[Set]]
-
- 赋值时调用函数
- 定义多个属性
-
Object.defineProperties()
- 读取属性特性
-
Object.getOwnPropertyDescriptor()
2. 创建对象
2.1 工厂模式
function createPerson(name, age, job){ let o = new Object(); //... return o; } let person1 = createPerson("Bonne",12,"Writer");
2.2 构造函数模式
- 构造经历步骤
-
- 创建一个对象
-
- 将构造函数的作用域赋给新对象
-
- 执行构造函数中的代码
-
- 返回新对象
function Person(name, age, job){ this.name = name; this.age = age; this.job = job; //... } let person = new Person("Nick",11,"Doctor");
- 对象都拥有
constructor
属性,指向对象类型 - 将构造函数作为函数
let person = new Person(...) //构造函数 Person(...) //挂在window对象下 let o = new Object();Person.call(o,...); //在另一个对象的作用域中使用
- 问题
-
- 在构造函数中定义的函数会导致不同的作用域链和标识符解析,即是不同的函数
需要在构造函数外部定义函数,再在构造函数中将函数指针赋值
- 在构造函数中定义的函数会导致不同的作用域链和标识符解析,即是不同的函数
2.3 原型模式
function Person(){} Person.prototype.name = "Google"; Person.prototype.age = 23; Person.prototype.sayname = function(){ //... }; let person1 = new Person();
prototype
属性-
- 是一个指针,指向一个对象
-
- 可以共享它所包含的属性和方法
- 理解
-
- 只要创建一个新函数,就会创建一个
prototype
属性,指向原型对象
- 只要创建一个新函数,就会创建一个
-
- 所有原型对象在默认下自动获得一个
constructor
属性,包括指向prototype
属性所在函数的指针
- 所有原型对象在默认下自动获得一个
-
- 关系
-
-
- 构造函数的
prototype
指向原型对象
- 构造函数的
-
-
-
- 原型对象的
constructor
指回构造函数
- 原型对象的
-
-
-
- 原型对象中包括后来添加的属性
-
-
-
- 对象实例的
__proto__
指向原型对象
- 对象实例的
-
-
isPrototypeOf()
-
-
- 确定实例与原型对象是否有关系
-
-
getPrototypeOf()
-
-
- 返回的对象是对象的原型
-
-
- 实例需要有与原型对象重名的属性时,就在实例创建该属性,就会屏蔽原型属性
-
-
delete
删除实例属性后,再访问该属性会获取原型的值
-
-
- 获取属性时,先从实例中询问是否有该属性,如果没有,就到原型中询问是否有该属性
-
hasOwnproperty()
-
-
- 属性是否来自实例
-
in
-
- 属性是否能够被访问
-
hasPrototypeProperty()
-
-
- 访问的属性是否在原型对象中且没有在实例声明
-
-
for-in
-
-
- 枚举所有属性
-
-
-
Enumerable
为false
时不能枚举
-
-
Object.keys()
-
-
- 返回可枚举的属性的字符串数组
-
prototype
可重写为新对象-
- 没有默认声明
constructor
,因此需要手动赋值到对象类型
- 没有默认声明
- 动态性
-
- 原型查找值是一次搜索,即代码从上往下执行,下面声明的原型不会提升
- 问题
-
- 原型中的引用会共享,导致数组公用
2.4 组合使用构造函数模式和原型模式
function Person(name, age, job){ this.name = name; this.age = age; this.job = job; this.friends = ["Shell", "Twitter"]; //不同实例的friends分配了不同的空间 } Person.prototype = { //手动赋值到对象类型 constructor: Person, sayname: function(){ //... } //方法对于所有的实例都共用代码 }
2.5 动态原型模式
2.6
寄生构造函数模式function Person(name, age, job){ var o = new Object(); //先创建一个对象 o.name = name; //对实例属性赋值 //... return o; }
- 不能用
instanceof
,不推荐
2.7 稳妥构造函数模式
- 不能用
instanceof
3. 继承
3.1 原型链
- 一个对象类型A的原型对象是另外一个对象类型B的实例,称对象类型A继承了对象类型B
- 一个原型是另一个类型的实例,层层递进,形成链,就是原型链
- 搜索机制
-
- 实例->原型->原型所指向的实例原型->...
- 默认都继承原型
Object.prototype
- 子类型原型的属性可以屏蔽父类型属性
- 问题
-
- 父类型构造函数中的数组将在子类型中共用同一个空间
-
- 在创建子类型时,不能向父类型的构造函数中传递参数
3.2 借用构造函数
- 可以向父类型的构造函数中传递参数
- 问题
-
- 无法复用方法
function superType(){ //... } function subType(){ superType.call(this); //继承superType //... }
3.3 组合继承
- 父类型构造函数解决引用问题
- 子类型构造函数
call
解决传递参数问题 - 子类型原型模式
prototype
继承方法和定义方法 -
- 有两次调用父类型构造函数的缺点
3.4 原型式继承
Object.create()
- 引用的数组、对象会共享
3.5 寄生式继承
- 在调用函数中创建对象,在对象中赋予其他属性与方法,返回该对象
- 无法复用方法的代码
3.6 寄生组合式继承
- 最理想的继承方式
- 只有一次调用父类型构造函数
function Super(){ //... } function Sub(){ Super.call(this); //... //其他属性 } Sub.prototype = Object.create(Super.prototype); //创建、指定对象 Sub.prototype.constructor = Sub; //增强对象 //... //其他方法
六、函数
1. 函数声明提升
- 执行之前会先预读函数声明
- 可以调用后面代码声明的函数
- 函数表达式不会提升
2. 递归
- 使用
argument.callee
来递归,可以避免函数指针问题 - 使用函数表达式也可以避免函数指针问题
3. 闭包
- 有权访问另一个函数作用域中的变量的函数
- 作用域链本质上是一个指向变量对象的指针列表,只是引用而没有复制
- 在函数A内部创建另一个函数B,B使用A中的变量
-
- 在B中访问A的变量时会在作用域链中搜索变量
-
- 当B执行完毕后,B的作用域链会销毁
function createComparisonFunction(propertyName){ return function(obj1, obj2){ var v1 = obj1[propertyName]; var v2 = obj2[propertyName]; //... }; } var compareNames = createComparisonFunction('name'); //执行完createComparisonFunction函数后,活动对象没有被销毁,作用域链保留,arguments并没有消除 var res = compareNames({name: 'Nick'}, {name: 'Greg'}); //因为变量propertyName依然为'name',所以可以执行比较 compareNames = null; //解除引用,消除活动对象
- 闭包与变量
-
- 闭包只能取得包含函数中任何变量的最后一个值
this
-
- 匿名函数的执行环境具有全局性,因此
this
对象通常指向window
- 匿名函数的执行环境具有全局性,因此
- 内存泄漏
-
- 如果闭包中变量是HTML元素,将导致作用域链无法消除
-
- 在闭包函数末尾将HTML变量置为
null
,可以正常回收内存
- 在闭包函数末尾将HTML变量置为
- 模仿块级作用域
-
- JavaScript没有块级作用域的概念
-
- 在函数的语句块中声明变量,都会定义在该函数的活动对象中,导致在语句块外也可以访问这个变量
-
- 语法
-
-
(function(){ ...})();
-
- 私有变量
-
- 在构造函数中定义变量与函数,但是没有赋值到属性当中,这些变量与函数就成为私有变量、私有函数
-
arguments
也可以作为私有变量
-
- 静态私有变量
-
- 模块模式
-
- 增强模块模式