由浅入深JavaScript——JavaScript秘密花园笔记
万物皆对象,除了undefined和null
//除了undefined和null以外的所有值都是对象
false.toString(); // 'false'
[1, 2, 3].toString(); // '1,2,3'
function Foo(){}
Foo.bar = 1;
Foo.bar; // 1
数字对象使用时注意避免'.'被解析成浮点
2..toString(); // 第二个点号可以正常解析
2 .toString(); // 注意点号前面的空格
(2).toString(); // 2先被计算
对象的属性
//属性名可以是变量名也可以是字符串字面值
var foo = {name: 'kitten'}
var foo = {'name': 'kitten'}
//取值使可以用成员变量的方法取值也可以通过键名取值
foo.name; // kitten
foo['name']; // kitten
//甚至可以传入一个键名的字符串来获取
var get = 'name';
foo[get]; // kitten
//数字的字面值不能用成员变量的方法来取值,因为变量名不能为纯数字
foo.1234; // SyntaxError
foo['1234']; // works
//在低版本的ES中,属性名与保留字相同时,只能使用字符串字面值的方法进行声明
var foo={'delete':'0'};
//删除属性仅能通过delete关键字
delete foo.attribute;
//如果对象包含一个数组,那么length只能获取到数组的长度
//length作为属性,和name是同级别的,其他属性不会影响到length的值
var foo=[1,2,3];
foo.name='foo';
foo.length; //3
对象的原型
function Foo() {
this.value = 42;
}
Foo.prototype = {
method: function() {}
};
function Bar() {}
// 设置Bar的prototype属性为Foo的实例对象
Bar.prototype = new Foo();
Bar.prototype.foo = 'Hello World';
// 修正Bar.prototype.constructor为Bar本身
Bar.prototype.constructor = Bar;
var test = new Bar() // 创建Bar的一个新实例
// 原型链
test [Bar的实例]
Bar.prototype [Foo的实例]
{ foo: 'Hello World' }
Foo.prototype
{method: ...};
Object.prototype
{toString: ... /* etc. */};
上面的例子中,test 对象从 Bar.prototype 和 Foo.prototype 继承下来;因此, 它能访问 Foo 的原型方法 method。同时,它也能够访问那个定义在原型上的 Foo 实例属性 value。
需要注意的是 new Bar() 不会创造出一个新的 Foo 实例,而是 重复使用它原型上的那个实例;因此,所有的 Bar 实例都会共享相同的 value 属性。
使用for in遍历对象的属性时,将遍历原型链上所有的属性
如果只遍历对象本身的属性,则需要使用hasOwnProperty进行过滤
hasOwnProperty
hasOwnProperty只查询对象本身是否含有某个属性而不查询对象的原型链
唯一一个处理属性但是不查找原型链的方法
This
- 全局范围内this指向全局(浏览器内指window,下同)
- 一般函数直接调用时,函数内的this指向全局
- 对象的成员方法调用时,方法内的this指向对象本身
- 在构造函数内部时,this指向新创建的对象
- 当Function.prototype显示调用apply或call时,函数内的this被设置成第一个参数
- 当对象的成员方法被赋值给一个变量后,用变量来调用方法,this指向全局
闭包
- 函数是JavaScript中唯一一个有自身作用域的结构
- 详细解析参看深入理解JavaScript闭包
arguments
- arguments是一个对象,但与数组极为相似(不能执行数组的pop,push,slice等方法)
构造函数
- 函数内this指向新创建的Object,新建对象的prototype指向构造函数的prototype。
- 构造函数如果没有显式return,则隐式返回this对象。
- 如果return的是字面值,则新建对象的构造函数还是原构造函数,如果return的是一个对象,那么新建对象的构造函数是return的对象的构造函数。
function Bar(){
return new Number(2);
}
console.log(new Bar().constructor===Number);//output: true
工厂模式
为了不使用new关键字,构造函数必须显式返回一个值
function Bar(){
var value=1;
return {
method: function(){
return value;
}
}
}
Bar.prototype={
foo:function(){}
}
var bar1=new Bar();
var bar2=Bar();//调用返回值与上面一种相同
console.log(bar1.proto===bar2.proto); //output: true
返回一个带method方法的对象,实际上是创建了一个闭包* *
工厂模式实例
function Foo() {
var obj = {};
obj.value = 'blub';
var private = 2;
obj.someMethod = function(value) {
this.value = value;
}
obj.getPrivate = function() {
return private;
}
return obj;
}
工厂模式好处:
- 避免通过new来调用函数
- 充分利用私有变量
工厂模式弊端: - 会占用更多内存,因为新建对象不能共享原型上的方法
- 为实现继承,需要从另一个对象拷贝所有属性,或把一个对象作为原型
作用域与命名空间
JavaScript仅支持函数作用域
声明变量时不加var会声明一个全局变量
var foo=42;//全局变量
function test(){
foo=21;//全局变量
}
test();
console.log(foo);//output:21
JavaScript中局部变量只能通过两种方式生命,一种是作为函数参数,一种是通过var关键字声明
变量声明提升
bar();
var bar = function() {};
var someValue = 42;
test();
function test(data) {
if (false) {
goo = 1;
} else {
var goo = 2;
}
for(var i = 0; i < 100; i++) {
var e = data[i];
}
}
JavaScript会自动把var和function声明提升到当前作用域顶部
注意,仅提升声明,赋值语句不会被提升!!
提升之后等价于如下代码:
// var 表达式被移动到这里
var bar, someValue; // 缺省值是 'undefined'
// 函数声明也会提升
function test(data) {
var goo, i, e; // 没有块级作用域,这些变量被移动到函数顶部
if (false) {
goo = 1;
} else {
goo = 2;
}
for(i = 0; i < 100; i++) {
e = data[i];
}
}
bar(); // 出错:TypeError,因为 bar 依然是 'undefined'
someValue = 42; // 赋值语句不会被提升规则(hoisting)影响
bar = function() {};
test();
名称解析顺序
当访问函数内的foo变量时,JavaScript会按以下顺序查找:
- 当前作用域内是否有var foo定义
- 函数形式参数是否有foo名称的
- 函数自身是否叫做foo
- 回溯到上一级作用域然后执行第1步
命名空间
命名冲突可以通过匿名包装器解决
(function(){
//函数创建一个命名空间
window.foo=function(){
//对外公开的 函数,创建了闭包
}
})();//立即执行
数组
-
for in语句可遍历原型链上的所有属性
-
for in的性能不如经典for语句
-
建议用变量存储length属性而不是每次都去访问它
-
如果数组的length被赋值为另外的其他数字,则数组会被截断
*建议使用[]构造数组而非构造函数,理由如下
[1, 2, 3]; // 结果: [1, 2, 3]
new Array(1, 2, 3); // 结果: [1, 2, 3][3]; // 结果: [3] new Array(3); // 结果: [] new Array('3') // 结果: ['3'] // 译者注:因此下面的代码将会使人很迷惑 new Array(3, 4, 5); // 结果: [3, 4, 5] new Array(3) // 结果: [],此数组长度为 3
类型
JavaScript是弱类型语言
等于操作符==
- 可以通过==来判断两个值是否相等
- ==符号会在比较时对两个值进行强制类型转换
"" == "0" // false
0 == "" // true
0 == "0" // true
false == "false" // false
false == "0" // true
false == undefined // false
false == null // false
null == undefined // true
" \t\r\n" == 0 // true
严格等于操作符===
- ===只能用来判断两个相同类型的值是否相等
- 推荐显式转换类型再用===比较,而非用==直接比较
#######比较对象 - 只有同一个对象实例才会被认为是相等的
typeof操作符
Value | Class | Type |
---|---|---|
"foo" | String | string |
new String("foo") | String | string |
1.2 | Number | object |
new Number(1.2) | Number | number |
true | Boolean | boolean |
new Date() | Date | object |
new Error() | Error | object |
[1,2,3] | Array | object |
new Array(1,2,3) | Array | object |
new Function("") | Function | function |
/abc/g | RegExp | object(function in Nitro/V8) |
new RegExp("meow") | RegExp | object(function in Nitro/V8) |
{} | Object | object |
new Object() | Object | object |
为了获取对象的 [[Class]],我们需要使用定义在 Object.prototype 上的方法toString。
测试未定义变量
typeof foo !== 'undefined'
instanceof操作符
用于比较两个操作数的构造函数,只有比较自定义对象时才有意义
function Foo() {}
function Bar() {}
Bar.prototype = new Foo();
new Bar() instanceof Bar; // true
new Bar() instanceof Foo; // true
// 如果仅仅设置 Bar.prototype 为函数 Foo 本身,而不是 Foo 构造函数的一个实例
Bar.prototype = Foo;
new Bar() instanceof Foo; // false
类型转换
// 下面的比较结果是:true
new Number(10) == 10; // Number.toString() 返回的字符串被再次转换为数字
10 == '10'; // 字符串被转换为数字
10 == '+10 '; // 同上
10 == '010'; // 同上
isNaN(null) == false; // null 被转换为数字 0
// 0 当然不是一个 NaN(译者注:否定之否定)
// 下面的比较结果是:false
10 == 010;//八进制
10 == '-10';
内置类型构造函数
内置类型(比如 Number 和 String)的构造函数在被调用时,使用或者不使用 new 的结果完全不同。
new Number(10) === 10; // False, 对象与数字的比较
Number(10) === 10; // True, 数字与数字的比较
new Number(10) + 0 === 10; // True, 由于隐式的类型转换
-
转换为数字
+'010' === 10
Number('010') === 10
parseInt('010', 10) === 10 // 用来转换为整数+'010.2' === 10.2 Number('010.2') === 10.2 parseInt('010.2', 10) === 10
-
转换为布尔型
通过使用 否 操作符两次,可以把一个值转换为布尔型。
!!'foo'; // true
!!''; // false
!!'0'; // true
!!'1'; // true
!!'-1' // true
!!{}; // true
!!true; // true