对象
toString:所有对象都有toString()方法,基本类型除null和undefined外也有
2.toString() 语法错误因为会把 . 当成数字运算,解决办法
2..toString(); // 第二个点号可以正常解析
2 .toString(); // 注意点号前面的空格
(2).toString(); // 2先被计算
默认情况下,toString()方法被每个Object对象继承。如果此方法在自定义对象中未被覆盖,toString()返回 “[object type]”,其中type是对象的类型
第一个object:ecma规范要求toString 方法返回[object class] ,不过大多数内部类覆盖了toString方法,所以只有自定义对象的会返回[object object] ,客户端内建的对象一般class都是为Object(大写) ,虽然很多类覆盖了toString方法,但是我们可以用Object.prototype.toString.apply(o) 显示调用object.toString。
delete obj[属性名]才能删除属性名,该属性设置为null/undefined只是解除与值的关联 。用obj.hasOwnProperty(i)检验
原型属性:可以把任何类型的值赋给它(prototype)。 然而将原子类型赋给 prototype 的操作将会被忽略。
function Foo() {}
Foo.prototype = 1; // 无效
hasOwnProperty:hasOwnProperty 是 JavaScript 中唯一一个处理属性但是不查找原型链的函数。
// 修改Object.prototype
Object.prototype.bar = 1;
var foo = {goo: undefined};
foo.bar; // 1
'bar' in foo; // true
for(let i in foo)
// ——这3个都会查找
函数声明
1、将匿名函数赋值给变量
foo; // 'undefined'
foo(); // 出错:TypeError
var foo = function() {};
由于 var 定义了一个声明语句,对变量 foo 的解析是在代码运行之前,因此 foo 变量在代码运行时已经被定义过了。
但是由于赋值语句只在运行时执行,因此在相应代码执行之前, foo 的值缺省为 undefined。
命名函数的赋值表达式
2、另外一个特殊的情况是将命名函数赋值给一个变量。
var foo = function bar() {
bar(); // 正常运行
}
bar(); // 出错:ReferenceError
bar 函数声明外是不可见的,这是因为我们已经把函数赋值给了 foo; 然而在 bar 内部依然可见。这是由于 JavaScript 的 命名处理 所致, 函数名在函数内总是可见的。
注意:在IE8及IE8以下版本浏览器bar在外部也是可见的,是因为浏览器对命名函数赋值表达式进行了错误的解析, 解析成两个函数 foo 和 bar
this
函数调用的时候内部this指向window
常见误解:
Foo.method = function() {
function test() {
// this 将会被设置为全局对象(译者注:浏览器环境中也就是 window 对象)
}
test();
}
为了在 test 中获取对 Foo 对象的引用,我们需要在 method 函数内部创建一个局部变量指向 Foo 对象。
Foo.method = function() {
var that = this;
function test() {
// 使用 that 来指向 Foo 对象
}
test();
}
函数别名:也就是将一个方法赋值给一个变量。
var test = someObject.methodTest;
test();
上例中,test 就像一个普通的函数被调用;因此,函数内的 this 将不再被指向到 someObject 对象。——指向window(严格模式下是undefined)
function Foo() {}
Foo.prototype.method = function() {};
function Bar() {}
Bar.prototype = Foo.prototype;
new Bar().method();
当 method 被调用时,this 将会指向 Bar 的实例对象。
函数 是 JavaScript 中唯一拥有自身作用域的结构
循环中的闭包:
for(var i = 0; i < 10; i++) {
(function(e) {
setTimeout(function() {
console.log(e); // 可以打印1-10
}, 1000);
})(i);
}
for(var i = 0; i < 10; i++) {
setTimeout((function(e) {
return function() {
console.log(e);
}
})(i), 1000)
}
arguments 对象
JavaScript 中每个函数内都能访问一个特别变量 arguments。这个变量维护着所有传递到这个函数中的参数列表。
注意: 由于 arguments 已经被定义为函数内的一个变量。 因此通过 var 关键字定义 arguments 或者将 arguments 声明为一个形式参数, 都将导致原生的 arguments 不会被创建。
arguments 变量不是一个数组(Array)。 尽管在语法上它有数组相关的属性 length,但它不从 Array.prototype 继承,实际上它是一个对象(Object)。
因此,无法对 arguments 变量使用标准的数组方法,比如 push, pop 或者 slice。 虽然使用 for 循环遍历也是可以的,但是为了更好的使用数组方法,最好把它转化为一个真正的数组。
传递参数
function foo() {
bar.apply(null, arguments);
}
function bar(a, b, c) {
// 干活
}
function Foo() {}
Foo.prototype.method = function(a, b, c) {
console.log(this, a, b, c);
};
// 创建一个解绑定的 "method"
// 输入参数为: this, arg1, arg2...argN
Foo.method = function() {
// 结果: Foo.prototype.method.call(this, arg1, arg2... argN)
Function.call.apply(Foo.prototype.method, arguments);
};
// 上面的 Foo.method 函数和下面代码的效果是一样的:
Foo.method = function() {
var args = Array.prototype.slice.call(arguments);
Foo.prototype.method.apply(args[0], args.slice(1));
};
自动更新
arguments 对象为其内部属性以及函数形式参数创建 getter 和 setter 方法。
因此,改变形参的值会影响到 arguments 对象的值,反之亦然。
function foo(a, b, c) {
arguments[0] = 2;
a; // 2
b = 4;
arguments[1]; // 4
var d = c;
d = 9;
c; // 3
}
foo(1, 2, 3);
console.assert() ——如果断言为false,则将一个错误消息写入控制台。如果断言是 true,没有任何反应。
arguments.callee:
callee 是 arguments 对象的一个属性。它可以用于引用该函数的函数体内当前正在执行的函数。这在函数的名称是未知时很有用,例如在没有名称的函数表达式 (也称为“匿名函数”)内。
function foo() {
arguments.callee; // do something with this function object
arguments.callee.caller; // and the calling function object
}
function bigLoop() {
for(var i = 0; i < 100000; i++) {
foo(); // Would normally be inlined...
}
}
foo 不再是一个单纯的内联函数 inlining(译者注:这里指的是解析器可以做内联处理), 因为它需要知道它自己和它的调用者。 这不仅抵消了内联函数带来的性能提升,而且破坏了封装,因此现在函数可能要依赖于特定的上下文。
构造函数:通过 new 关键字方式调用的函数都被认为是构造函数。如果被调用的函数没有显式的 return 表达式,则隐式的会返回 this 对象 - 也就是新创建的对象。
显式的 return 表达式将会影响返回结果,但仅限于返回的是一个对象。
function Bar() {
return 2; //new Bar().constructor === Bar
}
new Bar(); // 返回新创建的对象,不是2
function Test() {
this.value = 2;
return {
foo: 1 // new Test().constructor === Object
};
}
new Test(); // 返回的对象
(new Test()).value === undefined //true
(new Test()).foo === 1. //true
作用域:尽管 JavaScript 支持一对花括号创建的代码段,但是并不支持块级作用域; 而仅仅支持 函数作用域。
JavaScript 中没有显式的命名空间定义,这就意味着所有对象都定义在一个全局共享的命名空间下面。
每次引用一个变量,JavaScript 会向上遍历整个作用域直到找到这个变量为止。 如果到达全局作用域但是这个变量仍未找到,则会抛出 ReferenceError 异常。
// 脚本 A
foo = '42';
// 脚本 B
var foo = '42'
脚本 A 在全局作用域内定义了变量 foo,而脚本 B 在当前作用域内定义变量 foo。
局部变量:JavaScript 中局部变量只可能通过两种方式声明,一个是作为函数参数,另一个是通过 var 关键字声明。
解决命名冲突:匿名包装器
(function() {
// 函数创建一个命名空间
window.foo = function() {
// 对外公开的函数,创建了闭包
};
})(); // 立即执行此匿名函数
匿名函数被认为是 表达式;因此为了可调用性,它们首先会被执行。
( // 小括号内的函数首先被执行
function() {}
) // 并且返回函数对象
() // 调用上面的执行结果,也就是函数对象
// 另外两种方式
+function(){}();
(function(){}());
名称解析顺序
比如,当访问函数内的 foo 变量时,JavaScript 会按照下面顺序查找:
- 1、当前作用域内是否有 var foo 的定义。 2、函数形式参数是否有使用 foo 名称的。 函数自身是否叫做 foo。
回溯到上一级作用域,然后从 #1 重新开始。
注意: 自定义 arguments 参数将会阻止原生的 arguments 对象的创建。
数组
为了达到遍历数组的最佳性能,推荐使用经典的 for 循环。
var list = [1, 2, 3, 4, 5, ...... 100000000];
for(var i = 0, l = list.length; i < l; i++) {
console.log(list[i]);
}
通过 l = list.length 来缓存数组的长度。
虽然 length 是数组的一个属性,但是在每次循环中访问它还是有性能开销。 可能最新的 JavaScript 引擎在这点上做了优化,但是我们没法保证自己的代码是否运行在这些最近的引擎之上。
实际上,不使用缓存数组长度的方式比缓存版本要慢很多。
Array 构造函数
var arr = new Array(3);
arr[1]; // undefined
1 in arr; // false, 数组还没有生成
new Array(count + 1).join(stringToRepeat);
new Array(3).join('#') 将会返回 ##
类型
typeof
typeof
返回值不准确,Object.prototype.toString
可以返回对象的内部属性 [[Class]] 的值
typeof 唯一适用:检测是否定义。如果没有定义而直接使用会导致 ReferenceError 的异常
typeof foo !== 'undefined'
JavaScript 标准文档中定义: [[Class]] 的值只可能是下面字符串中的一个: Arguments, Array, Boolean, Date, Error, Function, JSON, Math, Number, Object, RegExp, String.
Object.prototype.toString.call([]) // "[object Array]"
Object.prototype.toString.call({}) // "[object Object]"
Object.prototype.toString.call(2) // "[object Number]"
function is(type, obj) {
var clas = Object.prototype.toString.call(obj).slice(8, -1);
return obj !== undefined && obj !== null && clas === type;
}
is('String', 'test'); // true
is('String', new String('test')); // true
//在 ECMAScript 5 中,为了方便,对 null 和 undefined 调用 Object.prototype.toString 方法, 其返回值由 Object 变成了 Null 和 Undefined。
// IE8
Object.prototype.toString.call(null) // "[object Object]"
Object.prototype.toString.call(undefined) // "[object Object]"
// Firefox 4
Object.prototype.toString.call(null) // "[object Null]"
Object.prototype.toString.call(undefined) // "[object Undefined]"
instanceof
用来比较来自同一个 JavaScript 上下文的自定义对象。只有在比较自定义的对象时才有意义。 如果用来比较内置类型,将会和 typeof 操作符 一样用处不大。
类型转换
内置类型(比如 Number 和 String)的构造函数在被调用时,使用或者不使用 new 的结果完全不同。
new Number(10) === 10;
// False, 对象与数字的比较
需要比较的话,最好先显式转换成以下3种:
(1)转换为字符串:'' + 10 === '10'; // true
(2)转换成数字:+'10' === 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
(3)转换为布尔型:!!'foo'===true
核心
eval
eval 函数会在当前作用域中执行一段 JavaScript 代码字符串。
var foo = 1;
function test() {
var foo = 2;
eval('foo = 3');
return foo;
}
test(); // 3
foo; // 1
但是 eval 只在被直接调用并且调用函数就是 eval 本身时,才在当前作用域中执行。
var foo = 1;
function test() {
var foo = 2;
var bar = eval;
bar('foo = 3');
return foo;
}
test(); // 2
foo; // 3
上面的执行作用域相当于全局,等价于window.foo=3
或者
// 写法二:使用 call 函数修改 eval 执行的上下文为全局作用域
var foo = 1;
function test() {
var foo = 2;
eval.call(window, 'foo = 3');
return foo;
}
test(); // 2
foo; // 3
在任何情况下我们都应该避免使用 eval 函数。99.9% 使用 eval 的场景都有不使用 eval 的解决方案。
1、伪装的 eval
定时函数 setTimeout 和 setInterval 都可以接受字符串作为它们的第一个参数。 这个字符串总是在全局作用域中执行,因此 eval 在这种情况下没有被直接调用。
2、安全问题
eval 也存在安全问题,因为它会执行任意传给它的代码, 在代码字符串未知或者是来自一个不信任的源时,绝对不要使用 eval 函数。
undefined 和 null
undefined 是一个值为 undefined 的类型。
这个语言也定义了一个全局变量,它的值是 undefined,这个变量也被称为 undefined。
但是这个变量不是一个常量,也不是一个关键字。这意味着它的值可以轻易被覆盖。
访问未修改的全局变量 undefined。
由于没有定义 return 表达式的函数隐式返回。
return 表达式没有显式的返回任何内容。
访问不存在的属性。
函数参数没有被显式的传递值。
任何被设置为 undefined 值的变量。
由于全局变量 undefined 只是保存了 undefined 类型实际值的副本, 因此对它赋新值不会改变类型 undefined 的值。
chrome浏览器控制台:window.undefined
好像不能被覆盖了
避免覆盖技巧
//1、使用一个传递到匿名包装器的额外参数
var undefined = 123;
(function(something, foo, undefined) {
// 局部作用域里的 undefined 变量重新获得了 `undefined` 值
})('Hello World', 42);
//2、在函数内使用变量声明
var undefined = 123;
(function(something, foo) {
var undefined;
...
})('Hello World', 42);
null
JavaScript 中的 undefined 的使用场景类似于其它语言中的 null,实际上 JavaScript 中的 null 是另外一种数据类型。
它在 JavaScript 内部有一些使用场景(比如声明原型链的终结 Foo.prototype = null),但是大多数情况下都可以使用 undefined 来代替。
自动分号插入
JavaScript 不是一个没有分号的语言,恰恰相反上它需要分号来就解析源代码。 因此 JavaScript 解析器在遇到由于缺少分号导致的解析错误时,会自动在源代码中插入分号。——可能会产生一些不希望的副作用
var foo = function() {
} // 解析错误,分号丢失
test()
在前置括号的情况下,解析器不会自动插入分号
log('testing!')
(options.list || []).forEach(function(i) {})
//上面代码被解析器转换为一行。
log('testing!')(options.list || []).forEach(function(i) {})
log 函数的执行结果极大可能不是函数;这种情况下就会出现 TypeError 的错误,详细错误信息可能是 undefined is not a function。
建议绝对不要省略分号,同时也提倡将花括号和相应的表达式放在一行, 对于只有一行代码的 if 或者 else 表达式,也不应该省略花括号。
其他
setTimeout 和 setInterval
作为第一个参数的函数将会在全局作用域中执行,因此函数内的 this 将会指向这个全局对象。
function Foo() {
this.value = 42;
this.method = function() {
// this 指向全局对象
console.log(this.value); // 输出:undefined
};
setTimeout(this.method, 500);
}
new Foo();
setInterval
:当回调函数的执行被阻塞时,setInterval 仍然会发布更多的回调指令。在很小的定时间隔情况下,这会导致回调函数被堆积起来。
function foo(){
// 阻塞执行 1 秒
}
setInterval(foo, 100);
上面代码中,foo 会执行一次随后被阻塞了一秒钟。
在 foo 被阻塞的时候,setInterval 仍然在组织将来对回调函数的调用。 因此,当第一次 foo 函数调用结束时,已经有 10 次函数调用在等待执行。
处理可能的阻塞调用:使用setTimeout
function foo(){
// 阻塞执行 1 秒
setTimeout(foo, 100);
}
foo();
不仅封装了 setTimeout 回调函数,而且阻止了调用指令的堆积,可以有更多的控制。 foo 函数现在可以控制是否继续执行还是终止执行。
清除所有定时器:由于没有内置的清除所有定时器的方法,可以采用一种暴力的方式来达到这一目的。
// 清空"所有"的定时器
for(var i = 1; i < 1000; i++) {
clearTimeout(i);
}
// 如果定时器调用时返回的 ID 值大于 1000不会被清除
// 因此我们可以事先保存所有的定时器 ID,然后一把清除。
setTimeout 和 setInterval 也接受第一个参数为字符串的情况。 这个特性绝对不要使用,因为它在内部使用了 eval。
function foo() {
// 将会被调用
}
function bar() {
function foo() {
// 不会被调用
}
setTimeout('foo()', 1000);
}
bar();
由于 eval 在这种情况下不是被直接调用,因此传递到 setTimeout 的字符串会自全局作用域中执行; 因此,上面的回调函数使用的不是定义在 bar 作用域中的局部变量 foo。
建议不要在调用定时器函数时,为了向回调函数传递参数而使用字符串的形式。
function foo(a, b, c) {}
// 不要这样做
setTimeout('foo(1,2, 3)', 1000)
// 可以使用匿名函数完成相同功能
setTimeout(function() {
foo(1, 2, 3);
}, 1000)
绝对不要使用字符串作为 setTimeout 或者 setInterval 的第一个参数, 这么写的代码明显质量很差。当需要向回调函数传递参数时,可以创建一个匿名函数,在函数内执行真实的回调函数。
原文地址:JavaScript 秘密花园