数据类型
1.基本数据类型和引用类型
基本数据类型包含 number, string, boolean, undefined, 引用类型包含object 和 function.
它们之间的区别在于:
- 引用类型的值可以动态的增加属性和方法,而基本类型不行。(基本类型的包装类型属于引用类型)
var a = "hello world"; // string为基本类型,没有属性和方法,在调用方法时,实际上是转换成了包装类型(object)
var b = {}; // b为object类型,可以自由的添加属性和方法
b.type = "";
b.add = function() {};
- 基本类型复制变量值时,会创建一个新值,而引用类型不会创建新对象,只是简单复制了引用的地址
var a = 1;
var b = a; //可以认为a,b两个变量指向两个基本类型的值,此时它们的值相等,都为1
a = 2; // a变量指向的值变成了另一个基本类型的值,b不变化
console.log(b); // 输出1
var obj1 = {age : 1}; // 在堆内存中创建了一个对象,obj1的值存放了这个对象的地址
var obj2 = obj1; // 可以认为将obj1中存放对象的地址告诉了obj2
obj1.age = 2; // .操作为访问对象的属性或方法,赋值操作将对象的age属性值更改为2
console.log(obj2.age); // 读取对象的age属性,此刻为2
2.各类数据类型的值
- undefined
var x; //仅声明了变量x,并没有赋值,此时类型为undefined
x; // 特别注意,没有var表示声明变量,而直接使用,则默认为缺省window,此刻可认为 window.x = undefined
- number
var x1 = 123;
var x2 = new Number("123abc"); // x2为object, 实际值为NaN
var x3 = parseInt("123abc"); // x3的值为123
var x4 = parseFloat("0.23acv"); // x4的值为0.23
- string
var x = "hello world";
var x = 'hello world'; // 单引号和双引号均表示字符串,这与java不同
var x = ne'w String(); // x为object对象, "123".length,就是将基本类型转换成了包装类型
- boolean
var x = true; //
var x = 1 && 2; // 1为number类型,会隐式转换成boolean类型
var x = new Boolean(true); // Boolean为包装类,x 为对象类型
- object
var x = null; // 对null进行typeof操作,结果为object
var x = {};
var x = new Object(); // new关键字是执行类的构造函数,并返回该类的一个实例对象
- function
//String, Object, Array, Date等等都是类,都是function类型
function A() {}; // 定义了一个function A,也可以称之为类A
var doAdd = new Function("iNum", "alert(iNum + 20)");
doAdd(10); // 注意这里,new 返回的实例不是对象,而是一个Function(类)
补充: object类型和function类型的区别
- function类型的变量可以通过new的形式返回该方法(类)的一个实例
var x = function A() {return 1}; // 若是全局环境下定义函数,则函数将会作为window的一个属性
var obj = new x(); // 注意obj不为1,构造函数内的return在new关键字上是无效的
- function运行,只能通过对象object调用,或者通过函数的方法 call调用
function test(){console.log(111)};
test(); // 此处调用函数默认缺省了this,完整的应该为this.test();
(function test(){console.log(111)})(); // 此处为函数直接量的写法
var obj = {};
test.call(obj);
3. 数据类型之间的转换
- 在逻辑运算(与或非)和if条件判断和new Boolean()时,非boolean类型的值会转换成boolean类型的值
// 转换规则为
如果是number类型,0为false,非0为true
如果是string类型,空字符串""为false,其余为true
如果是object类型,null为false,其余为true
如果是undefined类型,一律为false
- 在字符串连接操作 +运算和new String()时,非string类型的值会转换成string类型的值
//转换规则为
如果有toString发放, 则调用toString方法
如果是undefined类型或者是null,返回"undefined"或者"null"
- 在用 new Number()时,非number类型的值,会转换成number类型的值
//转换规则为
如果是boolean类型,true变为1,false变为0
如果是undefined, 变为NaN
如果是null, 变为0
如果是string, 空字符串变为0,其余按字符串字面最前匹配转换,如不能转换则为NaN
如果是object,先调用valueOf(),按上述规则转换,如果是NaN,再调用toString,再按上述规则转换
4. 类型检测
- typeof
对数据类型进行检测,返回"undefined","string","number","boolean","object","function" - instanceof
对object类型的对象进行类型检测
var obj = {};
var arr = [];
function a(){}
var a = new a();
console.log(obj instanceof Object) // true
console.log(arr instanceof Array) // true
console.log(a instanceof a) // true
变量及执行环境
1.全局变量、属性变量、局部变量(包含方法实参)的定义
- 全局变量又可以认为是 window 对象的属性,其可以在全局执行环境利用var定义,或者在函数执行环境不使用var定义
var x = 12; // 全局执行环境下,利用var定义变量,会变为window的属性
function A(){ // 全局执行环境下,定义方法,也会变为window的属性
y = 24; // 函数执行环境下,不用var来声明变量,也会变为window的属性
}
console.log(window); // window增加了三个属性,x、A以及y
(function B(){})();//特别值得一提的是,函数直接量的写法使得B形成一个闭包,外部环境是无法访问的
B(); // B为闭包,在全局环境下无法访问,会出现 undefined提示
// 函数直接量可以如下理解
function () {
function B(){}; // 在函数执行环境下,定义了B方法,B属于局部变量,而不是全局变量
B(); // 在函数执行环境下,执行了B方法
}
- 属性变量的定义,可以直接通过.来定义,或者在创建对象时声明
var obj = {name : "lwz9103", age : 21}; // 为obj对象定义了name属性和age属性
obj.xyz = 123; // 为obj对象定义了xyz属性
- 局部变量只能在函数执行环境下定义,函数实参也可以看作是局部变量
function test(x, y) {
var txt = "nihao"; // 在函数执行环境下用var定义局部变量 txt
return x + y
};
test(1, 2); // 方法执行时,定义了方法实参 x,y
// 函数执行时实参定义的过程可以这么理解
function test(x, y) {
arguments = [1, 2]; // 将传入参数列表放入arguments对象,类似java的可变参数列表
var x = 1;
var y = 2;
}
2.执行环境及作用域链
-
变量对象(保证了执行环境能对环境中定义的变量和函数进行访问)
变量对象是执行环境用来保存在此环境下定义的变量或者函数的一个特殊对象,无法被开发者访问
-
全局执行环境和函数执行环境
全局环境是最外围的执行环境,在html中<script>标签开始执行的都是全局执行环境。 全局环境的变量对象是window,所有的全局变量和函数都是window的对象(注:全局变量可以在函数环境中非var定义) 全局环境变量对象window的生命期为打开网页到关闭网页(window.close()) 函数环境是在执行函数时创建的,在执行函数时,会创建一个活动对象作为其变量对象,用以保存在此环境下定义的变量和方法 此活动对象在创建时,会包含一个arguments对象 函数环境变量对象的生命期为开始执行函数到退出函数
-
作用域链 (保证了执行环境能对在自身以及外部环境定义的变量和函数进行访问)
每个变量对象在创建时,都会创建一个作用域链的属性。作用域的开始都是其变量对象本身,下一个则是外层环境的变量对象,以此类推,最后一个为全局环境的变量对象,即window对象。 在搜索变量时,优先在当前变量对象中搜索,然后沿着作用域链一层一层往下搜索。 作用域链在try catch和with中会延长
对象与函数
1.this对象
全局环境下,this对象为window,函数环境下,this为函数的调用者,如果函数没有显示的调用者,则这个调用者为window
function B(){
function C(){
console.log(this); // window
};
C();
console.log(this); // b
};
var b = {add : B};
b.add();
2.对象创建方式
- 工厂方式
function factory(){
var obj = new Object();
obj.name = "lwz";
obj.add = function() {};
return obj;
}
var obj = factory();
// 工厂方法的缺陷是没有对象的类型信息, 且相同的方法每个实例都需要创建
- 构造方法方式
function A(){
this.age = 1;
this.add = function(){};
}
var a = new A();
// 构造方法方式的缺陷是相同的方法每个实例都需要重新创建
- 原型方式
function A(){
this.age = 1;
}
A.prototype.add = function(){};
A.prototype.name = "张三";
var a1 = new A();
a1.name = "lwz";
var a2 = new A();
console.log(a2.name); // lwz
// 原型方法将对象方法都放在原型属性上,达到共用的目的
// 需要注意的是,原型属性会造成脏属性的情况,需要避免在原型上定义属性
3.函数原型对象
在定义函数时,同时会创建一个原型对象。该类(函数)在创建实例时,会有一个proto属性指向原型对象。
function A(){};
A.prototype.age = 12;
var a = new A();
它们之间的关系是:
函数A的prototype属性指向 A.prototype对象
A.prototype.constructor 指向函数A
a的_proto_属性指向 A.prototype
注:对象访问属性或者方法的规则
优先查找对象的一级属性/方法(for in能找到),如果不存在,则查找_proto_属性(即原型属性)下的属性和方法,依此逐级往下查找
4.继承
- 原型链继承
function B(){
this.arr = [1,2,3];
};
B.prototype = new Array();
//这个也可以这么写
//B.prototype = {
// length : 0,
// ...
// shift : function(){...},
// push: function(){...},
// ...
//}
var b = new B();
b.shift(1);
b.arr.push(4);
var c = new B();
console.log(c.arr); // 1,2,3,4
// 原型链继承缺陷是原型对象中可能包含引用属性,这样每个实例对象的这个属性变成了共用的,则会出现问题
- 组合继承
function A(){
this.arr = [1,2,3];
}
A.prototype.add = function(){};
function B(){
A.call(this); // 赋值属性
}
B.prototype = new A();
B.prototype.constructor = B;
var b1 = new B();
b1.arr.push(4);
var b2 = new B();
console.log(b2.arr); // 1,2,3
// 组合继承是显示调用了A的构造方法,使得arr存在于实例的一级属性中,但是缺陷是三级属性中也包含了arr, 即b1._proto_._proto_.arr是存在的
- 寄生组合继承
function A(){
this.arr = [1,2,3,4];
};
A.prototype.add = function() {};
function B(){
A.call(this);
};
function inheritPrototype(x, y){
var prototype = clone(x.prototype)
prototype.constructor = x;
y.prototype = prototype;
}
inheritPrototype(A, B);
// 寄生组合继承的特点是,属性拷贝了一份,原型属性也拷贝了一份,而不像组合继承那样,原型属性使用的是一个对象实例。