JavaScript中的对象javascript
对象的概念java
JavaScript对象的描述: An object is a collection of properties and has a single prototype object. The prototype may be either an object or the null value.数组
从上面一段话中咱们能够看出,在JavaScript中,对象是一个属性的集合,而且有一个原型对象。 而这个原型自己或者为null,或者也是一个对象。浏览器
JavaScript中内置了11种对象,咱们这里关注比较特殊的两个对象Function和Object。ide
Function: 在JavaScript中函数也是对象,用来构造对象的函数叫作构造函数,如 function Animal (){}, Animal就是一个构造函数,全部的函数均可以用来构造对象,用new就是构造函数,但一般咱们采用首字母大写来与普通函数区别。 全部的构造函数都是由Function这个函数对象构造出来的,它构造了系统中全部的函数对象,包括用户自定义的函数对象,系统内置的函数对象,也包括它本身。能够用如下代码证实:函数
function Animal() {}; 布局
console.log(Animal instanceof Function); // 用户自定义函数对象 this
console.log(Object instanceof Function); //系统内置Object函数对象idea
console.log(Function instanceof Function); //Function自己spa
console.log(Date instanceof Function); //系统内置的Date对象
运行结果
Object:Object是JavaScript中另一个比较重要的内置对象,全部的对象都将继承Object原型。它自己也是一个构造函数,且由Function构造。
理解构造关系在JavaScript中十分重要,它决定了JavaScript中的原型链。对象有一个重要的属性[[prototype]], 这个[[prototype]]是一个引用,其指向构造对象自己的构造函数的prototype。 为了便于表达,咱们用Mozilla定义的__proto__来表示[[prototype]](非标准,其它JavaScript引擎不必定叫__proto__)。注意不要把__proto__和prototype弄混, 全部的对象都有一个额外的属性__proto__来指向一个原型,构造函数也不例外,这个__proto__就指向其构造函数的prototype。在本文的描述中__proto__只是指向一个prototype,而prototype自己是个对象,通常用来存放函数。
在ECMAScript中这样描述prototype:
Each constructor is a function that has a property named “prototype” that is used to implement prototype-based inheritance and shared properties.
经过上面描述咱们可知,prototype只是构造函数的一个属性,其用于实现基于原型的继承和共享属性。
下面咱们经过一段代码的内存布局图来理解对象的构造关系以及其__proto__的指向关系。
function Animal() {};
var dog = new Animal();
这两句简单的代码在内存中布局以下:
__proto__和prototype总结
prototype: prototype是在构造函数生成的时候,会默认由Object函数给其构造一个prototype,因此其prototype的__proto__指向Object的prototype,因此全部的构造函数都有prototype, 可是实例对象没有,就是你手工赋值一个prototype,那prototype也是一个普通属性,和原型没有关系。
__proto__: 全部对象(包括构造函数, 由于函数也是对象)都有__proto__属性,这个属性指向其构造函数的prototype。 每一个
由于实例的__proto__指向其构造函数的prototype, 构造函数的prototype的__proto__又指向Object的prototype,因此实例能够根据这种关联找到全部prototype上的属性和方法,这种指向关系即咱们常说的原型链。
对象的建立
因为JavaScript语法的灵活性,其对象的建立有不少方式,在阅读别人源码的时候,常常会看到各类不一样的建立方法。下面咱们看看几种常见的建立对象方式的优缺点。
var person = {
name:"Archer",
age:29,
job:"software engineer",
sayName:function(){
console.log(this.name);
}
};
person.sayName();
这种方式建立对象简单易用,可是不方便代码重用。
function createPerson(name,age, job){
var o = new Object();
o.name = name;
o.age = age;
o.job = job;
o.sayName = function(){
console.log(this.name);
};
return o;
}
var person1 = createPerson("Archer", 29,"software engineer");
var person2 = createPerson("idda", 24,"software engineer");
这种方式建立也很简单,能够重用代码。
function Person(name,age,job){
this.name = name;
this.age = age;
this.job = job;
this.sayName = function(){
console.log(this.name);
};
}
var person1 = new Person("Archer", 29,"software engineer");
var person2 = new Person("idda", 24,"software engineer");
构造函数和工厂模式比:
构造函数模式时,建立对象实际为以下4步:
构造函数和其它普通函数的区别:
//当构造函数使用
var person = new Person("Archer", 29,"software engineer");
person.sayName(); //Archer
//普通函数使用
Person("idda",20,"Doctor"); //在浏览器中,是添加到window
window.sayName(); //idda
console.log(window.age); //20
构造函数的缺点:每一个方法都要在每一个实例中建立一遍:
console.log(person1.sayName === person2.sayName); //false
内存布局为:
function Person(){
}
Person.prototype.name = "Archer";
Person.prototype.age = 28;
Person.prototype.job = "software engineer";
Person.prototype.sayName = function(){
console.log(this.name);
};
var person1 = new Person();
var person2 = new Person();
咱们建立的每一个函数都有一个prototype(原型)属性, 这个属性是一个指针,指向一个prototype对象。 这个prototype对象包含全部此函数的实 例共享的属性和方法。
原型模式的优势对比构造函数来讲就是让全部实例共享它包含的属性和方法。
console.log(person1.sayName === person2.sayName); //true
执行:Console.log(person1.name);
跟踪person1的状态可见,person1没有name属性,会根据原型链去起__proto__重查找name,其值为Archer.
执行person1.name = "idda";
后跟踪其状态,可见person1增长了一个同名属性name。
因此更改person1的属性,不会修改person2的属性,代码及结果以下:
执行结果
内存布局以下:
原型的简化语法
function Person(){
}
Person.prototype ={
name:"archer",
age:29,
job:"software engineer",
sayHello:function(){
console.log("hi");
}
};
注意简化写法constructor再也不指向Person,而是指向Object,若是须要指向Person,可手动修改:
function Person(){
}
Person.prototype ={
constructor:Person,
name:"archer",
age:29,
job:"software engineer",
sayHello:function(){
console.log("hi");
}
};
另一个微小的区别,其constructor的[[Enumerable]]的值由false变成了true,能够代码重置
Object.defineProperty(Person.prototype,”constructor”,{
enumerable:false,
value:Person
}
);
对原型的修改,都会反映到其指向它的实例。若是重写了对象原型后,对象和其新原型没有任何联系,得不到新原型的任何属性
function Person(){
}
var friend = new Person();
Person.prototype ={
name:”archer”,
age:29,
job:”software engineer:,
sayHello:function(){
console.log(“hi”);
}
};
firend不能访问name,sayHello等属性,如图
原型模型的问题是全部实例都共享原型,对于原型中的值类型,能够访问原型中的值,可是若是修改原型中的值,则会给对象自身添加一个同名属性,当下次访问此属性的时候,则返回此属性,再也不查找做用域链。对于引用类型,若是不修改引用,而修改其堆上的值,就会致使全部实例的值都发生更改。
,如:
function Person(){
}
Person.prototype.name = "Archer";
Person.prototype.age = 28;
Person.prototype.job = "software engineer";
Person.prototype.firends = ["idda","gang"];
Person.prototype.sayName = function(){
console.log(this.name);
};
var person1 = new Person();
var person2 = new Person();
person1.firends.push("maxi");
下图能够看见,咱们修改的是person1,可是person2也跟着改了:
因此咱们不多直接用原型模式,通常来讲咱们都会采用组合模式,组合模式是JavaScript中经常使用的模式
function Person(name,age,job){
this.name = name;
this.age = age;
this.job = job;
this.friends = ["mike","xiaoyu","maxi"];
}
Person.prototype = {
constructor: Person,
sayHello:function(){
console.log(this.name);
}
}
var person1 = new Person("archer",20,"software engineer");
var person2 = new Person("idea",29,"peasant-worker");
构造函数中的属性name,age,job,friends都是自身属性,将存储在实例自己里(不在原型中),修改不会影响其它实例,而函数sayHello是共享的,存储在原型中。
内存布局以下:
对象的继承
JavaScript中对象的继承也比较灵活,咱们列举几种常见的模式,并讨论其优缺点
function SuperType(){
this.name = "parent";
}
SuperType.prototype.getSuperValue = function(){
return this.name;
}
function SubType(){
this.sub_name = "child";
}
//注意:此处继承了SuperType,其实是将SubType的原型改成SuperType的实例,从而拥有其原型链
SubType.prototype = new SuperType();
//注意,此方法必定要放在上面赋值语句后面,也不能采用字面量方式声明(字面量方式声明其实是建立一个Object对象),不然修改了原型的指针,没法继承其原型链。
SubType.prototype.getSubValue = function(){
return this.sub_name;
}
var sub = new SubType();
console.log(sub.getSuperValue());
内存布局入下:
原型继承的缺点:
function SuperType(){
this.colors = new ["blue”,”green”,”red"];
}
function SubType(){
//借用构造函数继承
SuperType.call(this);
this.sayHello = function(){
console.log("hi");
}
}
借用构造的问题是,方法都在构造函数内定义,没法重用代码。
function SuperType(name){
this.name = name;
this.colors = ["white","red"]
}
SuperType.prototype.sayHello = function(){
console.log(this.name);
}
function SubType(name,age){
SuperType.call(this,name); //第二次调用
this.age = age;
}
SubType.prototype = new SuperType(); //第一次调用
SubType.prototype.constuctor = SubType;
SubType.prototype.sayAge = function(){
console.log(this.age);
}
var person = new SubType("Archer",29);
person.sayAge();
内存布局为:
组合继承的缺点在于调用了两次SuperType的构造函数,并且new SuperType调用了构造函数,形成内存浪费。SuperType的构造函数生产的变量会被SubType调用的call(this,name)给覆盖。可用下列方法改进:
将 SubType.prototype = new SuperType(); 替换为:
var F = function(){
};
F.prototype = SuperType.prototype;
SubType.prototype = new F();
改进前:
改进后能够看见其__proto__指向F,因此没有给colors和name分配内存。