简介编程
在基于原型的对象系统中,至少包含如下规则:数组
(1) 一切皆对象浏览器
(2) 建立一个对象的方式是找到一个原型对象,并克隆它,而不是经过实例化类函数
(3) 对象会记住它的原型this
(4) 对象会继承它的原型链中的全部内容spa
建立对象:prototype
在 JS 中能够经过对象字面量或者构造函数来建立对象code
//示例一:经过对象字面量建立对象 var boy = { name: 'Bob', sex: 'male' }
//示例二:经过构造函数建立对象 function Person(name, sex) { this.name = name; this.sex = sex; } var boy = new Person('Bob', 'male');
对象的属性:对象
在 JS 的对象中有两种属性:数据属性和访问器属性。blog
数据属性即常见的键值对映射,一个属性名对应一个属性值,另外有4个用于描述其行为的描述符:
configurable(是否可配置):表示可否经过 delete 删除属性从而从新定义属性,可否修改属性的描述符,或者可否把属性修改成访问器属性,默认为 false
enumerable(是否可枚举):表示可否经过 for-in 循环返回属性,默认为 false
writable(是否可写入):表示可否修改属性值,默认为 false
value:用于存储对应这个属性的属性值,默认为 undefind
若是像前面 "示例1"、"示例2" 中那样,直接定义对象的属性,那么它们的 "configurable"、"enumerable"、"writable" 都被默认设置为 true,"value" 则被设置为指定的值,若是要修改属性的描述符,必须使用 Object.defineProperty() 方法,这个方法有三个参数:"属性所在的对象"、"属性名"、"描述符对象",例如:
var obj = { test: '123' }; Object.defineProperty(obj,'test',{ writable: false }); obj.test = '456'; console.log(obj.test); //输出123
此外,还能够经过 Object.create() 方法,在建立对象的时候修改属性的描述符,该方法有两个参数:"原型对象"、"描述符对象",例如:
//示例三:经过 Object.create() 方法建立对象 var obj = { test: '123' }; var newObj = Object.create(obj,{ test: { writable: false, value: '12' } }); newObj.test = '456'; console.log(newObj.test); //输出12
注意:若是这里将描述符对象中的 "value" 属性省略,那么 "newObj.test" 的值将为undefind,并不会继承原型 "Obj.test" 的值(只要指定了描述符对象),能够理解新的描述符对象将原型中的覆盖了,而 "value" 的默认值为 undefind。
访问器属性不能直接定义,必须经过 Object.defineProperty() 方法来定义。它也有4个用于描述其行为的描述符:
configurable(是否可配置):表示可否经过 delete 删除属性从而从新定义属性,可否修改属性的描述符,或者可否把属性修改成数据属性,默认为false
enumerable(是否可枚举):表示可否经过 for-in 循环返回属性,默认为false
get:在读属性前调用的函数,默认为undefind
set:在写属性前调用的函数,默认为undefind
var obj = { test : '123' }; Object.defineProperty(obj,'tt',{ get: function() { console.log('读属性'); return this.test; }, set: function(newValue) { console.log('写属性'); this.test = newValue; } }); console.log(obj.tt); obj.tt = '456'; console.log(obj.tt); //读属性 //123 //写属性 //读属性 //456
注意:若是只单独指定了 "get" 或只单独指定了 "set",表示只读或只写;不要在 "get" 方法里面又读本属性,也不要在 "set" 方法里面又写本属性,否则会致使无限递归循环;一个属性只能定义为数据属性或访问属性中的一种,若是同时定义则会报错,可是若是指定了 "configurable" 为 true,能够先经过 "delete" 操做符删除属性再从新定义。
delete 操做符:
用于删除一个对象上的属性。在严格模式下,若是对象是一个不可配置的属性 (configurable 为 false),删除会抛出异常,非严格模式下返回 false,其余状况返回 true。
注意:一、delete 不能删除从原型继承来的属性;
二、delete 也不能删除内置对象的内置属性,例如 Math.PI;
三、delete 删除数组元素时,数组的长度并不会变小,删除的值会被 undefind 代替;
一切皆对象
事实上,在 JS 中并不是一切皆对象,这只是一种笼统的说法,因为 JS 引入了两种数据类型:基本类型 ( Undefind、 Null、 Boolean、 Number 和 String ) 和对象类型 ( Object ),对象类型是对象天然没必要多说,问题在于基本类型是对象吗?咱们先上字符串类型来讲明一下:
var str = 'Make life better'; console.log(str.length); //输出 16
按理说 "str" 变量只是一个字符串,可是它却使用了对象才有的 "length" 属性,输出了字符串的长度,所以这里咱们有理由把字符串类型当作对象,称为 "包装对象"。这个对象是临时的,也就是说只有在读取它的属性的时候 JS 才会把这个字符串经过 new String() 方式建立成一个字符串对象,一旦引用结束这个对象就被销毁了,换句话说 "包装对象" 的属性是 "只能读,不能写" 的。同理 "Boolean" 和 "Number" 在读取属性的时候也能够经过本身的构造函数来建立本身的一个包装对象,并像对象同样引用各自的属性。
其次,"null" 表示 "空值",对 "null" 执行 "typeof" 操做,输出结果为 "Object",因此咱们也能够把 "null" 当作一个对象,称为 "空对象"
最后,"undefind" 表示 "未定义",当咱们对变量只声明没有初始化时,输出 "undefind",或者引用一个不存在的属性时,输出也为 "undefind",对 "undefind" 执行 "typeof" 操做的输出结果为 "undefind",这么说来 "undefind" 其实并不属于对象范畴
建立一个对象的方式是找到一个原型对象,并克隆它,而不是经过实例化类
在 JS 中,"Object.prototype" 是全部对象的原型,咱们并不须要关心克隆的细节,由于这是引擎内部负责实现的。咱们所须要作的只是显式地调用 var obj1 = {}; 或者 var obj2 = new Object(),此时,引擎内部会从 "Object.prototype" 上面克隆一个对象出来,做为新对象的原型。
示例一:
var obj1 = {}; var obj2 = new Object(); console.log(Object.getPrototypeOf(obj1) === Object.prototype); //输出true console.log(obj2.__proto__ === Object.prototype); //输出true
每一个对象都具备 "__proto__"(先后两个下划线) 属性,它指向该对象的原型,可是它只是一个内部属性,而不是一个正式的对外 API,原则上是不能访问的,这是因为不少浏览器的支持,才把这个属性暴露出来了。在ES5中使用 "Object.getPrototypeOf()" 获取一个对象的原型,在ES6中可使用 "Object.setPrototypeOf()" 设置一个对象的原型。所以,在这里二者的做用都是同样的,都是获取对象的原型,而且它们的原型都是 "Object.prototype"。
只有函数才有 "prototype" 属性,例如 "Object.prototype",对于函数而言,"__proto__" 属性指向它本身的原型, "prototype" 属性则是经过这个函数构造出来的实例的原型,能够理解为这样一条原型链,"__proto__" 老是指向原型链的顶端,而函数刚好能够延长原型链,因而它将本身 "prototype" 属性指向的对象压入原型链的顶端,天然它构造出来的实例的 "__proto__" 属性就指向了它本身的 "prototype"。
示例二:
function Person(name, sex) { this.name = name; this.sex = sex; } var boy = new Person('Bob', 'male'); console.log(Object.getPrototypeOf(Person) === Function.prototype); //true console.log(Object.getPrototypeOf(boy) === Person.prototype); //true
Person 函数继承自 Function 对象,若是这么写就很直观了:
var Person = new Function('name', 'sex', 'this.name = name;this.sex = sex;');
所以 Person 函数的原型指向 Function.prototype,boy 对象是经过 Person 函数构造而来的,所以它的原型指向 Person.prototype。
对象会记住它的原型
上面已经提到,JS 给全部对象提供了一个 "__proto__" 属性,用于访问它的原型
对象会继承它的原型链中的全部内容
示例:
function A() {} A.prototype = { name: 'better' }; var a = new A(); console.log(a.name); //输出better
a 对象自己没有 "name" 属性,因而在它的原型,即构造函数的 "prototype" 中去找,若是找到,则中止上溯,输出结果。
面向对象编程
当要建立两个或多个对象时,若是直接使用字面量或者构造函数方式将会产生没必要要的重复代码,例如:
var obj1 = { name: 'test1', sayName: function() { console.log(this.name); } } var obj2 = { name: 'test2', sayName: function() { console.log(this.name); } }
"obj1" 和 "obj2" 共享同一个方法 "sayName()",可是它们却占用了两分内存,所以最好是经过构造函数的方式实现私有属性的继承,再经过原型的方式实现共有属性和共有方法的继承,例如:
function CreateObj(name) { this.name = name; } CreateObj.prototype.sayName = function() { console.log(this.name); } var obj1 = new CreateObj('test1'); var obj2 = new CreateObj('test2');