JS中对象的定义:无序属性的集合,其属性值可以包含基本值,对象或者函数。我们可以把js的对象想象成散列表,无非就是一组键值对,其中的值可以是数据或函数。
属性有两种类型:数据属性和访问器属性。
数据属性:包含一个数据值的位置,在这个位置可以读取和写入值。数据属性有四个特性:
configurable 属性能否delete掉,属性特性能否修改,能否把数据属性变为访问器属性
enumerable 能否通过for in循环返回属性
writable 能否修改属性的值
value 属性的数据值
var person = {};
//person的name属性值为Jack 不可修改值
Object.defineProperty(person,"name",{
writable:false,
value:"Jack"
});
alert(person.name);
person.name="Grey";
alert(person.name);
Object.defineProperty(person,"age",{
configurable:false,
value:22
})
alert(person.age);
delete person.age;//configurable为false,不能修改
alert(person.age);
访问器属性:不包含数据值,它们包含一对getter和setter函数。
configurable 属性能否delete掉,属性特性能否修改,能否把数据属性变为访问器属性
enumerable 能否通过for in循环返回属性
get 读取属性时调用的函数
set 写入属性时调用的函数
访问器属性不能直接定义,需通过defineProperty来定义。在调用该方法时,如果不指定,configurable,enumerable,writable特性的默认值都是false
var book={
_year:2004,
edition:1
};
Object.defineProperty(book,"year",{
get:function(){
return this._year;
},
set:function(newValue){
if(newValue>2004){
this._year=newValue;
this.edition+=newValue-2004;
}
}
});
book.year=2006;
alert(book.edition);
book._year=2007;//直接设置下划线属性的值也会起作用
_year前面的下划线是一种常用的记号,用于表示只能通过对象方法定义的属性。_year是数据属性,year是访问器属性
定义多个属性 通过描述符一次定义多个属性,第一个参数是要修改的对象,第二个对象的属性与第一个对象中要添加或修改的属性一一对应
var book={};
Object.defineProperties(book,{
_year:{
value:2004
},
edition:{
value:1
},
year:{
get:function(){
return this._year;
},
set:function(){
if(arguments[0]>2004){
this._year=arguments[0];
this.edition+=arguments[0]-2004;
}
}
}
})
var des=Object.getOwnPropertyDescriptor(book,"_year");
console.log(des);//读取属性描述符
创建对象
工厂模式
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("jack",22,"worker");
person1.sayname();
console.log(person1);
alert(person1 instanceof Object);//工厂模式的坏处,无法确定对象类型,都是Object
构造函数模式
function Person(name,age,job){
this.name=name;
this.age=age;
this.job=job;
this.sayname=function(){
alert(this.name);
}
}
var person1 = new Person("jack",33,"workers")
person1.sayname();
console.log(person1);
alert(person1 instanceof Person);//构造函数可以确定对象类型 但是实例上每个方法都要在实例上创建一遍
原型模式
我们创建的每个函数都有一个prototype属性,这个属性是一个指针,指向一个对象,这个对象包含特定类型的所有实例共享的属性和方法。
function Person(){
}
Person.prototype.name="jack";
Person.prototype.age=29;
Person.prototype.job="coder";
Person.prototype.sayname=function(){
alert(this.name);
}
var person1 = new Person();
person1.sayname();//jack
var person2 = new Person();
person2.name="lucy";
person2.sayname();//lucy
person1.sayname();//jack
无论什么时候,只要创建一个新函数,就会为该函数创建一个prototype属性,这个属性指向函数的原型对象。而原型对象里也有一个constructor指针指向该函数。当用函数创建一个新实例以后,这个实例的内部的proto指针将指向原型。
console.log(Object.getPrototypeOf(person1));//实例对应的原型
每当读取某个对象的某个属性时,搜索首先从对象实例本身开始,如果找到了则返回对应的值,找不到到原型对象里找。
我们在实例对象里添加一个属性,即使和原型里的某个属性重名,也不会覆盖原型里的属性。
person1.city="beijing";
alert(person1.hasOwnProperty('city'));//属性在实例中返回true true
alert(person1.hasOwnProperty('name'));//false
in 只要属性能找到就返回true
alert("job" in person1);
for(var p in person1){//遍历所有属性,包括原型中的
alert(p);
}
alert(Object.keys(person1));//返回所有属性组成的字符串,不包含原型
字面量重写原型对象
function Person(){}
//字面量重写原型对象
Person.prototype={
constructor:Person//重写设定指针
name:"jack",
age:29,
job:"coder",
sayName:function(){
alert(this.name);
}
}
原型中查找值的过程是一次搜索,因此我们对原型对象所做的任何修改都能立即从实例上反应出来,即使先创建实例后修改原型也照样如此。
原型模式的缺点:如果属性是引用类型,一个实例的修改会导致所有实例的值都被修改。
创建自定义类型的最常见方式是组合使用构造函数模式和原型模式。(实例属性用构造,方法和共享属性用原型)
function Person(name,age,job){
this.name=name;
this.age=age;
this.job=job;
this.friends=["lucy","bob"];
}
Person.prototype={
constructor:Person,
sayName:function(){
alert(this.name);
}
}
var person1=new Person("jack",22,"worker");
继承
主要依靠原型链来完成
让某个实例的原型对象等于另一类型的实例。
function Person(name,age,job){
this.name=name;
this.age=age;
this.job=job;
this.friends=["lucy","bob"];
}
Person.prototype={
constructor:Person,
sayName:function(){
alert(this.name);
}
}
function Student(school){
this.school=school;
}
Student.prototype=new Person();
Student.prototype.saySchhol=function(){
alert(this.school);
};
var s1=new Student("ustc");
s1.name="jack";
s1.age=12;
s1.sayName();
console.log(s1);
原型链的问题:一 父类的引用属性,子类一但修改,所有子类实例的值都会变化,二 无法向父类传递构造参数。
借用构造函数
在子类的构造函数内部调用父类构造函数
function Student(school){
Person.call(this);//每个子类都会有父类属性的拷贝副本
this.school=school;
}
组合继承
使用原型链实现对原型属性和方法的继续,而借用构造函数来实现对实例属性的继承。
function Person(name){
this.name=name;
}
Person.prototype.sayname=function(){
alert(this.name);
}
function Student(name,school,score){
Person.call(this,name);
this.school=school;
this.score=score;
}
Student.prototype=new Person("Jack");
Student.saySChool=function(){
alert(this.school);
}
var s1=new Student("lucy","ustc",99);
var s2=new Student("mike","autc",80);
console.log(s1);
console.log(s2);
s1.sayname();
s2.sayname();
寄生式继承
增强原型式继承的功能的一种模式
缺点:使用寄生式继承来为对象添加函数,会由于不能做到函数复用而降低效率,这一点与构造函数模式类似。
const createAnother = function(original){
const clone = object(original)
clone.sayHi = function(){
console.log('hi')
}
return clone
}
const person = {
name: 'Nicholas',
friends: ['Shelby', 'Court', 'Van']
}
const anotherPerson = createAnother(person)
anotherPerson.sayHi() //hi
寄生组合式继承
所谓寄生组合式继承,就是借用构造函数来继承属性
const object = function(o){
function F(){}
F.prototype = o
return new F()
}
const inheritPrototype = function(Child, Parent){
const prototype = object(Parent.prototype)
prototype.constructor = Child
Child.prototype = prototype
Child.__proto__ = Parent
}
function Parent(name) {
this.name = name
}
Parent.sayHello = function (){
console.log('hello')
}
Parent.prototype.sayName = function() {
console.log('my name is ' + this.name)
return this.name
}
function Child(name, age) {
Parent.call(this, name)
this.age = age
}
inheritPrototype(Child, Parent)
Child.prototype.sayAge = function () {
console.log('my age is ' + this.age)
return this.age
}
明天开始第七章 函数表达式