内容简介:在面向对象编程的语言中,都有类的概念,可以基于这个类创建无数个拥有相同属性和方法的对象。在js中,是没有类的概念的,所以会略有所不同。对象: {} 这就是一个对象,对,没错,就是这么简单。我们可以将对象想象成一个散列表,无非就是一些键值对,值可以是数据或函数。每一个对象都是基于引用类型创建的,可以是原生(基本、引用)类型,也可以是自定义类型。基本类型(按值访问): Number, String, Undefined, Unll, Boolean, Symbol.
在面向对象编程的语言中,都有类的概念,可以基于这个类创建无数个拥有相同属性和方法的对象。在js中,是没有类的概念的,所以会略有所不同。
对象: {} 这就是一个对象,对,没错,就是这么简单。我们可以将对象想象成一个散列表,无非就是一些键值对,值可以是数据或函数。每一个对象都是基于引用类型创建的,可以是原生(基本、引用)类型,也可以是自定义类型。
基本类型(按值访问): Number, String, Undefined, Unll, Boolean, Symbol.
引用类型(按引用访问): Object, Function, Array, Date, RegExp...
- 在最早的时候,我们都是这样创建对象的
最早
const obj = new Object() obj.name = 'len' obj.age = 23 obj.sayName = function() { console.log(this.name) } obj.sayName() // len 复制代码
A few years later...
字面量
const obj = { name: 'len', age: 23, sayName: function() { console.log(this.name) } } 复制代码
这样创建的两个对象是一样的,都有相同的属性和方法。这些属性在创建的时候,都会带有一些特征值。
属性分两种:1. 数据属性 2. 访问器属性
数据属性
configuration: 是否通过delete操作删除从而重新定义。 默认 true enumeration: 是否能通过for...in 循环返回属性。 默认 true writable: 能否修改属性的值。 默认 true value: 属性的值。 默认 undefined 复制代码
我们可以这样设置一个数据属性的属性特征值
let obj = {} Object.defineProperty(obj, 'name', { configuration: true, enumeration: false, writable: false, value: 'len' }) obj.name // len obj.name = 'lance' obj.name // len 因为writable为false,赋值会被忽略,在严格模式下会报错 复制代码
tips: 当一个属性的 configuration
的特征值为设置为false的时候,也就是不能配置的时候,就不能再变回可配置的了,
let obj = {} Object.defineProperty(obj, 'name', { configuration: false, value: 'len' }) obj.name // len delete obj.name obj.name // len 因为configuration为false,delete会被忽略,严格模式下会报错 // 这时候我们再这样,除了修改writable以外,其他的都会导致报错 Object.defineProperty(obj, 'name', { configuration: true }) 调用的时候,如果不指定这些特征值,默认为false 复制代码
访问器属性
configuration: 是否可配置 默认 true enumeration: 是否可枚举 默认 true get: 读取属性的时候调用 默认 undefined set: 设置属性的时候调用 默认 undefined 复制代码
访问器属性不能直接定义,必须使用 Object.defineProperty()
来定义。请看:
var obj = { _year: 2018, count: 1 } Object.defineProperty(obj, 'year', { get: function() { console.log('get') return this._year }, set: function(newValue) { console.log('set') if (newValue > 2018) { this._year = newValue this.count += newValue - 2018 } } }) console.log(obj.year) // get 2018 obj.year = 2019 // set console.log(obj.year) // get 2019 console.log(obj.count) // 2 复制代码
_year前面的下划线是一种常用的几号,表示只能通过对象方法访问属性。而访问器属性包含getter、setter, getter返回 _year值, setter设置 _year的值。不一定非要同时指定getter和setter,只指定getter表示属性是只读的。
我们可以用 Object.defineProperties()
一下定义多个属性
var obj = {} Object.defineProperties(obj, { _year: { value: 2018 }, count: { value: 1 }, year: { get: functin() { return this._year }, set: function(newValue) { if (newValue > 2018) { this._year = newValue this.count += newValue - 2018 } } } }) 复制代码
注意:数据描述符和存取描述符不能混用,也就是writable或者value和get,set不能混用等...
使用字面量创建对象的缺点: 重复代码太多,无法复用
工厂函数
function person(name, age) { var obj = new Object() obj.name = name obj.age = age obj.sayName = function() { return this.name } return obj } var p1 = person('len', 23) p1.name // len p1.age // 23 p1.sayName() // len var p2 = person('lance', 23) p2.name // lance p2.age // 23 p2.sayName() // lance 复制代码
优点: 解决了字面量重复代码,无法复用的问题
缺点: 不能确定对象的类型。
什么叫不能确定对象的类型。来,我们看下这个
console.log(p1 instanceof person) // false console.log(p1 instanceof Object) // true 复制代码
所有的实例对象都是Object,自然而言就没法确定了
构造函数
// 构造函数一般首字母大写 function Person(name, age) { this.name = name this.age = age this.sayName = function() { return this.name } } let p1 = new Person('len', 23) console.log(p1.name) // len console.log(p1.age) // 23 console.log(p1.sayName()) // len let p2 = new Person('lance', 23) console.log(p2.name) // lance console.log(p2.age) // 23 console.log(p2.sayName()) // lance 复制代码
和工厂函数的不同
new
正是使用了new关键字 ,所以才没有做上述的操作,可想而知,都是new在底层帮我们实现了上述功能
new内部所做的事情
- 生成一个新的对象
- 将构造函数的作用域赋给新对象(this 就指向了新对象)
- 执行构造函数中的代码 (给新对象添加了属性和方法)
- 返回新对象
手动实现new
function createObj() { // 1. 生成新对象 let obj = new Object() let cons = [].unsift.call(arguments) // 3. 执行构造函数中的代码 obj.__proto__ = cons.prototype // 2. 将构造函数的作用域赋给新对象 const res = cons.apply(obj, arguments) return typeof res === 'object' ? res : obj } 复制代码
构造函数的调用方式
let p = new Person() let p = Person() let o = new Object() / Person.call(o)
优点: 解决了不知道对象类型的问题
console.log(person1.constructor == Person) //true console.log(person2.constructor == Person) //true console.log(p1 instanceof Person) // true console.log(p1 instanceof Object) // true console.log(p2 instanceof Person) // true console.log(p2 instanceof Object) // true 复制代码
这样就知道了p1,p2都是Person的实例对象
function Human() {} let h = new Human() console.log(h instanceof Human) // true 这样h就是Human的实例了,这样就做到了区分 复制代码
缺点: 每个方法都要在每个实力上重新创建一遍, 请看
// 从逻辑角度讲, 是可以这么定义的 function Person(name, age) { this.name = name this.age = age this.sayName = new Function() { console.log(this.name) } } 复制代码
每个Person实例都包含一个不同的Fuction实例以显示name属性。 以这种方法创建函数,会导致不同的作用域链和标识符解析,但创建Function新实例的机制还是一样的。因此,不同实力上的同名函数是不相等的, 一下代码是可以证明的。
console.log(p1.sayName === p2.sayName) // true
因此,为了解决这个问题,我们可以使用原型模式
原型模式
function Person() { } Person.prototype.name = 'len' Person.prototype.age = 23 Person.prototype.sayName = function() { return this.name } let p1 = new Person() console.log(p1.name) // len console.log(p1.age) // 23 console.log(p1.sayName()) // len let p2 = new Person() console.log(p2.name) // len console.log(p2.age) // 23 console.log(p2.sayName()) // len console.log(p1.sayName === p2.sayName) // true 复制代码
上面之所以能打印,是因为原型链的关系,实例上没找到,在原型上找到了
这里解释下原型对象,这样有助于我们理解原型继承
原型对象:无论什么时候,我们只要创建了一个新函数,就会根据一组特定的规则为该函数创一个 prototype
属性,这个属性指向函数的原型对象。在默认情况下,所有原型对象会获得一个 constructor
属性,这个属性包含一个指向prototype属性所在函数的指针 Person.prototype.constructor === Person // true
, Person
是构造函数, Person.prototype
是原型对象。创建构造函数之后,默认只会取得constructor属性,其他属性都是从 Object
继承而来的。那为什么当我们获取 p1.name
的时候,会从原型上找呢,是因为,在 每个实例对象当做,都有一个__proto__属性指向构造函数的原型对象,也就是Person.prototype
,所以才能在原型上找到。又因为Person是从Object继承而来,所以,Person.prototype. proto
=== Object.prototype,Object和Object.prototype之前的关系就像p1和Person之间的关系,所以,这样就形成了原型链。
虽然在所有的实现中,我们都无法访问到__proto__,但可以通过 isPrototypeOf()
方法来确定对象之间是否存在这种关系
console.log(Person.prototype.isProptotypeOf(p1)) // true console.log(Person.prototype.isProptotypeOf(p2)) // true 复制代码
在es6中增加了一个新方法,叫 Object.getPrototypeOf()
,该方法返回__proto__的值
console.log(Object.getPrototypeOf(p1) === Person.prototype) // true console.log(Object.getPrototypeOf(p1).name) // len 复制代码
虽然可以通过对象实例访问保存在原型中的值,但却不能通过对象实例重写原型中的值。
function Person() {} Person.prototype.name = 'len' let p1 = new Person() let p2 = new Person() p1.name = 'lance' console.log(p1.name) // lance console.log(p2.name) // len 还是原型中的值 复制代码
我们可以通过 hasOwnProperty()
来判断属性是实例的还是原型的
function Person() {} Person.prototype.name = 'len' let p1 = new Person() let p2 = new Person() console.log(p1.getOwnProperty('name')) // false p1.name = 'lance' console.log(p1.getOwnProperty('name')) // true delete p1.name // delete 可以完全删除实例属性 console.log(p1.getOwnProperty('name')) // false 复制代码
我们还可以用in来判断
function Person() {} Person.prototype.name = 'len' let p1 = new Person() console.log('name' in p1) // true p1.name = 'lance' console.log('name' in p1) // true 复制代码
所以in操作符不管是实例属性还是原型上的属性,只要找到了,就返回true
我们还可以用 hasOwnProperty()
,只在属性存在于实例中的时候才返回true, 所以我们可以结合in使用,来判断,属性到底是原型的属性还是实例的属性
for...in
返回的是不管是实例上的还是原型上的,只要是 enumeration
不为 false
的所有属性集合。
在es6中,可以使用 Object.keys()
来获取所有可枚举的实例属性(实例和原型)
还可以使用 Object.getOwnPropertyNames
,这个获取的是所有的实例属性,包括不可枚举的。
更简单的原型语法
function Person() {} Person.prototype = { name: 'len', age: 23, sayName: function() { return this.name } } 复制代码
上面代码有个例外,因为我们重写了Person.prototype,所以constructor不再指向Person,尽管 instanceof操作符还能返回正确的结果,但通过 constructor 已经无法确定对象的类型了,如下所示。
let p1 = new Person(); console.log(p1 instanceof Object) //true console.log(p1 instanceof Person) //true console.log(p1.constructor == Person) //false console.log(p1.constructor == Object) //true 复制代码
如果constructor很重要,我们可以这样
Person.prototype = { constructor: Person, name: 'len', age: 23, sayName: function() { return this.name } } 复制代码
这样constructor的enumeration会被设置为true,我们可以通过Object.defineProperty()来设置为false
Object.defineProperty(Person.prototype, "constructor", { enumerable: false, value: Person }) 复制代码
原型的动态性
var p1 = new Person() // 这里没有重写 Person.prototype.sayHi = function(){ console.log("hi") } p1.sayHi() //"hi"(没有问题!) 再看这个 function Person(){ } var p1 = new Person() // 这里重写了 Person.prototype = { constructor: Person, name : "Nicholas", age : 29, job : "Software Engineer", sayName : function () { return this.name } } p1.sayName() //error 复制代码
为什么这里报错了呢?是因为p1指向的原型中不包含以该名字命名的属性。重写原型对象切断了现有原型与任何之前已存在的对象实例之间的联系,他们应用的任然是最初的原型。
原型对象的问题,所有的实例和方法都是共享的,当然,你可以重新覆盖之前的属性。但是,对引用类型的话,就没这那么好受了,请看
function Person() { } Person.prototype = { name: 'len', age: 23, loveColors: ['white', 'black', 'red'] } let p1 = new Person() let p2 = new Person() p1.loveColors.push('yellow') console.log(p1.loveColors) // ['white', 'black', 'red', 'yellow'] console.log(p2.loveColors) // ['white', 'black', 'red', 'yellow'] console.log(p1.loveColors === p1.loveColors) // true 复制代码
我们怎样才能做到引用类型的私有化呢?这就是我们下面要说的。
组合使用构造函数模式和原型模式, 废话不多说,直接上代码
function Person(name, age) { this.name = name this.age = age this.loveColors = ['black', 'white'] } Person.prototype = { constructor: Person, sayName: function() { return this.name } } let p1 = new Person() let p2 = new Person() p1.loveColors.push('red') console.log(p1.loveColors) // ['black', 'white', 'red'] console.log(p2.loveColors) // ['black', 'white'] console.log(p1.loveColors === p2.loveColors) // false console.log(p1.sayName === p2.sayName) // true 复制代码
这种方式是目前使用最广泛、认同度最高的一种创建自定义类型的方法
下面还有两种模式供参考
动态原型模式
function Person(name, age) { this.name = name this.age = age if (typeof this.sayName !== 'function') { Person.prototype.sayName = { return this.name } } } 复制代码
这里只有在sayName不存在的情况下,才会将它添加到原型中。这段代码只会在初次调用构造函数时才会执行。
唯一需要注意的是:不能使用字面量重写原型。在已经创建了实例的情况下重写原型,会切断现有实例与新原型之间的关系。
寄生构造函数模式
function Person(name, age) { let obj = new Object() obj.name = name obj.age = age obj.sayName = function() { return this.name } return obj } let p = new Person('len', 23) console.log(p.sayName()) // len 这种模式其实和工厂函数一模一样,不同的在于生成实例的时候,这里使用了new操作符。这个模式可以在特殊的情况下用来为对象创建构造函数。 function SpecialArray(){ //创建数组 var values = new Array(); //添加值 values.push.apply(values, arguments); //添加方法 values.toPipedString = function(){ return this.join("|"); }; //返回数组 return values; } var colors = new SpecialArray("red", "blue", "green"); alert(colors.toPipedString()); //"red|blue|green" 复制代码
稳妥构造函数模式
function Person(name, age) { var obj = new Object() obj.name = name obj.age = age obj.sayName = function() { return this.name } return obj } let p = Person('len', 23) console.log(p.sayName) // len 这种模式有两个限制,不能在构造函数内使用this,不能使用new生成实例。比较适用于一些安全的环境中。 复制代码
以上所述就是小编给大家介绍的《JavaScript 的面向对象(OO)》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:- 面向Python,面向对象(基础)
- 面向Python,面向对象(基础3)
- <<深入PHP面向对象、模式与实践>>读书笔记:面向对象设计和过程式编程
- 《JavaScript面向对象精要》之六:对象模式
- 《JavaScript面向对象精要》之三:理解对象
- 面向对象的程序设计之理解对象
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
企业应用架构模式
Martin Fowler、王怀民、周斌 / 王怀民、周斌 / 机械工业出版社 / 2004-7 / 49.00元
本书作者是当今面向对象软件开发的权威,他在一组专家级合作者的帮助下,将40多种经常出现的解决方案转化成模式,最终写成这本能够应用于任何一种企业应用平台的、关于解决方案的、不可或缺的手册。本书获得了2003年度美国软件开发杂志图书类的生产效率奖和读者选择奖。本书分为两大部分。第一部分是关于如何开发企业应用的简单介绍。第二部分是本书的主体,是关于模式的详细参考手册,每个模式都给出使用方法和实现信息,并一起来看看 《企业应用架构模式》 这本书的介绍吧!