JavaScript面向对象系列:六、对象模式(二)

构造函数中的私有成员

模块模式在定义单个对象的私有属性上十分有效,但是对于那些同样需要私有属性的自定义类型,也可以在构造函数中使用类型的模式来创建每个实例的私有数据。

例如

function Person (name){
var age = 25;

this.name = name;

this.getAge = function(){
return age;
}

this.growOlder = function(){
age++;
}

}

var person = new Person("laowang");

console.log(person.name); //"laowang"
console.log(person.getAge()); //25

person.age = 100;
console.log(person.getAge()); //25

person.growOlder();
console.log(person.getAge()); //26

如果需要所有实例可以共享的私有数据,可以结合模块模式和构造函数。

例如

var Person = (function (name){
var age = 25;

this.name = name;

function InnerPerson (name){
this.name = name;
}

InnerPerson.prototype.getAge = function(){
return age;
}

InnerPerson.prototype.growOlder = function(){
age++;
}

return InnerPerson;
})();

var person1 = new Person("laowang");
var person2 = new Person("xiaowang");

console.log(person1.name); //"laowang"
console.log(person1.getAge()); //25

console.log(person2.name); //"xiaowang"
console.log(person2.getAge()); //25

person1.growOlder();
console.log(person1.getAge()); //26
console.log(person2.getAge()); //26

JavaScript面向对象系列:六、对象模式(一)

前言

js有很多创建对象的模式,完成工作的方式也不是只有一种。可以随时定义自己的类型和自己的泛用对象。可以使用继承或者混入等其他技术令对象间行为共享。也可以利用js高级技巧来组织对象结构被改变。

私有成员和特权成员

js对象对象的所有属性都是公有的,且没有显式的方法指定某个属性不能被外界某个对象访问。然而,有时候可能不希望数据公有。

模块模式

模块模式是一种拥有私有数据的单件对象的模式。基本做法就是使用立即调用函数表达式(IIFE)来返回一个对象。IIFE是一种被定义后立即调用并产生结果的函数表达式,该函数表达可以包括任意数量的本地变量,它们在函数外不可见。因为返回的对象被定义在函数内部,对象的方法可以访问这些数据。(IIFE定义的所有对象都可以访问通用的本地变量)以这种方式访问私有数据的方法被称为特权方法。

基本格式如下

var yourObject = (function(){
//私有数据

return {
//公有方法和属性
};
})();

IIFE是js中一种很流行的模式,部分原因就是模块模式中的应用。

模块模式允许使用普通变量作为非公有对象属性。通过创建必报函数作为对象方法来操作它们。闭包函数就是一个可以访问其作用域外部数据的普通函数。

例如

var person = (function (){
var age = 25;

return {
name:"laowang",
getAge:function(){
return age;
},
growOlder:function(){
age++;
}
};
})();

console.log(person.name); //"laowang"
console.log(person.getAge()); //25

person.age = 100;
console.log(person.getAge()); //25

person.growOlder();
console.log(person.getAge()); //26

模块模式还有一个变种叫暴露模块模式,它将所有的变量和方法都组织在IIFE的顶部,然后将它们设置到需要被返回的对象上。可以使用暴露模块模式改写上面的例子。

例如

var person = (function (){
var age = 25;

function getAge(){
return age;
}

function growOlder(){
age++;
}

return {
name:"laowang",
getAge:getAge,
growOlder:growOlder
};
})();

console.log(person.name); //"laowang"
console.log(person.getAge()); //25

person.age = 100;
console.log(person.getAge()); //25

person.growOlder();
console.log(person.getAge()); //26

JavaScript面向对象系列:五、继承

原型对象链和 Object.prototype

js內建的继承方法被称为原型对象链,又可以称为原型对象继承。原型对象的属性可以经由对象实例访问,这就是继承的一种形式。对象实例继承了原型对象的属性。因为原型对象也是一个对象,他也有自己的原型对象并继承其属性。这就是原型对象链,而原型对象继承它的原型对象,以此类推。

所有对象,包括哪些你自己定义的对象都自动继承自 Object,除非另有指定。更确切的说,所有对象都继承自 Object.prototype。任何以队形字面量形式定义的对象,其 [[Prototype]] 的值都被设为 Object.prototype,这意味着它继承 Object.prototype 的属性。

var book = {
title:"javascript"
};

var prototype = Object.getPrototypeOf(book);
console.log(prototype === Object.prototype); //true

继承自 Object.peototype 的方法

前面说到的很多方法其实都是定义在 Object.prototype 上面的。因此可以被其他对象继承。

方法 作用
hasOwnProperty() 检查是否存在一个给定名字的自有属性
propertyIsEnumerable() 检查一个自有属性是否可枚举
isPrototypeof() 检查一个对象是否是另一个对象的原型对象
valueOf() 返回一个对象的值表达
toString() 返回一个对象的字符串表达

valueOf,每当一个操作符被用于一个对象时就会调用 valueOf() 方法。 valueOf() 默认返回对象实例本身。原始封装类型重写了 valueOf() 以使得它对 String 返回一个字符串,对 Boolean 返回一个布尔值,对 Number 返回一个数字。类似的,Date 对象返回一个 epoch 时间。如果你的对象也要这样使用操作符,也可以自定义 valueOf() 方法.定义的时候并没有改变操作符的行为,仅仅定了操作符默认行为所使用的值。

toString(),一旦 valueOf() 方法返回的是一个引用而不是原始值的时候,就会回退调用 toString() 方法。另外,当js期望一个字符串时,也会对原始值隐式调用 toString() .例如,当加号操作符的一边是一个字符串时,另一边会被自动转换成字符串。如果另一边是一个原始值,会自动被转换成一个字符串表达(例如,true 转换成 “true”)。如果另一边是一个引用值,则会调用 valueOf()。如果 valueOf() 返回一个引用值,则调用 toString()

例如

var book = {
title:"javascript"
};

var message = "Book = " + book;
console.log(message); //"Book = [object Object]"

这段以 “Book =” 和book来构造字符串。因为book是一个对象,此时调用它的 toString() 方法。该方法继承自 Object.prototype,大部分js引擎返回默认值 “[object Object]”。如果对这个值满意,就不需要改变对象的 toString() 方法。定义自己的 toString() 方法有时候可以为此类字符串转换提供更过信息的值。

var book = {
title:"javascript",
toString:function(){
return "[Book " + this.title + "]";
}
};

var message = "Book = " + book;
console.log(message); //"Book = [Book javascript]"

修改 Object.pototype

所有的对象都默认继承自 Object.prototype,所以改变 Object.prototype 会影响所有的对象,是非常危险的。

例如

Object.prototype.add = function(value){
return this + value;
}

var book = {
title:"javascript"
};

console.log(book.add(5)); //"[object Object]5";
console.log("title".add("end")); //"titleend"

//在浏览器中
console.log(document.add(true)); //"[object HTMLDocument]true"
console.log(window.add(5)); //"[object Window]6"

上面给 Object.prototype 添加方法可能会带来不可预知的结果

例如

var empty = {};

for(var prpperty in empty){
console.log(property);
}
// add

空对象依然会输出一个”add”属性。考虑到js中 for-in 使用频繁,为 Object.prototype 添加可枚举属性会影响大量代码。所以可以在 for-in 中使用 hasOwnProperty().

例如

var empty = {};

for(var prpperty in empty){
if(empty.hasOwnProperty(property)){
console.log(property);
}
}
//

这样循环只会输出对象自有属性,不会输出原型属性。

对象继承

对象继承是最简单的继承类型。唯一需要做的就是制定那个对象是新对象的 [[Prototype]] 。对象字面量形式会隐式指定 Object.prototype 为其 [[Prototype]],也可以使用 Object.create() 方法显式指定。

例如

var book = {
title:"javascript"
};

//和下面是一样的

var book = Object.create(Object.prototype,{
title:{
configurable:true,
enumerable:true,
value:"javascript",
writable:true
}
});

继承其他对象

var person1 = {
name:"laowang",
sayName:function(){
console.log(this.name);
}
};

var person2 = Object.create(person1,{
name:{
configurable:true,
enumerable:true,
value:"xiaowang",
writable:true
}
});

person1.sayName(); //"laowang"
person2.sayName(); //"xiaowang"

console.log(person1.hasOwnProperty("sayName")); //true
console.log(person1.isPrototypeOf(person2)); //true
console.log(person2.hasOwnProperty("sayName")); //false

另外,也可以通过 Object.create() 创建 [[Prototype]] 为null的对象,这样的对象是没有原型对象链的对象。意味着 toString()valueOf() 等內建方法都不存在该对象上面。实际上,这种对象完全就是一个没有任何预定义属性的白板,也是一个完美的哈希容器,因为不会有自由属性和原型属性的冲突。

例如

var nakedObject = object.create(null);

console.log("toString" in nakedObject); //false
console.log("valueOf" in nakedObject); //false

构造函数继承

js中的对象继承也是构造函数继承的基础。几乎所有的函数都有 prototype 属性,它可以被修改或者换。该 prototype 属性被自动设置为一个新的继承自 Object.prototype 的泛用对象,该对象有一个自有属性 constructor ,实际上js引擎自动做了下面的事情

function YourConstructor(){

}

//js引擎自动为你做了下面的事情
YourConstructor.prototype = Object.create(Object.prototype,{
constructor:{
configurable:true,
enumerable:true,
value:YourConstructor,
writable:true
}
});

创建出来的对象都继承自 Object.prototype。YourConstructor 是Object的子类,Object是YourConstructor 的父类。

由于 prototype 属性可写,可以通过改写它来改变原型对象链。

例如

function Rectangle(length,width){
this.length = length;
this.width = width;
}

Rectangle.prototype.getArea = function(){
return this.length * this.width;
};

Rectangle.prototype.toString = function(){
return "[Rectangle " + this.length + "X" + this.width + " ]";
};

//继承Reactangle
function Square(size){
this.length = size;
this.width = size;
}

Square.prototype = new Rectangle();
Square.prototype.constuctor = Square;
Square.prototype.toString = function(){
return "[Square " + this.length + "X" + this.width + " ]";
};

 

var rect = new Rectangle(5,10);
var square = new Square(6);

console.log(rect.getArea()); //50
console.log(square.getArea()); //36

console.log(rect.toString()); //"[Rectangle 5X10 ]"
console.log(square.toString()); //"[Square 6X6 ]"

console.log(rect instanceof Rectangle); //true
console.log(rect instanceof Object); //true

console.log(square instanceof Square); //true
console.log(square instanceof Rectangle); //true
console.log(square instanceof Object); //true

此时不需要给Rectangle的调用提供参数,因为他们不需要被使用,而且如果提供了,那所有Square的对象实例都会共享同样的维度。用这种方式改变原型对象链时,需要确保构造函数不会再参数缺失时抛出错误(很多构造函数包含的初始化逻辑会需要参数)且构造函数不会改变任何全局状态,比如追踪有多少实例被创建等。

rect作为Rectangle的实例被创建,而square则是作为Square的实例被创建。两个对象都有getArea方法,那是因为继承自Reatangle.prototype。instanceof操作符认为变量square同时是Square、Rectangle、Object的实例,因为instanceof是使用原型对象链检查对象类型。

使用 Object.create() 方法可以简化并且不会导致参数缺失而报错。

例如

function Rectangle(length,width){
this.length = length;
this.width = width;
}

Rectangle.prototype.getArea = function(){
return this.length * this.width;
};

Rectangle.prototype.toString = function(){
return "[Rectangle " + this.length + "X" + this.width + " ]";
};

//继承Reactangle
function Square(size){
this.length = size;
this.width = size;
}

Square.prototype = Object.create(Rectangle.prototype.{
constructor:{
configurable:true,
enumerable:true,
value:Square,
writable:true
}
});
Square.prototype.toString = function(){
return "[Square " + this.length + "X" + this.width + " ]";
};

 

var rect = new Rectangle(5,10);
var square = new Square(6);

console.log(rect.getArea()); //50
console.log(square.getArea()); //36

console.log(rect.toString()); //"[Rectangle 5X10 ]"
console.log(square.toString()); //"[Square 6X6 ]"

console.log(rect instanceof Rectangle); //true
console.log(rect instanceof Object); //true

console.log(square instanceof Square); //true
console.log(square instanceof Rectangle); //true
console.log(square instanceof Object); //true
````

## 构造函数窃取

由于js中的继承是通过原型对象链来实现的,因此不需要调用对象的父类构造函数。如果需要在子类构造函数中调用父类构造函数,就需要用 ___call()___ 或者 ___apply()___。

例如
```javascript
function Rectangle(length,width){
this.length = length;
this.width = width;
}

Rectangle.prototype.getArea = function(){
return this.length * this.width;
};

Rectangle.prototype.toString = function(){
return "[Rectangle " + this.length + "X" + this.width + " ]";
};

//继承Reactangle
function Square(size){
Retangle.call(this,size,size);
}

Square.prototype = Object.create(Rectangle.prototype.{
constructor:{
configurable:true,
enumerable:true,
value:Square,
writable:true
}
});
Square.prototype.toString = function(){
return "[Square " + this.length + "X" + this.width + " ]";
};

 

var square = new Square(6);

console.log(square.length); //6
console.log(square.width); //6
console.log(square.getArea()); //36

由于这种做法模仿了那些基于类语言的类继承,通常被称为伪类继承。

访问父类方法

子类提供的新功能覆盖父类方法很常见,但是如果还想访问父类方法,只能通过 call() 或者 apply() 来访问了,而且这是唯一方法。

例如

function Rectangle(length,width){
this.length = length;
this.width = width;
}

Rectangle.prototype.getArea = function(){
return this.length * this.width;
};

Rectangle.prototype.toString = function(){
return "[Rectangle " + this.length + "X" + this.width + " ]";
};

//继承Reactangle
function Square(size){
Retangle.call(this,size,size);
}

Square.prototype = Object.create(Rectangle.prototype.{
constructor:{
configurable:true,
enumerable:true,
value:Square,
writable:true
}
});
Square.prototype.toString = function(){
var text = Rectangle.prototype.toString().call(this);
return text.replace("Rectangle","Square");
};

总结

js通过原型对象链支持继承。当将一个对象的 [[Prototype]] 设置为另一个对象时,就在这两个对象之间创建了一条原型对象链。所有的泛用对象都自动继承自 Object.prototype 。如果你想创建一个继承自其它对象的对象,你可以用 Object.create() 指定 [[Prototype]] 为一个新对象。

 

可以在构造函数中创建原型对象链来完成自定义类型之间的继承。通过将构造函数的 prototype 属性设置为某一个对象那个,就建立了自定义类型对象和该对象的继承关系。构造函数的所有对象、实例共享同一个原型对象,所以他们都继承自该对象。这个技术在继承其他对象的方式时工作得很好。但是不能用原型继承自有属性。

为了正确继承自有属性,可以使用构造函数窃取。只需要以 call() 或者 apply() 调用父类的构造函数,就可以在子类里面完成各种初始化。结合构造函数窃取和原型对象链是js中最常见的继承手段。由于和基于类的继承相似,这个组合经常被称为伪类继承。

可以通过直接访问父类原型对象的方式访问父类方法。必须以 call() 或者 apply() 执行父类方法并传入一个子类的对象。

JavaScript面向对象系列:四、构造函数和原型对象

[[Prototype]] 属性

一个对象实例通过内部属性 [[Prototype]] 跟踪其原型对象。该属性是一个指向该实例使用的原型对象的指针,当你new创建一个新的对象时,构造函数的原型对象会被赋值给该对象的 [[Prototype]] 属性。

可以调用对象的 Object.getPrototypeOf() 方法读取[[Prototype]] 属性的值。

var obj = {};
var prototype = Object.getPrototypeOf(obj);
console.log(prototype === Object.prototype); //true

大部分js引擎都会提供一个 __ proto __属性。该属性使你可以直接读写 ___[[Prototype]]___ 属性。

可以使用 isPrototypeOf() 方法检查某个对象是否是另一个对象的原型对象,该方法在所有对象中都有.

var obj = {};
console.log(Object.prototype.isPrototypeOf(obj)); //true

因为obj是一个泛用对象,原型是Object.prototype

当读取一个对象的属性时,js引擎首先在该对象的自有属性中查找属性名。如果找到则返回。如果自有属性中不包含该名字,则js引擎会搜索[[Prototype]] 中的对象,如果找到则返回。如果找不到,则返回undefined。

var obj = {};
console.log(obj.toString()); //"[object Object]"

obj.toString = function(){
return "[object Custom]";
}
console.log(obj.toString()); //"[object Custom]"

delete obj.toString;
console.log(obj.toString()); //"[object Object]"

delete obj.toString;
console.log(obj.toString()); //"[object Object]"

上述例子也说明了,仅当自有属性被删除时,原型属性才会再一次被使用。delete操作符仅对自有属性起作用,无法删除原型对象的属性。

在构造函数中使用原型对象

使用对象字面量形式改写原型对象时要注意 constructor 属性

例如

function Person(name){
this.name = name;
}

Person.prototype = {
sayName:function(){
console.log(this.name);
},
toString:function(){
return "[Person " + this.name + "]";
}
}

var person1 = new Person("laowang");

console.log(person1 instanceof Person); //true
console.log(person1.constructor === Person); //false
console.log(person1.constructor === Object); //true

使用这种方式创建原型对象,constructor 属性会有误差。每一个原型对象都有一个 constructor 属性,这是其他对象实例没有的。

当一个函数被创建时,它的 prototype 属性也被创建,且该原型对象的 constructor 属性指向该函数。当使用对象字面量形式改写原型对象的时候,constructor 被置为泛对象Object。得手动修正。

例如

function Person(name){
this.name = name;
}

Person.prototype = {
constructor:Person,
sayName:function(){
console.log(this.name);
},
toString:function(){
return "[Person " + this.name + "]";
}
}

var person1 = new Person("laowang");
var person2 = new Person("xiaowang");

console.log(person1 instanceof Person); //true
console.log(person1.constructor === Person); //true
console.log(person1.constructor === Object); //false

console.log(person2 instanceof Person); //true
console.log(person2.constructor === Person); //true
console.log(person2.constructor === Object); //false

构造函数、原型对象和对象实例之间的关系最有趣的一个方面也许就是对象实例和构造函数之间没有直接联系。不过对象实例和原型对象以及原型对象和构造函数之间都有直接联系。

改变原型对象

给定类型的所有对象实例功效一个原型对象,所以可以一次性扩充所有对象实例。_[[Prototype]] 属性只是包含了一个指向原型对象的指针。任何对原型对象的改变都立即反映到所有引用它的对象实例上。

例如

function Person(name){
this.name = name;
}

Person.prototype = {
constructor:Person,
sayName:function(){
console.log(this.name);
},
toString:function(){
return "[Person " + this.name + "]";
}
}

var person1 = new Person("laowang");
var person2 = new Person("xiaowang");

console.log("sayHi" in person1); //false
console.log("sayHi" in person2); //false

Person.prototype.sayHi = function(){
console.log("Hi");
};

person1.sayHi(); //"Hi"
person2.sayHi(); //"Hi"

对象封印和对象冻结只能操作对象的自有属性。封印或者冻结之后,无法添加自有属性或者改变冻结对象的自有属性。

例如

function Person(name){
this.name = name;
}

Person.prototype = {
constructor:Person,
sayName:function(){
console.log(this.name);
},
toString:function(){
return "[Person " + this.name + "]";
}
}

var person1 = new Person("laowang");
var person2 = new Person("xiaowang");

Object.freeze(person1);

console.log("sayHi" in person1); //false
console.log("sayHi" in person2); //false

Person.prototype.sayHi = function(){
console.log("Hi");
};

person1.sayHi(); //"Hi"
person2.sayHi(); //"Hi"

其实,[[Prototype]] 属性是对象实例的自有属性,属性本身被冻结,但是其指向的值(原型对象)并没有被冻结。

实际开发中,可能不会频繁的使用原型对象,但是理解对象实例及其原型对象之间的关系是非常重要的。

改变内建原型对象

所有的內建对象都有构造函数,因此也都有原型对象可以改变。

例如,简单修改Array的原型对象

Array.prototype.sum = function(){
return this.reduce(function(pre,cur){
return pre + cur;
});
};

var numbers = [1,2,3,4,5];
var result = numbers.sum();

console.log(result); //15

总结

构造函数就是用 new 操作符调用的普通函数。可以随时定义自己的构造函数来创建多个具有同样属性的对象。可以用 instanceof 操作符或直接访问 constructor 属性来鉴别对象是被哪个构造函数创建的。

每一个函数都有都具有 prototype 属性,它定义了该构造函数创建的所有对象共享的属性。通常,共享的方法和原始值属性被定义在原型对象里,而其他属性都定义在构造函数里。 constructor 属性实际上被定义在原型对象里供所有对象实例共享。

原型对象被保存在对象实例内部的 [[Prototype]] 属性中。这个属性时一个引用而不是副本。由于js查找属性的机制,对原型对象的修改都立刻出现在所有对象实例中。当试图访问一个对象的某个属性时,js首先在自有属性里查找该名字,如果自有属性中没有找到则在原型属性中查找。这样的机制意味着原型对象可以随时改变而引用它的对象实例则立即反映出这些改变。

內建对象也有可以被修改的原型对象。

JavaScript面向对象系列:二、函数

在js中,函数气死就是对象。使函数不同于其他对象的决定性特点是函数存在一个被称为 [[Call]] 的内部属性。内部属性无法通过代码访问而是定义了代码执行时的行为。es为js的对象定义了多种内部属性,这些内部属性都用双重中括号来标注。

[[Call]] 属性是函数独有的,表明该对象是可以被执行的。由于仅函数拥有该属性,es定义typeof操作符对任何具有 [[Call]] 属性的对象返回”function”。这在过去曾导致一些问题,因为某些浏览器曾经在正则表达式中包含 [[Call]] 属性,导致后者被错误鉴别为函数。现在,所有的浏览器行为都一致。typeof 不会再将正则表达式鉴别为函数了.

声明还是表达式

函数具有两种字面形式

第一种:函数声明

function add(num1,num2){
return num1 + num2;
}

第二种:函数表达式

var add = function(num1,num2){
return num1 + num2;
}

这两种看起来相似,实际上有一个非常重要的区别

函数名声形式的会被提升至上下文(context)顶部,而函数表达式不会

所以就会有下面的情况

var result = add(5,5);

function add(num1,num2){
return num1 + num2;
}

这种实际上是能正确运行的,这是因为函数声明被提升至上下文顶部,好像被写成

function add(num1,num2){
return num1 + num2;
}

var result = add(5,5);

但是下面这种就会报错

var result = add(5,5);

var add = function(num1,num2){
return num1 + num2;
}

因为函数表达式不会被提升,所以上面执行时找不到函数。
不过,只要你始终在使用函数前定义他们,你就可以随意使用者两种字面形式.

参数

js的函数另一个独特之处在于你可以给函数传递任意数量的参数却不造成错误。因为函数参数实际被保存在arguments的类数组对象中。

函数的命名参数只不过是为了方便使用,并不是真正的限制了该函数可接受参数的个数。

函数的命名参数都是函数的期望参数,函数的length属性只会显示出期望参数的个数。

例如

情况一

function reflect(value){
return value;
}

console.log(reflect("1")); // "1"
console.log(reflect("1"),25); //"1"
console.log(reflect.length); //1

情况二

function reflect(){
return arguments[0];
}

console.log(reflect("1")); // "1"
console.log(reflect("1"),25); //"1"
console.log(reflect.length); //0

重载

大多数面向对象语言支持函数重载,它能让一个函数具有多个 函数签名(函数签名由函数的名字,参数的个数以及其类型组成) ,因为js可以接受任意数量的参数且没有类型限制,所以js没有函数重载。

例如

function sayMessage(message){
console.log(message);
}

function sayMessage(){
console.log("Default message");
}

sayMessage("Hello"); //"Default message"

这里为什么是 “Default message”,可以用对象来帮助理解

var sayMessage = new Function("message","console.log(message);");
sayMessage = new Function("message","console.log(\"Default message\");");
sayMessage("Hello!"); //Default message

但是js可以模仿函数重载

例如

function sayMessage(message){
if(arguments.length === 0){
message = "Default message";
}
console.log(message);
}
sayMessage("Hello!"); //Hello!

总结

js函数的独特之处在于它们同时也是对象,也就是说它们可以被访问、复制、覆盖,就像其他对象一样。js中的函数和其他对象最大区别在于函数对象有一个特殊的内部属性 [[Call]],包含了该函数的执行指令。typeof 操作符会在对象内部查找这个内部属性,如果找到,就返回 “function”

函数字面形式有两种:声明和表达式。函数声明会被提升至上下文顶部,而函数表达式不会。但是函数表达式可以用于任何可以使用值的地方,例如赋值语句、函数参数或者另一个函数的返回值。

函数是对象,所以存在一个构造函数 Function。

JavaScript面向对象系列:一、类型

js有5中原始类型 Boolean number string null undefined,所有原始类型的值都有字面形式。字面形式是不被保存在变量中的值。

js和其他许多语言一样,原始类型的变量直接保存原始值(而不是一个指向对象的指针)。当你将原始值赋值给一个变量时,改值将被赋值到变量中。也是就说,如果你使一个变量等于另一个变量时,每一个变量都有它自己的一份数据拷贝.例如

var color1 = "red";
var color2 = color1;

每个含有原始变量的变量使用自己的存储空间,一个变量的改变不会影响到其他变量.

虽然字符串、数字和布尔值是原始类型,但是他们也拥有方法(null和undefined没有方法).

尽管原始类型拥有方法,但是他们不是对象,js使他们看上起像对象一样,以此来提供语言上的一致性体验。

引用类型是指js中的对象,同时也是在语言中能找到的最接近类的东西。引用值就是引用类型的实例,也是对象的同义词。对象是属性的无序列表。

当你将一个对象赋值给变量时,实际是赋值给这个变量一个指针。将一个变量赋值给另一个变量时,两个变量各获得了一份指针的拷贝,指向内存中的一个对象。

js语言有垃圾收集功能,因此当你使用引用类型时无需担心内存分配。但是最好在不使用对象时将其引用解除,让垃圾收集器对那块内存进行释放。解除引用的最佳手段是将对象变量设置为null。

js内建类型 Array Date Error Function Object RegExp

鉴别引用类型,对于函数,typeof 返回的是 funcion,但是其他引用类型的返回都是object.其他的引用类型可以使用instanceof来鉴别。

es5引入Array.isArray(item)来鉴别数组

原始封装类型共有三种,String,Number,Boolean。这些特殊引用类型的存在使得原始类型用起来和对象一样方便。

字符串对象的存在仅用于该语句并在随后被销毁。

例如:

var name = "aaaa";
name.last = "bbbb"
console.log(name.last); //undefined

下面是实际在js引擎中实际发生的事

var name = "aaaa";
var temp = new String(name);
temp.last = "bbbb";
temp = null;
var temp = new String(name);
console.log(temp); //undefined
temp = null

实际上是在一个立刻就被销毁的临时对象上而不是字符串上添加了新的属性。之后当你试图访问该属性时,另一个不同的临时对象被创建,而新属性并不存在。虽然原始封装类型会被自动创建,在这些值上进行instanceof检查对应类型的返回值却都是false。

例如

var name = "aaa";
var count = 10;
var found = false;

console.log(name instanceof String); //false
console.log(count instanceof Number); //false
console.log(found instanceof Boolean); //false

这是因为临时对象仅在值被读取时被创建。instanceof操作符并没有真的读取任何东西,也就是没有临时对象的创建,于是它告诉我们这些值并不属于原始封装类型。
你也可以手动创建原始封装类型,但是有某些副作用

var name = new String("aaa");
var count = new Number(10);
var found = new Boolean(false);

console.log(typeof name); //"object"
console.log(typeof count); //"object"
console.log(typeof found); //"object"

手动创建原始封装类型实际会创建出一个object,这意味着typeof无法手动鉴别出你实际保存的数据类型

一个对象在条件判断语句中总被认为是true,无论该对象的值是不是等于false。

var found = new Boolean(false);
if(found){
console.log("Found")
}

//结果执行了,"Found"

总结

javascript中虽然没有类,但是有类型。每个变量或数据都有一个对应的原始类型或者引用类型。5种原始类型(字符串、数字、布尔值、空类型、未定义)的值会被直接保存在变量对象中。除了空类型,都可以用typeof来鉴别。空类型必须直接跟null进行比较才能鉴别。

引用类型是js中最接近类的东西,而对象则是引用类型的实例。可以使用new操作符或者字面量的形式创建新对象。通常可以用点号访问属性和方法,也可以用中括号。函数在js中也是对象,可以使用typeof来鉴别他们。至于其他引用类型,你应该用instanceof和一个构造函数来鉴别。

为了让原始类型看上去更像引用类型,js提供了三种原始封装类型:String、Number、Boolean。js会在背后创建这些对象是的你能够像使用普通对象那样直接使用原始值,但是这些临时对象在使用它们的语句结束时就立刻被销毁。虽然你也可以自己创建原始封装类型的实例,但是它们太容易令人误解,所以最好别这么干。