JavaScript是一门面向对象的语言(Object Oriented,简称OO)
什么是对象?对象是一组无序属性的集合
在JavaScript中,一切都是对象?不对,除了6类基本类型外都是对象
Js有两大数据类型,基本类型和引用类型
基本类型是保存在栈内存中的简单数据,有String Number Boolean null undefined Symbol,按值访问
引用类型是保存在堆内存中的对象,有Object Array Function Date等,按引用访问
基本类型不是对象,那为什么他们有属性?
调用基本类型的属性时,会临时创建一个对象,调用对象属性,然后销毁对象.下次调用属性时,再创建一个新的对象
1 2 3 4 5 6 var str = 'lalala' ;str.valueOf (); var temp = new String (str);temp.valueOf (); temp = null ;
因此不能给基本类型添加属性.完成操作后,新创建的对象被销毁了
注 typeof对null返回"object"是个bug
参考
创建对象
工厂模式
缺点:
全部由Object构造函数生成,无法识别实例的对象类型
无法判断实例方法是否相等
1 2 3 4 5 6 7 8 9 10 11 12 13 14 function Person (name, age ){ return { name : name, age : parseInt (age), getName : function ( ) { return this .name ; } } } var p = Person ('anna' , 14.4 );console .log ( p.constructor ); var p1 = Person ('bob' , 20 );console .log ( p.getName === p1.getName );
构造函数模式
缺点:
1 2 3 4 5 6 7 8 function Person (name, age ){ this .name = name; this .age = parseInt (age); this .getName = function ( ){ return this .name ; } } var p = new Person ('anna' , 14.4 );
构造函数模式解决了识别对象类型的问题,可使用instanceof判断p是否是Person的实例
1 console .log ( p instanceof Person );
每个实例都有一个constructor属性,指回构造函数(实例是调用构造函数返回的对象)
1 2 console .log ( p.constructor ) console .log ( .constructor === Person );
但构造函数仍然无法判断实例的同名方法是否相等
1 2 3 var p1 = new Person ('bob' , 16 );console .log (p.getName === p1.getName )
原型模式
1 2 3 4 5 6 function Person ( ){ }Person .prototype .name = 'anna' ;Person .prototype .getName = function ( ){ return this .name ; } var p = new Person ();
什么是prototype?
在创建函数时,会自动生成一个对象,可通过函数的prototype属性访问这个对象,因此称其为"原型对象".这个对象包含一个constructor属性,指回函数.
构造函数生成的实例可以访问绑定在prototype上的属性和方法
JS的创造者为什么要设计prototype?
单纯的使用构造函数生成的对象无法共享属性/方法,为了解决这个问题,引入了原型链(prototype)的概念.
1 2 Person .prototype .age = 14 ;console .log ( p.age );
实例的constructor属性也继承自prototype
1 2 3 4 console .log ( Person .prototype .constructor === Person ); console .log ( p.hasOwnProperty ('constructor' )) console .log ( Person .prototype .hasOwnProperty ('constructor' ) ) console .log ( p.constructor === Person .prototype .constructor )
每个实例都有一个内部属性([[prototype]]),指向构造函数的原型对象,非ie浏览器可用__proto__访问这个原型对象
1 2 c (p.__proto__ === Person .prototype ); console .log ( p.constructor .prototype === p.__proto__ );
除了__proto__,还可以用Object.getPrototypeOf()访问构造函数的原型对象
1 console .log ( Object .getPrototypeOf (p) === Person .prototype );
可使用prototype.isPrototypeOf或instanceof判断一个对象是否在另一个对象的原型链中
1 2 console .log ( Person .prototype .isPrototypeOf (p) ); console .log ( p instanceof Person );
可使用in关键字判断一个属性是否在实例上/实例的原型链中
1 console .log ( 'name' in p );
注意 不能通过实例直接修改/删除prototype中的属性值
向实例对象添加属性时,该属性会被添加在实例对象上,而非修改原型对象中的同名属性
1 2 3 4 5 6 7 var p1 = new Person ();p1.name = 'bob' ; console .log ( p1.name ); console .log ( p.name ); delete p1.name ;console .log ( p1.name );
但如果绑在prototype上的属性是引用类型(对象),可以用push之类的操作直接修改的
1 2 3 Person .prototype .like = ['milk' , 'fish' ]p1.like .push ('banana' ); console .log ( p1.like );
prototype本质上来说是个对象,因此可直接重写
不过这会把contructror指向Object,可通过将constructor指向Person来修复这个问题
1 2 3 4 5 6 7 8 9 10 11 12 13 14 Person .prototype = { name : 'lan' , age : 20 , constructor : Person , } var p2 = new Person ();console .log ( p2.name ); console .log ( Object .getPrototypeOf (p2) ); console .log ( p.name ); console .log ( Object .getPrototypeOf (p1) );
为了避免绑定在prototype上的引用类型被修改,一般混合使用构造函数模式和原型链模式.
1 2 3 4 5 6 7 8 9 10 11 12 13 function Person (name, age, like ){ this .name = name; this .age = age; this .like = like; } Person .prototype .getlike = function ( ){ return this .like .join (',' ); } var p = new Person ('anna' , 14 , ['banana' ]);var p1 = new Person ('anna' , 14 , ['apple' ]);p.like .push ('pineapple' ); console .log ( p.getlike () );console .log ( p1.getlike () );
继承
让一个对象继承另一个对象的所有属性
Object.create()
Object.create接收两个参数,新对象的原型和需新添加的属性(可选)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 var p = { name :'anna' , like : ['apple' , 'cake' ], }; var p1 = Object .create (p, { name : { value : 'bob' , enumerable : true , }, }); console .log ( p.name ); console .log ( p1.name ); p1.like .push ('banana' ); console .log ( p.like );
Object.create本质上类似原型式继承
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 var p = { name :'anna' , like : ['apple' , 'cake' ], }; function createObject (parent ) { var f = function ( ){}; f.prototype = parent; return new f (); } var p1 = createObject (p);p1.name console .log ( p1.__proto__ === p );
原型式继承的缺点: 无法向原型传递参数,对象中的引用类型会被所有实例共享
寄生式继承
寄生式继承是原型式继承的扩展,在继承原对象属性的同时,添加新的属性
缺点: 无法向原型传递参数, 无法复用属性和方法,对象中的引用类型会被所有实例共享
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 var p = { name :'anna' , like : ['apple' , 'cake' ], }; function createObject (parent ){ var f = function ( ){}; f.prototype = parent; var clone = new f (); clone.getName = function ( ){ return this .name ; } return clone; } var p1 = createObject (p);var p2 = createObject (p);p1.name = 'bob' ; console .log ( p1.getName () ); p1.like .push ('banana' ); console .log (p1.like ); console .log (p2.like );
组合继承
使用prototype和构造函数实现继承,解决了属性复用的问题,避免引用类型被实例共享
缺点: 需生成两份原型属性拷贝
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 function SuperType (name ) { this .name = name; this .colors = ['red' , 'blue' ]; this .getColor = function ( ){ return this .colors ; } } SuperType .prototype .sayName = function ( ) { console .log (this .name ); } function SubType (name, age ) { SuperType .call (this , name); this .age = age; } SubType .prototype = new SuperType ();SubType .prototype .sayAge = function ( ){ console .log (this .age ); } Object .defineProperty (SubType .prototype , 'constructor' , { value : SubType , writable : true , configurable : true , enumerable : false , }) var child = new SubType ('bob' , 29 );var child1 = new SubType ('tom' , 30 );child.sayAge (); child.sayName (); child1.sayAge (); child1.sayName (); child.colors .push ('orange' ); console .log ( child.colors ); console .log ( child1.colors ); console .log ( child.getColor === child1.getColor ) console .log ( child.sayName === child1.sayName ) console .log ( child.constructor === child1.constructor )
寄生组合式继承
同时使用寄生继承和组合继承,避免多次调用构造函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 function createObject (SuperTypePrototype ) { var F = function ( ){}; F.prototype = SuperTypePrototype ; return new F (); } function inheritPrototype (subType, superType ) { var prototype = createObject (superType.prototype ); prototype.constructor = subType; subType.prototype = prototype; } function SuperType (name ) { this .name = name; this .colors = ['red' , 'blue' ]; } SuperType .prototype .sayName = function ( ) { alert (this .name ); } function SubType (name, age ) { SuperType .call (this , name); this .age = age; } inheritPrototype (SubType , SuperType );var child = new SubType ('bob' , 29 );
属性
什么是属性?属性就是个变量,指向内存中的某个值.
定义属性
Js有两种属性,数据属性和访问器属性
数据属性: 具有值的属性,有以下键值: value writable enumerable configurable
访问器属性: 由getter/setter函数定义的属性,有以下键值: get set enumerable configurable
Object.defineProperty()
定义对象中新的属性,或修改已存在的属性
Object.defineProperty(obj, prop, descriptor)
obj 对象
prop属性名
descriptor 属性描述
value 属性值
writable 能否修改属性值
enumerable 能否通过for in和Object.keys获取属性
configurable 能否更改/删除属性
get getter方法,返回属性值,默认值为undefined
set 属性的setter方法,用于设置属性值,默认值为undefined
注:
writable enumerable configurable 的默认值皆为false
get不接受参数
当configurable为false时,仍可将writable: true改为false(反过来不行)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 var obj = {}; // 定义一个可修改值(writable: true),不可设置的属性(configurable: false) Object.defineProperty(obj, 'say', { value: 'hello', writable: true, configurable: false, }) obj.say = 'hi' obj.say // "hi" // 更改writable为false Object.defineProperty(obj, 'say', { value: 'hello', writable: false, configurable: false, }) // {say: "hello"} obj.say = 'lll' obj.say // "hello" // 再改回去,报错 Object.defineProperty(obj, 'say', { value: 'hello', writable: true, configurable: false, }) // TypeError: Cannot redefine property: say
当writable为false时,无法修改属性值
1 2 3 4 5 6 7 8 9 10 11 var obj = {}; Object.defineProperty(obj, 'say', { value: 'hello', writable: false, // 禁止修改属性值 configurable: true, }) // {say: "hello"} obj.say = 'hi'; // 尝试修改属性say,但还是"hello" // 如果是'use strict'模式, 会报错TypeError: "say" is read-only obj.say // "hello";
例 定义访问器属性
1 2 3 4 5 6 7 8 9 10 11 12 13 14 obj = {_a: 1}; Object.defineProperty(obj, 'say', { get: function(){ return this._a; }, set: function(value){ this._a = value; }, enumerable: true, configurable: true, }) obj.say // 1 obj.say = 2 obj.say // 2
还可以通过get和set关键字创建访问器属性
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 // 计数器 var c = { _index_: 0, set index(i) { this._index_ = i; }, get index() { // 获取值时自动加一 return ++this._index_; } } c.index = 1; c.index; // 2 c.index; // 3 c.index; // 4
属性名表达式
Es6允许使用表达式定义属性名
1 2 3 // 将表达式放在中括号内 var obj = {["s" + "ay"]: "hello world"}; obj // {say: "hello world"}
如果属性名是对象,会自动将其转为字符串
1 2 var obj = {[new Object()]: "hello world"} obj["[object Object]"] // "hello world"
访问属性
有两种方式访问属性
1 2 3 4 5 var obj = {say : "hello world" }obj.say obj["say" ]
当属性名中包含空格等特殊字符时,只能用第二种方式获取属性值
1 2 3 var obj = {"s-ay" : "hello world" }obj.s -ay obj["s-ay" ]
遍历属性
for in
1 2 3 4 5 6 7 8 var obj = { index : 1 , name : 'anna' , } for (var i in obj) { console .log (i); console .log (obj[i]); }
for in会向上遍历原型链(prototype),可用hasOwnProperty方法判断该属性是否来自自身.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 Object .prototype .color = '#fff' for (var i in obj) { console .log (i); console .log (obj[i]); } for (var i in obj) { if (obj.hasOwnProperty (i)) { console .log (i); } }
for of
es6新增, 一般用来遍历数组和字符串
1 2 3 4 5 6 7 var arr = ['a' , 'b' , 'c' ];for (var i of arr) { console .log (i) } for (var i in arr) { console .log (i) }
for in遍历的是对象的可枚举属性,返回属性名.而for of会尝试获取对象的迭代器,使用迭代器遍历属性值.
因此for of不会遍历原型链上的属性
1 2 3 4 5 var arr = ['a', 'b', 'c']; Object.prototype.color = '#fff'; for (var i of arr) { console.log(i) // a b c }
数组和字符串自带迭代器.如果想对普通对象使用for of,需声明迭代器
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 var obj = { index1: 'a', index2: 'b', // Symbol,Es6新增对象,可用Symbol.iterator声明对象迭代器 [Symbol.iterator]() { var _this = this; var keys = Object.keys(this); var i = 0; // 返回迭代器,包含一个next方法 // next方法有两个属性,value和done // value为返回值(在这里返回的是obj的属性值) // 当done为true时,迭代结束 return { next() { var v = _this[keys[i]]; i++; return {value: v, done: (i > keys.length)} } } } } for(var i of obj) { console.log(i); } // a b
Object.keys()
返回对象的可枚举属性值(不含prototype)
1 2 3 4 5 6 var obj = { name : 'anna' , index : 0 , } Object .prototype .color = '#fff' ;Object .keys (obj)
属性必须可枚举的
1 2 3 4 5 6 Object.defineProperty(obj, 'say', { enumeable: false, value: 'hi' }) // 无法获取say,因为enumeable为false Object.keys(obj) // ["name", "index"]
Object.getOwnPropertyNames()
获取对象的所有属性(无虑是否可枚举,不包括prototype和Symbol)
1 2 3 4 5 6 var obj = { name : 'anna' , index : 0 , } Object .prototype .color = '#fff' ;Object .getOwnPropertyNames (obj);
Object对象方法
getOwnPropertyDescriptor
获取属性描述,用于判断属性是否可以修改/枚举
1 2 3 var obj = {say: 'hello'} Object.getOwnPropertyDescriptor(obj, 'say') // {value: "hello", writable: true, enumerable: true, configurable: true}
preventExtensions
禁止向对象添加新属性
1 2 3 4 var obj = {say: 'hello'} Object.preventExtensions(obj) obj.index = 1 obj.index // undefined
seal
禁止向对象添加新属性,同时设置所有属性的configurable为false(禁止修改/删除)
1 2 3 4 5 var obj = {say: 'hello'} Object.seal(obj) obj.index = 1 obj.index // undefined delete obj.say // false