banner
NEWS LETTER

前端原生【对象】Object

Scroll down

练武不练功,到头一场空,JavaScript 原生对象,被业务应用层的研发容易忽略的一些底层细节。

对象的属性类型

属性有两种类型:数据类型和访问器类型。

  • 数据属性包含一个值,比如自己定义的 name,sex,age 等,而对象里有一个内置的 [[Put]] 方法,它的默认行为是创建数据属性;
  • 访问器属性不包含值而是定义了一个当属性被读取时调用的函数(称为 getter)和一个当属性被写入时调用的函数(称为 setter)。

访问器属性仅需要 gettersetter 两者中的任意一个,当然也可以两者都有。

在对象字面形式中定义访问器属性有特殊的语法:
例:访问器类型 :

var person1 = {
_name: 'YangYangYang',

get name() {
console.log('我在get,设定访问器读取属性name')
return this._name
},

set name(value) {
console.log('我在set,设定访问器设置属性name:', value)
this._name = value
},
}

console.log(person1.name)
person1.name = 'Greg' // 这里设置属性name 触发了内置的set 直接
console.log(person1.name)

使用场景:当你希望对象的赋值操作会触发一些行为或读取的值需要通过计算所需的返回值得到时,访问器属性会非常有用!

补充:利用访问器属性 实现表单双向数据绑定

var txt = document.getElementById('txt'),
sp = document.getElementById('sp'),
obj = {}

// 给对象obj添加msg属性,并设置setter访问器
Object.defineProperty(obj, 'msg', {
// 设置 obj.msg 当obj.msg反生改变时set方法将会被调用
set: function (newVal) {
// 当obj.msg被赋值时 同时设置给 input/span
txt.value = newVal
sp.innerText = newVal
},
})

// 监听文本框的改变 当文本框输入内容时 改变obj.msg
txt.addEventListener('keyup', function (event) {
obj.msg = event.target.value
console.log(event.target.value)
})

属性特征:(数据属性)

数据属性和访问器属性的特征

通用特征


有两个属性特征是数据和访问属性都具有的。

  1. [[Enumerable]] (可枚举的):决定了你是否可以遍历该属性;
  2. [[Configurable]] (可配置的):决定了该属性是否可配置;
    配置的意思就是:你可以用 delete 删除一个可配置的属性,或者随时更改它,开放的!所有自己申明的对象属性默认都是可枚举、配置的!

改变属性特征:Object.defineProperty() ===> 译(定义属性的意思)

使用 Object.defineProperty 方法如果不指定,除了 value 其他都时 false

  1. 拥有该属性的对象(属性所在的对象);
  2. 属性名;
  3. 包含需要设置的特征的属性描述对象;
    🔔 注意:参数三:3) 是一个对象{} 这个对象里面有四个属性:
    第一个属性:value 指定的第二个参数的值 (可选)
    第二个属性:enumerable 是否可枚举
    第三个属性:configurable 是否可配置 (配置是指 可否删除? 可否重新定义) 类似于总开关
    第四个属性:wriable 是否可写入 就是是否可以更改属性值等

🔔 注意:configurable 修改为 false 以后 除了 wriable 其他都是不可逆的,不可重新配置的

栗:

var person1 = {
name: 'YangYangYang',
}

// 强制定义让该对象的name属性不可被枚举:

Object.defineProperty(person1, 'name', {
enumerable: false, // 这句话代表不可枚举; 严格来说是:person1这个对象当中
// 的name属性 不可被枚举;
})

console.log('name' in person1) // true 判断对象当中 有没有这个属性可以用in,但是in不能过滤继承来的原型属性和方法;严格的话用hasOwnProperty()方法;
console.log(person1.propertyIsEnumerable('name')) // false 注意:propertyIsEnumerable是用来判断这个属性是否可以被枚举的内置方法,返回布尔值;

// 把当前对象person1的所有键值对的键,也就是属性,放入一个数组当中!

var properties = Object.keys(person1)
console.log(properties) // [] 因为上面的属性 只有一个name,而且name被限制为不可枚举,所以得到的会是一个空数组;
console.log(properties.length) // 所以长度自然为0 ;

// ------------------ 分割线
// 强制定义让该对象的name属性为不可设置(配置),也就是不可被修改

Object.defineProperty(person1, 'name', {
configurable: false,
writable: false, // 这句话的意思是 可写的 就是可以修改属性的值 默认是true可以修改 但是为false 值就不可被修改
})

// 尝试修改值
person1.name = '你大爷'
console.log(person1.name)

// 尝试删除person1对象当中的name属性

delete person1.name
console.log('name' in person1) // 结果没被删除掉 结果不是undefined 而是true 代表属性在当前对象中存在

// 注意:下面我尝试将配置开放,使其可以删除

Object.defineProperty(person1, 'name', {
configurable: true,
})

// 结果是抛出了一个 error错误!说明无法将一个不可配置的属性变成可配置,同样,在不可配置的情况下试图将数据属性编为访问器属性或反向变更也会抛出错误!

总结:可配置属性的这个 Object.defineProperty() 这个方法 是一次性的!在定义对象的时候,只起一次效果!不存在覆盖情况!🔔 最后注意 如果使用了属性类型的 getset 方法就不能使用 writablevalue 的任意一个 否则报错!

数据属性的特征


数据属性额外拥有两个访问器属性不具备的特征

  1. [[Value]] : 包含属性的值,当你在对象上创建属性时该特征被自动赋值,匹配属性的值,键值对的值,对象中所有的属性的值都保存在 [[Value]] 中,哪怕该值是一个函数。
  2. [[Wriable]]:该特征是一个布尔值,指示该属性是否可以写入。所以属性默认是可写的,除非另外指定
    通过这两个额外特征,数据属性相当于拥有完整的四个属性特征;就是 Object.defineProperty() 方法的第三个参数里面的四个完整的参数!

所以常规的一个对象创建,并给这个对象添加属性以及属性值(就是添加键值对的过程)

显式的是这样子:

var person1 = {
name: 'YangYangYang',
}
// 这个很简单 就是创建了一个对象 给它添加了一个属性以及属性值,但是实际隐式机制创建是下面这样子

隐式的是这样子:

var person1 = {} // 这里还有var person1 = new Object();
Object.defineProperty(person1, 'name', {
value: 'YangYangYang',
enumerable: true, // 可枚举
configurable: true, // 可配置
writable: true, // 可写入
})

注意一点:当 Object.defineProperty()被调用时,它首先先检查属性是否存在,如果不存在,将根据属性描述对象指定的特征创建!如果使用 Object.defineProperty()方法给对象添加属性以及值,如果不针对参数进行设置,那么它们将默认都为 false 不可枚举、不可配置、不可修改!

访问器属性特征


访问器属性也有两个额外特征。访问器属性不需要存储值,因为也就不需要(没有) [[Value]] 和 [[Wriable]]。取而代之的是[[Get]] 和 [[Set]],内含 gettersetter 函数。和对象字面形式的 gettersetter 一样,仅需要定义其中一个特征就可以创建一个访问器属性 。
🔔 注意:如果试图创建一个同时具有数据特征和访问器特征的属性,将会得到一个错误!


在属性类型笔记里面的例子 可以改写为如下:

var person1 = {
_name: 'YangYangYang',
}

Object.defineProperty(person1, 'name', {
get: function () {
console.log('我在get设定访问器的读取属性name')
return this._name
},

set: function (value) {
console.log('我在set设定访问器设置属性name:', value)
},

// 同样使使这个访问器 可枚举、可配置
enumerable: true,
configurable: true,
})

🔔 注意:这里传给 Object.defineProperty() 的属性描述对象中的 get,set 关键字属性,它们是包含函数的数据属性,在这里不能使用属性类型笔记中的那种对象字面形式的特殊设置方法 set(){},get(){}
🔔 注意:这里访问器属性如果不在 Object.defineProperty()方法中设置枚举以及配置为 true 默认任何调用 Object.defineProperty()方法的对象,里面指定的第二个参数属性,都是不可枚举,不可配置的!

和对象字面字面形式定义的访问器属性一样,在严格模式下试图写入没有 setter 的访问器属性会抛出错误,在非严格模式下会失效(什么也不弹出),试图读取一个没有 getter 的访问器属性则总是返回 undefined。

获取(读取)属性特征

Object.getOwnPropertyDescriptor() 方法
此方法只可用于自有属性,接收两个参数:

  1. 对象;
  2. 属性名;
    如果属性存在,则返回一个属性描述对象,内含 4 个属性:configurableenumerable;另外两个属性则根据属性类型决定,看是数据属性还是访问器属性!

🔔 注意:如果是访问器属性,这个对象属性有 configurableenumerablegetset
如果是数据属性,这个个对象属性有 configurableenumerablewritablevalue

栗:

var person1 = {
name: 'YangYangYang',
}

var descriptor = Object.getOwnPropertyDescriptor(person1, 'name')
console.log(descriptor.enumerable) // true
console.log(descriptor.configurable) // true
console.log(descriptor.writable) // true
console.log(descriptor.value) // YangYangYang

这里,属性 name 作为对象字面形式的一部分被定义。调用 Object.getOwnPropertyDescriptor() 方法返回的属性描述对象具有 4 个属性,因为上面是数据属性,所以返回 enumerable,configurable,writablevalue,即使它们从没有没 Object.defineProperty()显式定义。

定义多重属性


如果使用 Object.defineProperties() 方法而不是 Object.defineProperty() 方法,可以为一个对象同时定义多个属性。
Object.defineProperties() 接收两个参数:

  1. 需要改变的对象;
  2. 一个包含所有属性信息的对象;(对象的属性与第一个参数对象要添加或修改的一一对应)

栗:

var person1 = {}

Object.defineProperties(person1, {
// 属性来存储数据
_name: {
value: 'YangYnagYang',
enumerable: true,
configurable: true,
writable: true,
},
// 如果需要 继续往下写 比如age:{},sex:{}等等 ,访问器数据一样
// 访问器属性
name: {
get: function () {
console.log('设定获取')
return this._name
},

set: function (value) {
console.log('设定设置')
this._name = value
},

enumerable: true,
configurable: true,
},
})

🔔 注意:上面代码 name 是对象里面属性的名字,设置有两种方式,一种利用 for in 循环 让所有属性匹配数据属性,或者如上一个一个去设置,上面仅设置了 name 属性,假设对象当中有 name 属性的话!

继承自 Object.prototype 的方法

所有的对象都继承顶级对象 Object.prototype 下面的 5 种方法

  • hasOwnPrototype() 方法: 检查是否存在一个给定名字的自有属性(用来判断和过滤继承属性的)

  • propertyEnumerable() 方法:检查一个自有属性是否可枚举

  • isPrototypeOf() 方法:检查一个对象是否是另外一个对象的原型对象 (具体可参考笔记)

  • valueOf() 返回一个对象的值表达 (返回指定对象的原始值)

  • toString() 返回一个对象的字符串表达式,强大之处能把数组嵌套 N 层的强制转成字符串表达(隐)

这 5 种方法由继承出现在所有对象中,也就是所有对象都可以使用这 5 种方法。

判断某个对象是否是另外一个对象的原型

isPrototypeOf() 以及 Object.getPrototypeOf()

通过 isPrototypeOf() 来确定对象之间是否存在这种关系,本质上讲,如果[[Prototype]] (就是proto) 指向调用 isPrototypeOf() 方法的对象 (Person.prototype),那么这个方法就返回 true

console.log(
Person.prototype.isPrototypeOf(person1), // true
Person.prototype.isPrototypeOf(person2) // true
)

这里,用原型对象的 isPrototypeOf() 方法测试了 person1 和 person2 ,因为它们内部都有一个指向 Person.prototype 的指针,因此返回 true

isPrototypeOf(需要判断的对象名)
该方法被包含在所有对象中

var obj = {}
console.log(Object.prototype.isPrototypeOf(obj)) // 结果为true
// 任何实例化的对象,包括被new的对象它的隐式原型__proto__也就是[[Prototype]]
// 都指向了创建这个实例对象的构造函数的原型!这里创建obj实例对象的构造函数是
// Object 而实例对象的隐式原型指向了这个构造函数的原型Object.prototype;

🔔 注意:可以使用 Object.getPrototypeOf() 方法读取 __proto__ 也就是 [[Prototype]] 隐式原型属性的值

栗:

var obj = {}
var proto = Object.getPrototypeOf(obj) // 实例对象通过getprototypeOf() 获取实例对象的隐式原型
console.log(proto) // 同下面的一样
console.log(obj.__proto__) // 同上面的一样
console.log(proto === obj.__proto__) // true
// ------------- 分割线
console.log(obj.constructor === Object) // true
console.log(proto === Object.prototype) // true
// 任何一个实例化的对象 隐式原型__proto__ 总是指向了Object顶级对象的原型prototype

🔔 注意:上面代码 Object.getPrototypeOf(obj)得到的其实就是 obj 实例对象的 __proto__

拓展:

function Person() {}
var person1 = new Person()
var person2 = new Person()

console.log(
Object.getPrototypeOf(person1) === Person.prototype, // true
Person.prototype.isPrototypeOf(person1) // true
)

返回的对象实际就是这个对象的原型。

Object.create() 方法


该方法接收接收两个参数:

  1. 要继承的对象,也就是说,要通过原型链 __proto__也就是 [[prototype]] 往上查找的对象
  2. 可选参数:属性的描述对象,描述属性特征!是否枚举?是否可配置?是否可读写?以及 value
    如果第二个参数使用了不写,默认为 false

栗:

var person = {
name: 'Jonly',
firends: ['Luna', 'Eva', 'Van']
}

var obj = Object.create(person, {
name:{
value:'Jonly哥'
}
})
console.log(obj.name)
console.log(obj.firends)
console.log(Object.getOwnPropertyDescriptors(obj)); /
// Object.create() 不可枚举 不可扩展 不可写入

常规对象字面量方式创建对象:

var book = {
title: '我是这个对象的描述哈',
} // 这种创建方式 字面量创建方式 该对象自动继承Object.prototype,并且其属性被
// 默认设置为 可配置、可枚举、没读写

Object.create() 方式创建对象:

var book = Object.create(Object.prototype, {
// 第一个参数要继承的对象
title: {
configurable: true, // 可配置
enumerable: true, // 可枚举
value: '我是当前这个book对象里面title属性的值撒',
writeable: true, // 可读写
},
})

🔔 注意:以上两种申明方式具有相同的效果。

  • 第一种申明使用对象字面形式来定义一个具有单一属性的 title 的对象,该对象自动继承 Objec.prototype,并且其属性默认为可配置、可枚举、可读写!
  • 第二种申明使用 Object.create() 显式做了同样的操作,两个 BOOK 行为完全一致!

下面的例子是用来体验 Object.crate() 来对象之间发生继承关系:
栗:

var person1 = {
name: "YangYangYang",
sayName:function(){
console.log(this.name)
}
};

var person2 = Object.create(person1,{ // 指定继承person1
name:{
configurable:true,
enumerable:true,
value:"Greg",
writeable:true
}
});

person1.sayName(); // YangYangYang
person2.syaName(); // Greg 自己有name 就使用自己的 没有再去继承关系找
console.log(person1.hasOwnProperty("sayName)); // true sayName是person1的自有属性
console.log(person1.isPrototypeOf(person2)); // true person2是继承person1
console.log(person2.hasOwnProperty("sayName")) // false 这个不是person2自有的属性

当访问一个对象的属性时,javascript 引擎会执行一个搜索过程,如果在对象实例上发现该属性(就是自有属性),该属性值就会被使用。如果对象实例上没有发现该属性,则通过 [[prototype]] 也就是 __proto__ 查找,如果也没有,则继续搜索该原型对象的__proto__,直接原型链顶端,Object.prototype.__proto__null

🔔 注意反例:不要使其为 null;

var nakeObj = Object.create(null)
console.log('toString' in nakeObj) // false
console.log('valueOf' in nakObj) // false
// 这是一个恶趣味的javascript诡计,创建了一个没有原型的对象,一个哈希容器;
// 这样导致Object.prototype的5个对象继承的原型方法toString,valueOf,hasProperty,
// isProtypeOf(检查一个对象是否另一个对象的原型对象),propertyIsEnumerable(检查
// 一个对象是否可以被枚举);

延申,用来实现继承父类原型的时候可以使用:

// 将父类的原型 赋值缓存到一个变量
var prototype = Object.create(SuperClass.prototype)
// 这个原型的构造器 指向子类的构造函数
prototype.constructor = SubClass
// 将这个继承来的原型 放入子类自己的原型对象中
SubClass.prototype = prototype

设置原型继承 ES6 这个方法更加强大 Object.setPrototypeOf(Son.prototype,Person.prototype)

禁止修改对象


对象和属性一样具有指导其他行为的内部特征。其中对象内部 [[Extensible]] (可扩展的)是一个布尔值,它指明该对象本身是否可以被修改。所有创建的默认对象都是可扩展修改的,意味着新的属性可以随时添加。
设置 [[Extensible]] 为 false,就能禁止新属性的添加,原生 js 提供了 3 种方法!

  1. 禁止扩展;
  2. 对象封印;
  3. 对象冻结;

判断对象是否为可扩展:
isExtensible(对象名) ,返回一个布尔值,默认创建的对象为可扩展 true!
栗:

var person1 = {
name:"YangYangYang"
}
console.log(Object.isExtensible(person1))); // true 创建的对象默认都是ture

禁止扩展:Object.preventExtensions()

这种方法是利用 Object.preventExtensions() 方法, (防止扩展的意思),创建一个不可扩展的对象,该方法接收一个参数,就是你希望使其不可扩展的对象。一旦在一个对象上使用该方法,就永远不能再给它添加新的属性了,可以利用 Object.isExtensible()方法来检查 [[Extensible]] 的值。

栗:

var person1 = {
name: 'YangYangYang',
}

console.log(Object.isExtensible(person1)) // true 判断这个对象是否为可扩展的对象

Object.preventExtensions(person1) // 强制要求使其当前对象为不可扩展

console.log(Object.isExtensible(person1)) // 所以 现在反悔为false 不可扩展

// 再尝试给person1对象添加一个方法

person1.sayName = function () {
console.log(this.name)
}

console.log('sayName' in person1) // 判断这个新添加的方法 在person1对象中存在,结果为false 因为不可扩展 以上设置没被添加到

每一次创建一个对象的时候,都会隐式的检查该对象的 [[Extensible]] 特征,默认是可扩展。以上代码显示的将其变为不可扩展,由于不可扩展,所有上面的 sayName 方法永远无法添加到 person1 上!

对象封印:Object.seal()

对象封印是创建不可扩展对象的第二种方法。
一个被封印的对象是不可扩展的并且其所有属性都不可配置,意味着不能给添加对象新属性,也不能删除属性或改变类型,如果一个对象被封印,只能读写它的属性。

使用 Object.seal() 方法来封印一个对象。当该方法被调用时,[[Extensible]] 特征被置为 false,其所有属性的[[Configurable]] 特征被置为 false

判断对象是否被封印。
Object.isSealed(对象名),返回一个布尔值,默认是 false

var person1 = {
name:"YangYangYang";
}
console.log(Object.isSealed(person1)); // 返回false

栗:

var person1 = {
name: 'YangYangYang',
}
console.log(Object.isExtensible(person1)) // true 可以扩展
console.log(Object.isSealed(person1)) // false 没有被封印
// 下面对person1 对象进行封印
Object.seal(person1)
console.log(Object.isExtensible(person1)) // false 不能扩展
console.log(Object.isSealed(person1)) // true 被封印
// 尝试给对象添加方法
person1.sayName = function () {
console.log(this.name)
}
console.log('sayName' in person1) // 返回结果为true 证明添加对象方法失败
// 尝试修改自由属性的值
person1.name = 'Greg'
console.log(person1.name) // 修改成功 可以读写
// 获取属性特征看看
var descriptor = Object.getOwnPropertyDescriptor(person1, 'name')
console.log(descriptor.configurable) // 返回false 代表不可配置!不可以删除!不可以修改特征!

这段代码封印了 person1,因此不能再 person1 上面添加或删除属性,所有的封印对象都是不可扩展的对象,此时对 person1 使用 Ojbect.Extensible()方法将返回 false,并且试图添加 sayName() 会失败,而且,虽然 person1.name 被成功改变为新值,是因为它 writabletrue,可以读写,但是删除它确是失败,因为它的数据属性特征 configurablefalse,不可配置!

🔔 注意:

  1. 禁止扩展:只是阻止了扩展功能,无法添加新的属性和方法。
  2. 封印对象:在阻止扩展功能的前提上,增加了不可配置,不能删除和重新配置
    相同点:它们都是可以针对对象自有属性进行 writable 读写的!

对象冻结 Object.fieeze()


创建不可扩展对象的最后一种方法是冻结它。
如果一个对象被冻结,则不能再其上添加或删除属性,不能改变属性类型,也不能写入任何数据属性。
简而言之,被冻结对象是一个数据属性都为只读的被封印对象,所以 Object.isExtensible() 返回为 false (不可扩展),而 Object.isSealed()返回 true (被封印)。
被冻结对象无法解冻!


判断对象是否被冻结:Object.isFrozen(对象名)

var person1 = {
name:"YangYangYang";
};
console.log(Object.isExtensible(person1)); // true 可扩展
console.log(Object.isSealed(person1)); // false 没被封印
console.log(Object.isPFrozen(person1)); // false 没被冻结
// 强制冻结此对象
Object.freeze(person1);
console.log(Object.isExtensible(person1)); // false 不可扩展
console.log(Object.isSealed(person1)); // true 被封印
console.log(Object.isPFrozen(person1)); // true 被冻结

栗:

var person1 = {
name: 'YangYangYang',
}

console.log(Object.isExtensible(person1)) // true 可扩展
console.log(Object.isSealed(person1)) // false 没被封印
console.log(Object.isFrozen(person1)) // false 没被冻结

// 强制冻结
Object.freeze(person1)
console.log(Object.isExtensible(person1)) // false 不可扩展
console.log(Object.isSealed(person1)) // true 被封印
console.log(Object.isFrozen(person1)) // true 被冻结

// 尝试给对象添加方法
person1.sayName = function () {
console.log(this.name)
}
console.log('sayName' in person1) // 判断sayName在person1对象当中吗 结果为false 证明没添加到对象中

// 尝试给自由属性读写 改变属性值
person1.name = '杨大哥'
console.log(person1.name) // 结果依然是冻结之前定义的已有name 证明读写失败 writable为false 不可读写!

// 获取属性name的类型特征
var descriptor = Object.getOwnPropertyDescriptor(person1, 'name')
console.log(descriptor.configurable) // false 不可配置
console.log(descriptor.writable) // false 不可读写

🔔 注意:被冻结的对象仅仅只是对象在某个时间点上的快照,其用途有限且极少使被使用;使用的时候注意,不可扩展,对象封印,对象冻结的特性来根据需求选择!
冻结对象较于封印对象 多了属性 [[Writable]] 为 false 不可写! 只能读~

冻结/封印/扩展的时候注意是冻结实例对象还是冻结构造函数的原型,但是常规的原型引用的目的就是为了共享,所以原型的冻结是没必要的,一般原生提供的 Object 顶级对象的原型 就是可以提供修改的 只是不能删除(不可配置 configurable)

模块模式和暴露模块模式


模块模式:(单个对象,字面量对象)
模块模式是一种用于创建拥有私有数据的单件对象的模式。基本做法是使用立即执行函数表达(IIFE)来返回一个对象。IIFE 是一种被定义后立即调用并产生结果的函数表达,该函数表达可以包括任意数量的本地 变量,它们在函数外不可见。因为返回的对象被定义在函数内部,对象的方法可以访问这些数据。(IIFE 定义的所有的对象都可以访问同样的本地变量)以这种方式访问私有数据的方法被称为特权方法。


栗:基本格式

var yourObj = (function () {
// 私有数据 变量
return {
// 公有的方法和属性
}
})()

该模式创建了一个匿名函数并立即执行。这种立即执行函数意味着仅存在于被调用的瞬间,一旦执行完毕立即销毁。
模块模式允许使用普通变量作为非公有对象属性。
通过创建闭包函数作为对象方法来操作它们。
闭包函数就是一个可以访问其他作用域外部数据的普通函数。

举例来说:
当你在一个函数中访问一个全局对象,比如网页浏览器的 window 对象,该函数就是在访问其欧用于的变量。

区别是:
在模块模式里,变量是定义在 IIFE(立即执行表达式函数)中,而访问变量的函数也定义在 IIFE 中。

栗:

var person = (function () {
var age = 25
return {
name: 'Yang',
getAge: function () {
return age
},
growOLder: function () {
age++
},
}
})()
console.log(person.name) // Yang
console.log(person.getAge()) // 25
person.age = 100 //
console.log(person.getAge()) // 所以结果还是 25
person.growOlder() // 执行一次++函数
console.log(person.getAge()) // 26

这段代码使用模块创建了 person 对象。
变量 age 就是该对象的一个私有属性。
它无法被外界直接访问,但可以通过对象方法来操作。
该对象上有有两个特权方法:getAge()读取变量 age 的值,growOlder()age 自增.
这两个方法都可以直接访问变量 age,因为它们都定义了同一个 IIFE 内。

暴露模块模式

它将所有的变量和方法都组织在 IIFE 的顶部,然后将它们设置到需要被返回的对象上。

栗:

var person = (function () {
var age = 25
function getAge() {
return age
}
function growOlder() {
age++
}
// 以上都是本地的 私有的
// 下面是决定将哪些私有的 本地的 方法或属性 暴露出去
return {
name: 'Yang',
getAge: getAge,
growOlder: growOlder,
}
})()

在暴露模块模式中,age,getAge()growOlder() 都被定义成 IIFE 的本地对象。
然后 getAge()growOlder() 函数被设置到返回的对象中,有效地对外界进行了 “暴露”。
也就是说,只能对暴露出来的这些属性和方法进行操作!

构造函数的私有成员

模块模式在定义单 对象的私有属性上十分有效,但对于同样需要私有属性的自定义类型,需要用构造函数类型的模块模式来创建。

栗:

function Person(name) {
// 定义一个只能在Person构造函数内部访问的变量
var age = 25
this.name = name
this.getAge = function () {
return age
}
this.growOlder = function () {
age++
}
}
// 实例化对象
var person = new Person('Yang')
console.log(person.name) // Yang
console.log(person.getAge()) // 25
person.age = 100
console.log(person.getAge()) // 25
person.growOlder()
console.log(person.getAge()) // 26

在这段代码中,Person 构造函数有一个本地变量 age
该变量被用于 getAge()growOlder()方法,当你创建 Person 的一个实例时,该实例接收其自身的 age 变量、getAge() 方法和 growOlder()方法,这种做法在很多方面类似模块模式,构造函数创建一个本地作用域并返回 this 对象。
将方法直接放在对象实例上不如放在原型对象有效,但是如果需要实例私有的数据,这是唯一的手段!

所有实例可共享的私有数据:
就好像它被定义在原型对象里那样,需要结合模块模式和构造函数。

var Person = (function () {
// 每个人的年龄都一样
var age = 25
function InnerPerson(name) {
this.name = name
}
InnerPerson.prototype.getAge = function () {
return age
}
InnerPerson.prototype.growOlder = function () {
age++
}
return InnerPerson
})()

// 实例化对象
var person1 = new Person('Yang')
var person2 = new Person('Wang')
console.log(person1.name) // Yang
console.log(person1.getAage()) // 25
console.log(person2.name) // Wang
console.log(person2.getAge())
25
// 调用++函数
person1.growOlder()
console.log(person1.getAge()) // 26
console.log(person2.getAge()) // 26

在这段代码中,InnerPerson 构造函数被定义在一个 IIFE 中。
变量 age 被定义在构造函数外并被两个原型对象的方法使用。
IIFE 返回 InnerPerson 构造函数作为全局作用域里的 Person 构造函数。
最终,Person 的全部实例得以共享 age 变量,所以在一个实例上的改变自动影响另外一个。

我很可爱,请给我钱

其他文章