- toString()方法:当数字字面量使用这个方法时会出现错误,这是因为 JavaScript 解析器的一个错误, 它试图将点操作符解析为浮点数字面值的一部分。
例:2.toString(); // 出错:SyntaxError
解决办法:
2..toString(); // 第二个点号可以正常解析
2 .toString(); // 注意点号前面的空格
(2).toString(); // 2先被计算
2.访问属性:有两种方式来访问对象的属性,点操作符或者中括号操作符
两种语法是等价的,但是中括号操作符在下面两种情况下依然有效:
动态设置属性;
属性名不是一个有效的变量名(属性名中包含空格,或者属性名是 JS 的关键词);
3.删除属性:删除属性的唯一方法是使用
delete
操作符;设置属性为
undefined
或者
null
并不能真正的删除属性, 而
仅仅是移除了属性和值的关联
4.原型:
扩展内置类型的原型:会破坏
封装;
扩展内置类型的
唯一理由是为了和新的 JavaScript 保持一致,比如
Array.forEach
5.
hasOwnProperty
是 JavaScript 中唯一一个处理属性但是
不查找原型链的函数
JavaScript
不会保护
hasOwnProperty
被非法占用,因此如果一个对象碰巧存在这个属性, 就需要使用
外部的
hasOwnProperty
函数来获取正确的结果
例:
var foo = {
hasOwnProperty: function() {
return false;
},
bar: 'Here be dragons'
};
foo.hasOwnProperty('bar'); // 总是返回 false
// 使用其它对象的 hasOwnProperty,并将其上下为设置为foo
{}.hasOwnProperty.call(foo, 'bar'); // true
6.函数声明与函数赋值表达式
函数声明提升:会在执行前被解析,因此它存在于当前上下文的
任意一个地方, 即使在函数定义体的上面被调用也是对的
foo(); // 正常运行,因为foo在代码运行前已经被创建
function foo() {}
函数赋值表达式:赋值语句只在运行时执行
foo; // 'undefined'
foo(); // 出错:TypeError
var foo = function() {};
命名函数的赋值表达式:将命名函数赋值给一个变量
var foo = function bar() {
bar(); // 正常运行
}
bar(); // 出错:ReferenceError
注:
bar
函数声明外是不可见的,这是因为我们已经把函数赋值给了
foo
; 然而在
bar
内部依然可见。这是由于 JavaScript 的命名处理所致, 函数名在函数内
总是可见的。
7.this的指向
(1).当在全部范围内使用
this
,它将会指向
全局对象;
(2).全局函数调用,指向全局对象;
注:被认为是JavaScript语言另一个错误设计的地方,因为它
从来就没有实际的用途
Foo.method = function() {
function test() {
// this 将会被设置为全局对象(注:浏览器环境中也就是 window 对象)
}
test();
}
为了在
test
中获取对
Foo
对象的引用,我们需要在
method
函数内部创建一个局部变量指向
Foo
对象
Foo.method = function() {
var that = this;
function test() {
// 使用 that 来指向 Foo 对象
}
test();
}
(3).调用某个对象的函数,指向当前对象;
(4).使用New实例化对象时,在函数内部,
this
指向
新创建的对象;
(5).当使用
Function.prototype
上的
call
或者
apply
方法时,函数内的
this
将会被
显式设置为函数调用的第一个参数;
(6).方法的赋值表达式:将一个方法
赋值给一个变量
var test = someObject.methodTest;
test();//
test
就像一个普通的函数被调用;因此,函数内的
this
将不再被指向到
someObject
对象
8.闭包
(1).模拟私有变量
function Counter(start) {
var count = start;
return {
increment: function() {
count++;
},
get: function() {
return count;
}
}
}
var foo = Counter(4);
foo.increment();
foo.get(); // 5
(2).循环中的闭包
for(var i = 0; i < 10; i++) {
setTimeout(function() {
console.log(i);
}, 1000);
}
//会输出数字
10
十次;当
console.log
被调用的时候,
匿名函数保持对外部变量
i
的引用,此时
for
循环已经结束,
i
的值被修改成了
10
方法一:最好使用 匿名包裹器(注
:其实就是我们通常说的自执行匿名函数)
for(var i = 0; i < 10; i++) {
(function(e) {
setTimeout(function() {
console.log(e);
}, 1000);
})(i);
}
//输出数字
0
到
9;
外部的匿名函数会立即执行,并把
i
作为它的参数,此时函数内
e
变量就拥有了
i
的一个拷贝。 当传递给
setTimeout
的匿名函数执行时,它就拥有了对
e
的引用,而这个值是
不会被循环改变的
方法二:从匿名包装器中返回一个函数
for(var i = 0; i < 10; i++) {
setTimeout((function(e) {
return function() {
console.log(e);
}
})(i), 1000)
}
9.构造函数:通过
new
关键字方式调用的函数
在构造函数内部 - 也就是被调用的函数内 -
this
指向新创建的对象
Object
。 这个
新创建的对象的
prototype
被指向到构造函数的
prototype
(1).如果被调用的函数没有显式的
return
表达式,则隐式的会返回
this
对象 - 也就是新创建的对象
(2).显式的
return
表达式将会影响返回结果,但
仅限于返回的是一个对象
function Bar() {
return new Number(2);
}
new Bar().constructor === Number
(3)Function()和 New Function()
如果函数返回值为常规意义上的值类型(Number、String、Boolean)时,new 函数将会返回一个该函数的实例对象,
function Student(age){
this.sex="male";
this.age=age;
}
test1=Student(18);
test2=new
Student
(18);
test1就是函数Student()执行的返回值,也就是undefined. 在函数执行过程中,属性被加到全局作用域或者Student方法所属的对象上了。执行之后window.sex=="male"
而
test2
是Student的实例,Student
{sex:"male",age:18}
如果函数返回一个引用类型(Object、Array、Function),则 new 函数与直接调用函数产生的结果等同
function Student(age) {
var obj = new Object();
obj.sex = "mail";
obj.age = age;
return obj;
}
test3=Student(22);//test3是一个Object,不是Man的实例
test4=new Student(22);//test4是Student函数返回的对象,而不是通过
new
关键字新创建的对象
10.名称解析顺序
当访问函数内的
foo
变量时,JavaScript 会按照下面顺序查找:
- 当前作用域内是否有
var foo
的定义。 - 函数形式参数是否有使用
foo
名称的。 - 函数自身是否叫做
foo
。 - 回溯到上一级作用域,然后从 #1 重新开始
11.命名冲突
可以通过
匿名包装器解决
(function() {
// 函数创建一个命名空间
window.foo = function() {
// 对外公开的函数,创建了闭包
};
})(); // 立即执行此匿名函数
// 另外两种方式
+function(){}();
(function(){}());
推荐使用
匿名包装器(也就是自执行的匿名函数)来创建命名空间。这样不仅可以防止命名冲突, 而且有利于程序的模块化。
另外,使用全局变量被认为是
不好的习惯。这样的代码倾向于产生错误和带来高的维护成本。
12.数组遍历:为了更好的性能,推荐使用普通的
for
循环并缓存数组的
length
属性。 使用
for in
遍历数组被认为是不好的代码习惯并倾向于产生错误和导致性能问题
注:为
length
设置一个更小的值会截断数组,但是增大
length
属性值不会对数组产生影响。
var foo = [1, 2, 3, 4, 5, 6];
foo.length = 3;
foo; // [1, 2, 3]
foo.length = 6;
foo; // [1, 2, 3]
13.创建数组 :应该尽量避免使用数组构造函数创建新数组。推荐使用数组的字面语法
[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
特别注意的是,此时只有
length
属性被设置,真正的数组并没有生成
var arr = new Array(3);
arr[1]; // undefined
1 in arr; // false, 数组还没有生成
14.检测对象类型:为了检测一个对象的类型,强烈推荐使用
Object.prototype.toString
方法
Object.prototype.toString.call(obj).slice(8, -1);
Object.prototype.toString.call([]) // "[object Array]"
Object.prototype.toString.call({}) // "[object Object]"
Object.prototype.toString.call(2) // "[object Number]"
除非为了检测一个变量是否已经定义,我们应尽量避免使用
typeof
操作符
instanceof
操作符应该
仅仅用来比较来自同一个 JavaScript 上下文的自定义对象。 正如
typeof
操作符一样,任何其它的用法都应该是避免的。
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
15.字符串转换为数字的常用方法
+'010' === 10
Number('010') === 10
parseInt('010', 10) === 10 // 用来转换为整数
+'010.2' === 10.2
Number('010.2') === 10.2
parseInt('010.2', 10) === 10
注:内置类型(比如
Number
和
String
)的构造函数在被调用时,使用或者不使用
new
的结果完全不同
new Number(10) === 10; // False, 对象与数字的比较
Number(10) === 10; // True, 数字与数字的比较
new Number(10) + 0 === 10; // True, 由于隐式的类型转换
通过使用
否 操作符两次,可以把一个值转换为布尔型:
!!'foo'; // true
!!''; // false
!!'0'; // true
!!'1'; // true
!!'-1' // true
!!{}; // true
!!true; // true