6.1 理解对象
属性类型 | 属性特性 | 行为描述 |
数据属性 | Configurable | 表示是否能通过delete删除属性从而重新定义属性。 |
数据属性 | Enumerable | 表示能否通过for-in循环返回属性。对于直接在对象中定义的属性,默认为true |
数据属性 | Writable | 表示是否可以修改属性的值。 |
数据属性 | value | 表示这个属性的内部值。 |
访问器属性 | Configurable | 表示是否能通过delete删除属性从而重新定义属性。 |
访问器属性 | Enumerable | 表示能否通过for-in循环返回属性。对于直接在对象中定义的属性,默认为true |
访问器属性 | Get | 在读取属性时调用的函数 |
访问器属性 | Set | 在写入属性时调用的函数 |
常用方法
defineProperty() 修改属性的特性
defineProperties() 定义多个属性的特性
getOwnPropertyDescriptor() 获取指定属性的描述符
6.2 创建对象
工厂模式
function createPerson(name, age, job){ var o = new Object(); o.name = name; o.age = age; o.job = job; o.sayName = function(){ alert(this.name); }; return o; } var person1 = createPerson("Nicholas", 29, "Software Engineer"); var person2 = createPerson("Greg", 27, "Doctor"); person1.sayName(); //"Nicholas" person2.sayName(); //"Greg"
构造函数模式
function Person(name, age, job){ this.name = name; this.age = age; this.job = job; this.sayName = function(){ alert(this.name); }; } var person1 = new Person("Nicholas", 29, "Software Engineer"); var person2 = new Person("Greg", 27, "Doctor");
function Person(name, age, job){ this.name = name; this.age = age; this.job = job; this.sayName = function(){ alert(this.name); }; } var person = new Person("Nicholas", 29, "Software Engineer"); person.sayName(); //"Nicholas" Person("Greg", 27, "Doctor"); //adds to window window.sayName(); //"Greg" var o = new Object(); Person.call(o, "Kristen", 25, "Nurse"); o.sayName(); //"Kristen"
原型模式
原型prototype
1. prototype的概念
prototype是构造函数的一个属性, 该属性指向一个对象. 而这个对象将作为该构造函数所创建的所有实例的基引用(base reference), 可以把对象的基引用想像成一个自动创建的隐藏属性. 当访问对象的一个属性时, 首先查找对象本身, 找到则返回; 若不, 则查找基引用指向的对象的属性(如果还找不到实际上还会沿着原型链向上查找, 直至到根). 只要没有被覆盖的话, 对象原型的属性就能在所有的实例中找到.
原型默认为Object的新实例, 由于仍是对象, 故可以给该对象添加新的属性:// prototype默认为new Object(); 为了方便, 记为p_obj function Person(name) { this.name = name; } // 为 p_obj 增加 sayName 属性 Person.prototype.sayName = function(){ alert(this.name); } var john = new Person("John"); // john 的 base reference指向p_obj var eric = new Person("Eric"); // eric 的 base reference也是指向p_obj // 注意sayName代码中的this将指向实例化后的对象(this绑定) john.sayName(); // john对象本身没有sayName属性, 于是访问原型对象p_obj的sayName属性 eric.sayName(); // 访问同一个原型对象p_obj的sayName属性 var tmp = Person.prototype; tmp.boss = "David"; // 于这个运行点, p_obj已经被修改 // 根据上述属性访问流程, 新的修改(boss属性)能反映到所有的实例, 包括已经创建和即将创建的 alert("John's boss is " + john.boss); alert("Eric's boss is " + eric.boss); // hisCar和sayCar属性将增加到john对象而不是p_obj对象.. john.hisCar = "Audi"; john.sayCar = function(){ alert(this.name + " has a car of " + this.hisCar); } john.sayCar(); // ..因此下一句将错误, 因为eric对象本身和原型p_obj都没有sayName属性 /* eric.sayCar(); */2.2 原型链
除了能修改prototype指向的对象, 还能修改prototype指向哪一个对象, 即为prototype赋予一个不同的对象. 这可以实现一种简单的继承:function Superman() {} Superman.prototype.sayHello = function(){ alert("I'm a superman."); } function SupermanCan(skill){ this.skill = skill; } // 为prototype赋予Superman的实例.. SupermanCan.prototype = new Superman(); // ..再动态添加新的属性 SupermanCan.prototype.sayMore = function(){ this.sayHello(); // 调用"父类"的方法 alert("I can " + this.skill); } var david = new SupermanCan("fly"); // output: I'm a superman. I can fly david.sayMore();如果先实例化出一个对象, 再为构造函数prototype赋予一个不同的对象, 将会: 已经创建的对象的基引用不变, 将来创建的对象的基引用为新的原型对象:
var f1 = {echo: function() { alert("sound"); } }; function Foo() {}; var foo = new Foo(); // foo的基引用指向Object实例 Foo.prototype = f1; /* 未定义, 因为这是"foo对象自己或者基引用指向的对象有echo属性吗?" 而不是"foo对象自己或者Foo.prototype指向的对象有echo属性吗?" */ alert(foo.echo); var foo2 = new Foo(); // foo2的基引用指f1对象 foo2.echo(); // output: sound所有的构造函数的prototype都不能为空, 就是说Superman.prototype = null 会被解释引擎无视; 另一方面, Object构造函数也有prototype属性(该属性是只读的, 可以为原型增加属性,但不能赋予不同的对象), 故因此可以有多层的原型链, 但原型链的根必定会是Object.prototype . 这意味着给Object.prototype增加属性影响到所有对象:
Object.prototype.echo = function() { alert("hello"); } // echo属性将增加到所有对象固有对象和自定义对象 var arr = new Array(); arr.echo(); Array.echo(); function ObjCons() { this.dummy = "d"; } var obj = new ObjCons(); obj.echo(); ObjCons.echo();3. 构造函数和new的实质
构造函数是一个地地道道的函数, 一个函数之所以能成为构造函数, 是因为new运算符:this.msg = "window"; function Test() { alert(this.msg); } Test(); // window var test = new Test(); // undefined. 因为test对象没有定义msg属性二者区别在于如何切入对象: Test() 在某个对象(例子中为window)的上下文上执行代码, 即this指向这个对象; new Test()创建一个新对象, 并以这个新的对象为上下文(this指向新对象)执行代码, 然后返回这个新对象.
假如有个函数:function Test() { var dummy = "have money"; this.wish = dummy; doSomeThing(); }结合以上的所有论述, 可以推测new Test()行为的伪代码表示为:
创建一个新对象temp;
temp.constructor = Test;
temp.(base reference) = Test.prototype; // 这一句先于代码体执行, 意味着构造函数里的this.xxx能访问原型对象的属性xxx
bind: this = temp; // 将this绑定到temp对象
// 开始执行函数代码
var dummy = "have money";
this.wish = dummy; // 为temp对象添加wish属性
doSomeThing();
....
// 结束执行函数代码
return temp;
这个未必会符合内部的二进制实现, 但却能很好地解释了JavaScript的特性