函数
定义和调用
- 定义方法
- function 函数名(形参){…}
- var 变量名 = function(形参){…};
- 其中后者为匿名函数,实质为将这个匿名函数赋值给了变量。++注意末尾有分号++!
- 注意,函数体内部的语句在执行时,一旦执行到return时,函数就执行完毕,并将结果返回。如果没有return语句,函数执行完毕后也会返回结果,只是结果为undefined
- 函数对传入的实参多少并无要求。若多于形参个数则使用,少于时函数将接收到undefined,计算结果视情况而定(NaN…)
arguments:
- ++只在函数内部起作用++,并且永远指向当前函数的调用者传入的所有参数
- arguments类似Array但它不是一个Array
- 应用方面,一般用arguments.length表示传入参数个数 -
- rest:
-
-
-
-
-
-
-
变量作用域
- 与C相似,在此不做阐述
- 内部函数和外部函数的变量名重名
- 这说明JavaScript的函数在查找变量时从自身函数定义开始,从“内”向“外”查找。++如果内部函数定义了与外部函数重名的变量,则内部函数的变量将“屏蔽”外部函数的变量++
- 变量提升
- JavaScript的函数定义会先扫描整个函数体的语句,把所有申明的变量“提升”到函数顶部————–其实不用理解,总之在使用变量之前记得声明就好(否则其报错不会提及未声明变量,而是显示其所在的语句中某部分undefined)
- 全局作用域
- 不在任何函数内定义的变量就具有全局作用域
- JavaScript默认有一个全局对象window,全局作用域的变量实际上被绑定到window的一个属性。因此,访问全局变量element和访问window.element是完全一样的
- 同时,++以变量方式(var foo = function(){…})定义的函数实际上也是一个全局变量++。因此,顶层函数的定义也被视为一个全局变量,并绑定到window对象
- alert()函数亦是window的一个变量
名字空间
- 全局变量会绑定到window上,不同的JavaScript文件如果使用了相同的全局变量,或者定义了相同名字的顶层函数,都会造成命名冲突,并且很难被发现
- 减少冲突的一个方法是把自己的所有变量和函数全部绑定到一个全局变量中
// 唯一的全局变量MYAPP:
var MYAPP = {};
// 其他变量:
MYAPP.name = 'myapp';
MYAPP.version = 1.0;
// 其他函数:
MYAPP.foo = function () {
return 'foo';
};
如图,将代码全部放入唯一的名字空间MYAPP中,会大大减少全局变量冲突的可能
- jQuery,YUI,underscore等库都以此方法避免全局变量冲突的可能
局部作用域
- JavaScript的变量作用域实际上是函数内部,我们在for循环等语句块中是无法定义具有局部作用域的变量的
'use strict';
function foo() {
for (var i=0; i<100; i++) {
//
}
i += 100; // 仍然可以引用变量i
}
为此,ES6引入了新关键字let,用let替代var可以申明一个块级作用域的变量
'use strict';
function foo() {
var sum = 0;
for (let i=0; i<100; i++) {
sum += i;
}
i += 1; // SyntaxError
}
常量
- 一般来讲我们用大写字母区分变量与常量。但JavaScript中定义时并没有严格区分。也就是说你要是非给常量赋变量(比如PI=6x之类的)也是没办法的事。与人方便自己方便,靠自觉吧
- 更加靠谱的方式是用关键字const定义常量。const和let都有块级作用域
- const PI = 3.14;
- PI = 3; //有些浏览器不报错,但是无效果(和C中的const一样,不能更改)
方法
- 在一个对象中绑定函数,称为这个对象的方法
- 而在js中,对象的定义则类似于C中的结构体。在其中加上一个函数,绑定到对象上的函数就成为方法。但是注意,它用了一个this关键字!这是什么东西?
var xiaoming = {
name: '小明',
birth: 1990,
age: function () {
var y = new Date().getFullYear();
return y - this.birth;
}
};
- 在一个方法内部,this是一个特殊变量,它始终指向当前对象,也就是xiaoming变量。所以,this.birth可以拿到xiaoming的birth属性
- 我们将上边的代码拆分开写:
function getAge() {
var y = new Date().getFullYear();
return y - this.birth;
}
var xiaoming = {
name: '小明',
birth: 1990,
age: getAge
};
xiaoming.age(); // 25, 正常结果
getAge(); // NaN
- 为什么单独调用getAge()返回了NaN?
- js的函数若调用了this,那么这个this到底指向谁?——–答案是:视情况而定
- 若以对象的方法形式调用,比如‘xiaoming.age()’,则this指向被调用的对象
- 若单独调用函数,比如getAge(),此时该函数的this指向全局对象,也就是window
- 而且更难受的是,你若写成这样
var fn = xiaoming.age; // 先拿到xiaoming的age函数
fn(); // NaN
也是不行的
- 要保证this指向正确,必须用obj.xxx()的形式调用
- 好了,在此不为this变量做过多的解释,遇到具体问题再进行分析。我已经将有关于此的博客收藏,需要时进行查看。总之,由于this的不严谨性,尽量减少其使用频率
-
-
-
-
apply
- apply为我们提供了给予this确定指向的方法。它接收两个参数,需要绑定的this变量 ,和函数本身的参数。故上述getAge()调用可以写作
function getAge() {
var y = new Date().getFullYear();
return y - this.birth;
}
var xiaoming = {
name: '小明',
birth: 1990,
age: getAge
};
xiaoming.age(); // 25
getAge.apply(xiaoming, []); // 25, this指向xiaoming, 参数为空
- 而另一个与apply()类似的方法是call(),但二者也是有区别的
- apply()将参数打包成Array再传入
- call()把参数顺序传入
- eg:分别使用apply()和call()调用Math.max(3,4,5)
Math.max.apply(null, [3, 5, 4]); // 5
Math.max.call(null, 3, 5, 4); // 5
++对于普通函数调用,我们通常把this绑定为null++
装饰器
- 不仅可以用来确定指向的方法,apply()还可以改变函数的行为
- js中,所有对象都是动态的。即使内置的函数,我们也可以重新指向新的函数
- eg:统计代码中共调用了多少次parseInt()
var count = 0;
var oldParseInt = parseInt; // 保存原函数
window.parseInt = function () {
count += 1;
return oldParseInt.apply(null, arguments); // 调用原函数
};
// 测试:
parseInt('10');
parseInt('20');
parseInt('30');
count; // 3
注意,此例在备份原函数后用自定义函数count+=1替换掉了原函数parseInt();
简而言之,apply和袖时期等都是对this缺陷的一个补足,最根本的方法还是运用obj.xxx()的形式调用,以此来避免错误。当然,this亦是js中最重要的一份子之一,在循环,嵌套循环等条件下均有意想不到的妙用,故还需熟练掌握。
其具体使用方法我存放在我的收藏中,方便查看。
高阶函数
- 众所周知,函数可接收变量。而变量又可以指向函数。所以在此我们给下定义:接受另一个函数作为参数的函数我们称为高阶函数
map/reduce
- 如果你读过Google的那篇大名鼎鼎的论文“MapReduce: Simplified Data Processing on Large Clusters”,你就能大概明白map/reduce的概念
- 然而我并没有看过,所以咱们还是从最笨的方式来吧。先上代码:
function pow(x) {
return x * x;
}
var arr = [1, 2, 3, 4, 5, 6, 7, 8, 9];
arr.map(pow); // [1, 4, 9, 16, 25, 36, 49, 64, 81]
- 诺,如图,map传入的参数是++pow,也就是函数对象本身++。
- 其实就此题而言,用循环亦可解决。但是此类将函数作为参数传入的方法其实更加简便。不仅如此,我们还可以用map解决其他一定规则的运算。比如下面的将Array中的数字转为字符串
var arr = [1, 2, 3, 4, 5, 6, 7, 8, 9];
arr.map(String); // ['1', '2', '3', '4', '5', '6', '7', '8', '9']
如此,只需一行代码。可见map具有强大的抽象运算规则能力。如此函数,定当多多注意
reduce
- reduce()与map()函数不同之处在于,reduce()规定只能且必须接受两个参数。reduce()把结果继续和序列的下一个元素做累计计算,其效果为
[x1, x2, x3, x4].reduce(f) = f(f(f(x1, x2), x3), x4)
reduce亦有多种用法
- 用reduce对一个Array求和(求积同理):
var arr = [1, 3, 5, 7, 9];
arr.reduce(function (x, y) {
return x + y;
}); // 25
- 利用reduce求积
'use strict';
function product(arr) {
return arr.reduce(function(x,y){return x*y;})
}
- 把[1, 3, 5, 7, 9]变换成整数13579
var arr = [1, 3, 5, 7, 9];
arr.reduce(function (x, y) {
return x * 10 + y;
}); // 13579
- 用map将字符串变成整数
'use strict';
var arr = ['1', '2', '3'];
var r;
r = arr.map(function(x){return parseInt(x);});
很多人直接用 r = arr.map(Number);
但实际上,Number不会转整数只会转数字;
所以应该是:
r = arr.map(function(x){return parseInt(x);});
这样即规避了,由于map函数自动给parseInt传了2个参数导致的解析错误,又能装换整数。
- 把用户输入的不规范的英文名字,变为首字母大写,其他小写的规范名字。输入:[‘adam’, ‘LISA’, ‘barT’],输出:[‘Adam’, ‘Lisa’, ‘Bart’]
'use strict';
function normalize(arr) {
return arr.map(function(x){
return x[0].toUpperCase()+x.substring(1).toLowerCase()
})
}
- 想办法把一个字符串13579先变成Array——[1, 3, 5, 7, 9],再利用reduce()就可以写出一个把字符串转换为Number的函数。
练习:不要使用JavaScript内置的parseInt()函数,利用map和reduce操作实现一个string2int()函数:
'use strict';
function string2int(s) {
//split()分割字符串返回的是一个数组;
var arr = s.split('');
return arr.map(function(x){
//js的弱类型转换,‘-’会将字符串转变为数字,
//x (乘) 1也是一个道理,但这样为何不直接return s (乘) 1呢;
return x-0;
}).reduce(function a(x,y){
return x*10+y;
});
}
filter
- filter的作用是将Array中的某些元素过滤掉后返回剩下的元素。与map类似,,filter()接收一个参数。但++filter()将传入的函数依此作用于每个元素++然后根据返回的bool值决定该元素的去留
- eg:删除Array中的偶数
var arr = [1, 2, 4, 5, 6, 9, 10, 15];
var r = arr.filter(function (x) {
return x % 2 !== 0;
});
r; // [1, 5, 9, 15]
- eg:删掉Array中的空字符串
var arr = ['A', '', 'B', null, undefined, 'C', ' '];
var r = arr.filter(function (s) {
return s && s.trim(); // 注意:IE9以下的版本没有trim()方法
});
r; // ['A', 'B', 'C']
回调参数
- filter()所接收的回调参数其实可以有多个参数。通常我们使用第一个参数表示Array的某个元素,第二个参数表示元素位置,第三个参数表示数组本身
var arr = ['A', 'B', 'C'];
var r = arr.filter(function (element, index, self) {
console.log(element); // 依次打印'A', 'B', 'C'
console.log(index); // 依次打印0, 1, 2
console.log(self); // self就是变量arr
return true;
});
- 同时,利用filter亦可除去Array中重复元素。(去除重复元素依靠的是indexOf总是返回第一个元素的位置,后续的重复元素位置与indexOf返回的位置不相等,因此被filter滤掉了。)
'use strict';
var
r,
arr = ['apple', 'strawberry', 'banana', 'pear', 'apple', 'orange', 'orange', 'strawberry'];
r = arr.filter(function (element, index, self) {
return self.indexOf(element) === index;
});
alert(r.toString()); //apple,strawberry,banana,pear,orange
- 用filter()筛选出素数
'use strict';
function get_primes(arr) {
return arr.filter(function(x){
for(var i=2;i<x;i++)
if(x%i===0)
return false;
if(x>1)
return true;
});
}
sort
- JavaScript中sort()亦是用来排序的,不过排序结果为从小到大,以ASCII码为基准。好啦下面我们看一下例子
// 看上去正常的结果:
['Google', 'Apple', 'Microsoft'].sort(); // ['Apple', 'Google', 'Microsoft'];
// apple排在了最后:
['Google', 'apple', 'Microsoft'].sort(); // ['Google', 'Microsoft", 'apple']
// 无法理解的结果:
[10, 20, 1, 2].sort(); // [1, 10, 2, 20]
你会发现,第三种方法竟然毫无逻辑可言,与咱们当年学的C不一样啊?
具体情况嘛,js的sort()比较别扭,他是++先默认将所有元素转换为String后再排序++,如此一来第三种情况自然好理解啦
- 当然,如此不科学的方式自然有解决办法。js作为一种高级语言,它还可以接收一个比较函数来实现自定义的排序
- 按数字大小排序:
var arr = [10, 20, 1, 2];
arr.sort(function (x, y) {
if (x < y) {
return -1;
}
if (x > y) {
return 1;
}
return 0;
}); // [1, 2, 10, 20]
- 倒序排序
var arr = [10, 20, 1, 2];
arr.sort(function (x, y) {
if (x < y) {
return 1;
}
if (x > y) {
return -1;
}
return 0;
}); // [20, 10, 2, 1]
- 说了这么多,如何忽略刚才情况中的大小写问题使其直接按照字母表顺序排列呢?这就需要我们自己定义出忽略大小的比较算法了
var arr = ['Google', 'apple', 'Microsoft'];
arr.sort(function (s1, s2) {
x1 = s1.toUpperCase();
x2 = s2.toUpperCase();
if (x1 < x2) {
return -1;
}
if (x1 > x2) {
return 1;
}
return 0;
}); // ['apple', 'Google', 'Microsoft']
如图,此算法即是将字母统一变为大写或小写在比较
- 不过友情提示:sort()方法会直接对Array进行修改,它返回的结果仍是当前Array
var a1 = ['B', 'A', 'C'];
var a2 = a1.sort();
a1; // ['A', 'B', 'C']
a2; // ['A', 'B', 'C']
a1 === a2; // true, a1和a2是同一对象
高阶函数还有另外一个特性:接收函数作为返回值
我们先举个例子:
function lazy_sum(arr) {
var sum = function () {
return arr.reduce(function (x, y) {
return x + y;
});
}
return sum;
}
当我们调用lazy_sum()时,返回的并不是求和结果,而是求和函数:
var f = lazy_sum([1, 2, 3, 4, 5]); // function sum()
而调用函数f时,才真正计算求和的结果:
f(); // 15
此例中,我们并未立即调用求和函数,而是将sum定义在lazy_sum()中,使得sum可以引用外部函数lazy_sum()的参数和局部变量。相关参数变量和变量都保存在返回的函数中,这就是我们所说的闭包
Ps:当我们调用lazy_sum()时,每次调用都会返回一个新的函数,即使传入相同的参数:
var f1 = lazy_sum([1, 2, 3, 4, 5]);
var f2 = lazy_sum([1, 2, 3, 4, 5]);
f1 === f2; // false