JavaScript开发问题搜集
# 概述
JavaScript
基于原型的面向对象系统参考了Self
语言和Smalltalk
语言。原型模式不单是一种设计模式,也被称为一种编程泛型。基于原型链
的委托机制就是原型继承的本质。
鸭子类型的通俗说法是:“如果它走起路来像鸭子,叫起来也是鸭子,那么它就是鸭子。”鸭子类型指导我们只关注对象的行为,而不关注对象本身,也就是关注HAS-A
,而不是IS-A
。在动态类型语言的面向对象设计中,鸭子类型的概念至关重要。
在JavaScript
这种将函数
作为一等对象的语言中,函数本身也是对象,函数用来封装行为并且能够被四处传递。JavaScript
的函数既可以作为普通函数
被调用,也可以作为构造器
被调用。当使用new
运算符来调用函数时,此时的函数就是一个构造器
。函数的length
属性就是一个只读的属性,表示形参的个数。在JavaScript
中,如果函数没有return
值,则默认return this。
JavaScript
是一门完整的面向对象的编程语言,但这门语言同时也拥有许多函数式语言
的特性。函数式语言有Lambda表达式
、闭包
、高阶函数
等特性。闭包
的形成与变量的作用域以及变量的生存周期密切相关。
在IE
浏览器中,由于BOM
和DOM
中的对象是使用C++
以COM
对象的方式实现的,而COM
对象的垃圾收集机制采用的是引用计数策略。跟DOM
节点相关的操作往往是非常消耗性能的。
鸭子类型举例:
var duck = {
duckSinging: function(){
console.log( ’嘎嘎嘎’ );
};
}
var chicken = {
duckSinging: function(){
console.log( ’嘎嘎嘎’ );
}
};
var choir = []; //合唱团
var joinChoir = function( animal ){
if ( animal && typeof animal.duckSinging === 'function' ){
choir.push( animal );
console.log( ’恭喜加入合唱团’ );
console.log( ’合唱团已有成员数量:' + choir.length );
}
};
joinChoir( duck ); //恭喜加入合唱团
joinChoir( chicken ); //恭喜加入合唱团
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# 原型编程范型规则
原型编程范型至少包括以下基本规则:
❏ 所有的数据都是对象。
❏ 要得到一个对象,不是通过实例化类,而是找到一个对象作为原型并克隆它。
❏ 对象会记住它的原型。
❏ 如果对象无法响应某个请求,它会把这个请求委托给它自己的原型。
# 所有数据都是对象
JavaScript
中的根对象是Object.prototype
对象。Object.prototype
对象是一个空
的对象。
var obj1 = new Object();
var obj2 = {};
console.log( Object.getPrototypeOf( obj1 ) === Object.prototype ); //输出:true
console.log( Object.getPrototypeOf( obj2 ) === Object.prototype ); //输出:true
2
3
4
5
# 对象会记住它的原型
就JavaScript
的真正实现来说,其实并不能说对象有原型,而只能说对象的构造器有原型
。对于“对象把请求委托给它自己的原型”这句话,更好的说法是对象把请求委托给它的构造器的原型。
JavaScript
给对象提供了一个名为__proto__
的隐藏属性,某个对象的__proto__
属性默认会指向它的构造器的原型对象,即{Constructor}.prototype
。实际上,__proto__
就是对象跟“对象构造器的原型”联系起来的纽带。
对象的__proto__属性会指向构造器的原型
var a = new Object();
console.log ( a.__proto__=== Object.prototype ); // 输出:true
2
# 如果对象无法响应某个请求,它会把这个请求委托给它自己的原型
这条规则即是原型继承的精髓所在。
# this
跟别的语言大相径庭的是,JavaScript
的this
总是指向一个对象,而具体指向哪个对象是在运行时基于函数的执行环境动态绑定的,而非函数被声明时的环境。
this
的指向大致可以分为以下4种。
❏ 作为对象的方法调用。
❏ 作为普通函数调用。
❏ 构造器调用。
❏ Function.prototype.call
或Function.prototype.apply
调用。
# 作为对象的方法调用
当函数作为对象的方法被调用时,this指向该对象
var obj = {
a: 1,
getA: function(){
alert ( this === obj ); // 输出:true
alert ( this.a ); // 输出: 1
}
};
obj.getA();
2
3
4
5
6
7
8
9
# 作为普通函数被调用
当函数不作为对象的属性被调用时,也就是我们常说的普通函数方式,此时的this
总是指向全局对象。在浏览器的JavaScript
里,这个全局对象是window
对象。
指向window对象
window.name = 'globalName';
var getName = function(){
return this.name;
};
console.log( getName() ); // 输出:globalName
2
3
4
5
6
7
or
window.name = 'globalName';
var myObject = {
name: 'sven',
getName: function(){
return this.name;
}
};
var getName = myObject.getName;
console.log( getName() ); // globalName
2
3
4
5
6
7
8
9
10
11
指向window对象
var obj = {
myName: 'sven',
getName: function(){
return this.myName;
}
};
console.log( obj.getName() ); // 输出:'sven'
var getName2 = obj.getName;
console.log( getName2() ); // 输出:undefined
2
3
4
5
6
7
8
9
10
11
指向window对象
<html>
<body>
<div id="div1">我是一个div</div>
</body>
<script>
var getId = document.getElementById;
getId( 'div1' );
</script>
</html>
2
3
4
5
6
7
8
9
10
11
指向window对象
<html>
<body>
<div id="div1">我是一个div</div>
</body>
<script>
window.id = 'window';
document.getElementById( 'div1' ).onclick = function(){
alert ( this.id ); // 输出:'div1'
var callback = function(){
alert ( this.id ); // 输出:'window'
}
callback();
};
</script>
</html>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
指向div的办法
document.getElementById( 'div1' ).onclick = function(){
var that = this; // 保存div的引用
var callback = function(){
alert ( that.id ); // 输出:'div1'
}
callback();
};
2
3
4
5
6
7
# 构造器调用
除了宿主提供的一些内置函数,大部分JavaScript
函数都可以当作构造器使用。构造器里的this
就指向返回的这个对象
。
var MyClass = function(){
this.name = 'sven';
};
var obj = new MyClass();
alert ( obj.name ); // 输出:sven
2
3
4
5
6
但用new
调用构造器时,还要注意一个问题,如果构造器显式地返回了一个object
类型的对象,那么此次运算结果最终会返回这个对象,而不是我们之前期待的this
。如果构造器不显式地返回任何数据,或者是返回一个非对象类型的数据,就不会造成上述问题。
var MyClass = function(){
this.name = 'sven';
return { // 显式地返回一个对象
name: 'anne'
}
};
var obj = new MyClass();
alert ( obj.name ); // 输出:anne
2
3
4
5
6
7
8
9
var MyClass = function(){
this.name = 'sven'
return 'anne'; // 返回string类型
};
var obj = new MyClass();
alert ( obj.name ); // 输出:sven
2
3
4
5
6
7
# Function.prototype.call或Function.prototype.apply调用
跟普通的函数调用相比,用Function.prototype.call
或Function.prototype.apply
可以动态地改变传入函数的this
。这两个方法的应用也非常广泛,能熟练运用这两个方法,是我们真正成为一名JavaScript
程序员的重要一步。
var obj1 = {
name: 'sven',
getName: function(){
return this.name;
}
};
var obj2 = {
name: 'anne'
};
console.log( obj1.getName() ); // 输出: sven
console.log( obj1.getName.call( obj2 ) ); // 输出:anne
2
3
4
5
6
7
8
9
10
11
12
13
document.getElementById = (function( func ){
return function(){
return func.apply( document, arguments );
}
})( document.getElementById );
var getId = document.getElementById;
var div = getId( 'div1' );
alert (div.id); // 输出: div1
2
3
4
5
6
7
8
9
10
它们的作用一模一样,区别仅在于传入参数形式的不同。
apply
接受两个参数,第一个参数指定了函数体内this
对象的指向,第二个参数为一个带下标的集合,这个集合可以为数组,也可以为类数组,apply
方法把这个集合中的元素作为参数传递给被调用的函数。
var func = function( a, b, c ){
alert ( [ a, b, c ] ); // 输出 [ 1, 2, 3 ]
};
func.apply( null, [ 1, 2, 3 ] );
2
3
4
5
call
传入的参数数量不固定,跟apply
相同的是,第一个参数也是代表函数体内的this
指向,从第二个参数开始往后,每个参数被依次传入函数。
var func = function( a, b, c ){
alert ( [ a, b, c ] ); // 输出 [ 1, 2, 3 ]
};
func.call( null, 1, 2, 3 );
2
3
4
5
当使用call
或者apply
的时候,如果我们传入的第一个参数为null
,函数体内的this
会指向默认的宿主对象,在浏览器中则是window
。
var func = function( a, b, c ){
alert ( this === window ); // 输出true
};
func.apply( null, [ 1, 2, 3 ] );
2
3
4
5
var func = function( a, b, c ){
"use strict";
alert ( this === null ); // 输出true
}
func.apply( null, [ 1, 2, 3 ] );
2
3
4
5
6
# call或apply详细用法
# 最常用法是改变this指向
var obj1 = {
name: 'sven'
};
var obj2 = {
name: 'anne'
};
window.name = 'window';
var getName = function(){
alert ( this.name );
};
getName(); // 输出: window
getName.call( obj1 ); // 输出: sven
getName.call( obj2 ); // 输出: anne
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
使用call修正this
document.getElementById( 'div1' ).onclick = function(){
alert( this.id ); // 输出:div1
};
2
3
document.getElementById( 'div1' ).onclick = function(){
alert( this.id ); // 输出:div1
var func = function(){
alert ( this.id ); // 输出:undefined
}
func();
};
2
3
4
5
6
7
document.getElementById( 'div1' ).onclick = function(){
var func = function(){
alert ( this.id ); // 输出:div1
}
func.call( this );
};
2
3
4
5
6
再比如例子
document.getElementById = (function( func ){
return function(){
return func.apply( document, arguments );
}
})( document.getElementById );
var getId = document.getElementById;
var div = getId( 'div1' );
alert ( div.id ); // 输出: div1
2
3
4
5
6
7
8
9
# Function.prototype.bind
大部分高级浏览器都实现了内置的Function.prototype.bind
,用来指定函数内部的this
指向。
Function.prototype.bind = function( context ){
var self = this; // 保存原函数
return function(){ // 返回一个新的函数
return self.apply( context, arguments ); // 执行新的函数的时候,会把之前传入的context
// 当作新函数体内的this
}
};
var obj = {
name: 'sven'
};
var func = function(){
alert ( this.name ); // 输出:sven
}.bind( obj);
func();
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Function.prototype.bind = function(){
var self = this, // 保存原函数
context = [].shift.call( arguments ), // 需要绑定的this上下文
args = [].slice.call( arguments ); // 剩余的参数转成数组
return function(){ // 返回一个新的函数
return self.apply( context, [].concat.call( args, [].slice.call( arguments ) ) );
// 执行新的函数的时候,会把之前传入的context当作新函数体内的this
// 并且组合两次分别传入的参数,作为新函数的参数
}
};
var obj = {
name: 'sven'
};
var func = function( a, b, c, d ){
alert ( this.name ); // 输出:sven
alert ( [ a, b, c, d ] ) // 输出:[ 1, 2, 3, 4 ]
}.bind( obj, 1, 2 );
func( 3, 4 );
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 借用其他对象的方法
借用方法的第一种场景是“借用构造函数”,通过这种技术,可以实现一些类似继承的效果。
var A = function( name ){
this.name = name;
};
var B = function(){
A.apply( this, arguments );
};
B.prototype.getName = function(){
return this.name;
};
var b = new B( 'sven' );
console.log( b.getName() ); // 输出: 'sven'
2
3
4
5
6
7
8
9
10
11
12
13
14
借用方法的第二种运用场景跟我们的关系更加密切。在操作arguments
的时候,我们经常非常频繁地找Array.prototype
对象借用方法。
往类数组对象arguments身上添加新元素
(function(){
Array.prototype.push.call( arguments, 3 );
console.log ( arguments ); // 输出[1,2,3]
})( 1, 2 );
2
3
4
我们可以把“任意”对象传入Array.prototype.push
。如果这个对象满足下面条件 :❏ 对象本身要可以存取属性;❏ 对象的length属性可读写。
var a = {};
Array.prototype.push.call( a, 'first' );
alert ( a.length ); // 输出:1
alert ( a[ 0 ] ); // first
2
3
4
5
var a = 1;
Array.prototype.push.call( a, 'first' );
alert ( a.length ); // 输出:undefined
alert ( a[ 0 ] ); // 输出:undefined
2
3
4
var func = function(){};
Array.prototype.push.call( func, 'first' );
alert ( func.length );
// 报错:cannot assign to read only property ‘length' of function(){}
2
3
4
5
# 闭包
闭包的形成与变量的作用域以及变量的生存周期密切相关。
# 变量作用域
变量的作用域,就是指变量的有效范围。当在函数中声明一个变量的时候,如果该变量前面没有带上关键字var
,这个变量就会成为全局变量。反之则是局部变量。变量的搜索是从内到外而非从外到内的。
var func = function(){
var a = 1;
alert ( a ); // 输出: 1
};
func();
alert ( a ); // 输出:Uncaught ReferenceError: a is not defined
2
3
4
5
6
# 变量的生存周期
全局变量的生存周期当然是永久的,除非我们主动销毁这个全局变量。在函数内用var关键字声明的局部变量来说,当退出函数时,这些局部变量即失去了它们的价值,它们都会随着函数调用的结束而被销毁。
请特别注意这个例子,局部变量a并未销毁
var func = function(){
var a = 1;
return function(){
a++;
alert ( a );
}
};
var f = func();
f(); // 输出:2
f(); // 输出:3
f(); // 输出:4
f(); // 输出:5
2
3
4
5
6
7
8
9
10
11
12
13
14
用途
for ( var i = 0, len = nodes.length; i < len; i++ ){
(function( i ){
nodes[ i ].onclick = function(){
console.log(i); //按顺序输出0,1,2,3,4,5...
}
})( i )
};
2
3
4
5
6
7
# 高阶函数
高阶函数是指至少满足下列条件之一的函数。
❏ 函数可以作为参数被传递;
❏ 函数可以作为返回值输出。
# 函数作为参数传递
把函数当作参数传递,这代表我们可以抽离出一部分容易变化的业务逻辑,把这部分业务逻辑放在函数参数中,这样一来可以分离业务代码中变化与不变的部分。其中一个重要应用场景就是常见的回调函数。
回调函数
var getUserInfo = function( userId, callback ){
$.ajax( 'http://xxx.com/getUserInfo? ' + userId, function( data ){
if ( typeof callback === 'function' ){
callback( data );
}
});
}
getUserInfo( 13157, function( data ){
alert ( data.userName );
});
2
3
4
5
6
7
8
9
10
11
var appendDiv = function( callback ){
for ( var i = 0; i < 100; i++ ){
var div = document.createElement( 'div' );
div.innerHTML = i;
document.body.appendChild( div );
if ( typeof callback === 'function' ){
callback( div );
}
}
};
appendDiv(function( node ){
node.style.display = 'none';
});
2
3
4
5
6
7
8
9
10
11
12
13
14
# 函数作为返回值输出
函数柯里化,先将数值保存,直到最后才一并计算
var monthlyCost = 0;
var cost = function( money ){
monthlyCost += money;
};
cost( 100 ); // 第1天开销
cost( 200 ); // 第2天开销
cost( 300 ); // 第3天开销
//cost( 700 ); // 第30天开销
alert ( monthlyCost ); // 输出:600
2
3
4
5
6
7
8
9
10
11
12
var cost = (function(){
var args = [];
return function(){
if ( arguments.length === 0 ){
var money = 0;
for ( var i = 0, l = args.length; i < l; i++ ){
money += args[ i ];
}
return money;
}else{
[].push.apply( args, arguments );
}
}
})();
cost( 100 ); // 未真正求值
cost( 200 ); // 未真正求值
cost( 300 ); // 未真正求值
console.log( cost() ); // 求值并输出:600
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# Undefined类型
- 使用只声明而未初始化的变量时,会返回
“undefined”
。
var a;
console.log(a); // undefined
2
- 获取一个对象的某个不存在的属性(自身属性和原型链继承属性)时,会返回
“undefined”
。
var obj = {
name: 'kingx'
};
console.log(obj.address); // undefined
2
3
4
- 函数没有明确的返回值时,却在其他地方使用了返回值,会返回
“undefined”
。
function foo() {}
console.log(foo()); // undefined
2
- 函数定义时使用了多个形式参数(后文简称为形参),而在调用时传递的参数的数量少于形参数量,那么未匹配上的参数就为
“undefined”
。
function foo(param1, param2, param3) {
console.log(param3);
}
foo(1, 2); // undefined
2
3
4
null
是JavaScript
中的关键字,而undefined
是JavaScript
中的一个全局变量,即挂载在window
对象上的一个变量,并不是关键字。
# Number
在JavaScript
中,整数和浮点数都属于Number
类型,它们都统一采用64位浮点数进行存储。
# parseInt()与Array.map()将字符串转成整型
var arr = ['1', '2', '3', '4'];
var result = arr.map(function (val) {
return parseInt(val, 10);
});
console.log(result); // [1, 2, 3, 4]
2
3
4
5
6
7
# Number.isNaN()与isNaN()
isNaN()
函数在判断是否为NaN
时,需要先进行数据类型转换,只有在无法转换为数字时才会返回“true”
;
Number.isNaN()
函数在判断是否为NaN
时,只需要判断传入的值是否为NaN
,并不会进行数据类型转换。
# 逗号运算符
不使用中间变量交换变量的值
var a = 'a';
var b = 'b';
// 方案1
a = [b, b = a][0];
// 方案2
a = [b][b = a, 0];
2
3
4
5
6
# instanceof
数组不仅是Array
类型的实例,也是Object
类型的实例。因此判断类型的时候我们先判断Array
类型。
var a = [1, 2, 3];
console.log(a instanceof Array); // true
console.log(a instanceof Object); // true
var b = {name: 'kingx'};
console.log(b instanceof Array); // false
console.log(b instanceof Object); // true
2
3
4
5
6
7
# 比较扩展
[] == 0; // true
[1] == 1; // true
[2] == 2; // true
2
3
# !x == true的所有情况
- 变量为null。
- 变量为undefined。
- 变量为空字符串' '。
- 变量为数字0,包括+0、-0。
- 变量为NaN。
# 构造函数中new操作符和this的区别
如果函数没有return
值,则默认return this
。
← 设计模式