JavaScript学习笔记(二)

二、函数

1.js中函数(也是一个对象)如果没有return语句,函数执行完成后会返回undefined,有两种定义函数的方法,第二种是将函数名abs视为指向abs函数的变量,function (x)是一个匿名函数,“这个匿名函数赋值给了变量abs,所以通过变量abs就可以调用该函数”,示例代码:

function abs(x) {
    if (x >= 0) {
        return x;
    } else {
        return -x;
    }
}

var abs = function (x) {
    if (x >= 0) {
        return x;
    } else {
        return -x;
    }
};

2.js函数允许传入任意个参数而不影响调用(不会报错),会出现两种特殊情况:(1)传入的参数比定义的参数多,函数执行没有问题;(2)传入的参数比定义的参数少,未接收到具体的值的参数将收到undefined。注意js的不等于符号不是!=而是!==

3.“js的关键字arguments只在函数内部起作用,并且永远指向当前函数的调用者传入的所有参数(不管是多于还是少于定义的参数个数),arguments类似Array但又不是一个Array,常用于判断传入参数的个数:arguments.length”。

4.ES6标准引入了参数rest,多于函数定义参数个数的参数以数组的形式赋给rest,如果没传入足够的参数,rest的值是空数组[],而不是undefined,并且rest参数以...rest的形式写在最后,示例代码:

function foo(a, b, ...rest) {
    console.log('a = ' + a);
    console.log('b = ' + b);
    console.log(rest);
}

foo(1, 2, 3, 4, 5);
// 结果:
// a = 1
// b = 2
// Array [ 3, 4, 5 ]

foo(1);
// 结果:
// a = 1
// b = undefined
// Array []

5.练习用rest参数编写一个sum()函数,接收任意个参数(数值)并返回它们的和(接收任意个参数意味着sum函数只需要定义rest参数):

'use strict';
function sum(...rest) {
    var x = 0;
    for (var i = 0; i < rest.length; ++i) {
        if (typeof rest[i] === 'number') {
            x = x + rest[i];   
        }
    }
    return x;
}

// 测试:
var i, args = [];
for (i=1; i<=100; i++) {
    args.push(i);
}
if (sum() !== 0) {
    console.log('测试失败: sum() = ' + sum());
} else if (sum(1) !== 1) {
    console.log('测试失败: sum(1) = ' + sum(1));
} else if (sum(2, 3) !== 5) {
    console.log('测试失败: sum(2, 3) = ' + sum(2, 3));
} else if (sum.apply(null, args) !== 5050) {
    console.log('测试失败: sum(1, 2, 3, ..., 100) = ' + sum.apply(null, args));
} else {
    console.log('测试通过!');
}

6.注意正确的return多行语句写法需要括号括起来。

7.练习1,定义一个计算圆面积的函数area_of_circle(),它有两个参数:r: 表示圆的半径;pi: 表示π的值,如果不传,则默认3.14:

'use strict';

function area_of_circle(r, pi) {
    var s = 0;
    if (arguments.length === 1) {
        s = 3.14 * r * r;
    }else if (arguments.length === 2) {
        s = pi * r * r;
    }
    return s;
}
// 测试:
if (area_of_circle(2) === 12.56 && area_of_circle(2, 3.1416) === 12.5664) {
    console.log('测试通过');
} else {
    console.log('测试失败');
}

练习2,小明是一个JavaScript新手,他写了一个max()函数,返回两个数中较大的那个,但无论传入什么数,max()函数总是返回undefined,问题在于return多行语句处理时,return后面自动补上了分号;,修改后的代码:

'use strict';

function max(a, b) {
    if (a > b) {
        return a;
    } else {
        return b;
    }
}
console.log(max(15, 20));

8.“js的函数可以互相嵌套,内部函数可以访问(也可以改变)外部函数定义的变量,反过来则不行”,示例代码:

'use strict'; 
function foo() {    
    var x = 1;    
    function bar() {        
        var y = x++; // bar可以访问foo的变量x
        console.log('内部改变了外部的x:' + x);
    }
    bar();
    console.log('外部的x:' + x); // 外部函数的x值也改变了
}
foo();

结果:
内部改变了外部的x:2
外部的x:2

此外,“如果内部函数定义了与外部函数重名的变量,则内部函数的变量将屏蔽外部函数的变量”。

9.注意:“在函数内部定义变量时,请严格遵守“在函数内部首先申明所有变量”这一规则,最常见的做法是用一个var申明函数内部用到的所有变量”,示例代码:

'use strict';
function foo() {
    var
        x = 1, // x初始化为1
        y = x + 1, // y初始化为2
        z, i; // z和i为undefined
    // 其他语句
    console.log('x:' + x + ', y:' + y + ', z:' + z + ', i:' + i);
}
foo();

结果:
x:1, y:2, z:undefined, i:undefined

10.“不在任何函数内定义的变量就具有全局作用域,JS默认有一个全局对象window,全局作用域的变量实际上被绑定成为window的一个属性”,示例代码:

'use strict';

var course = 'Learn JavaScript';
alert(course); // 'Learn JavaScript'
alert(window.course); // 'Learn JavaScript'

“以变量方式var foo = function () {}定义的函数实际上也是一个全局变量,因此顶层函数的定义也被视为一个全局变量,并绑定到window对象”,示例代码:

'use strict';

function foo() {
    alert('foo');
}

foo(); // 直接调用foo()
window.foo(); // 通过window.foo()调用

11.“把自己的代码全部放入唯一的名字空间中(把自己的所有全局变量和顶层函数全部绑定给一个全局对象),会大大减少全局变量冲突的可能。”

12.“在for循环等语句块中是无法定义具有局部作用域的变量的”,函数内以for (var i = 0;...)形式定义的变量i作用域其实是整个函数内部,不是局限于for循环内部,“用let替代var可以申明一个块级作用域(在函数内部的括号{}是一个块级作用域,如for循环)的变量”。

13.解构赋值可以简化代码(但需要在支持ES6解构赋值特性的浏览器中运行),其可以将数组或对象中的元素或属性赋值给多个变量,但要注意赋值时结构要一致,并且可以忽略一些元素,但需要注意数组进行解构赋值时的情况,示例代码:

let [, , z] = ['hello', 'JavaScript', 'ES6']; // 忽略前两个元素,只对z赋值第三个元素
z; // 'ES6'

对象进行解构赋值且忽略一些元素时不用特殊处理,示例代码:

'use strict';

var person = {
    name: '小明',
    age: 20,
    gender: 'male',
    passport: 'G-12345678',
    school: 'No.4 middle school'
};
var {name, age, passport} = person;
// name, age, passport分别被赋值为对应属性:
console.log('name = ' + name + ', age = ' + age + ', passport = ' + passport);

结果:
name = 小明, age = 20, passport = G-12345678

注意如何将嵌套的对象属性赋值给变量的情况(重点学习),以及要使用的变量名和属性名不一致的情况,示例代码:

var person = {
    name: '小明',
    age: 20,
    gender: 'male',
    passport: 'G-12345678',
    school: 'No.4 middle school'
};

// 把passport属性赋值给变量id:
let {name, passport:id} = person;
name; // '小明'
id; // 'G-12345678'
// 注意: passport不是变量,而是为了让变量id获得passport属性:
passport; // Uncaught ReferenceError: passport is not defined

为了避免不存在的属性返回undefined,解构赋值可以使用默认值。注意最好在对象变量解构赋值时同时声明变量且赋值,如果先声明变量再赋值会出现问题,此时用小括号()括起来即可。

// 声明变量:
var x, y;
// 解构赋值:
{x, y} = { name: '小明', x: 100, y: 200};
// 语法错误: Uncaught SyntaxError: Unexpected token =

解决:
({x, y} = { name: '小明', x: 100, y: 200});

14.绑定给对象的函数成为对象的方法,绑定给对象的函数中,this指向该对象,但如果在该函数内部再定义函数,在内部函数中this指向undefined(在strict模式下)或window变量(不在strict模式下)注意:(1)要保证this指向正确,必须用obj.xxx()的形式调用;(2)为了可以在方法内部定义其他的函数,可以参考以下代码捕获this

'use strict';

var xiaoming = {
    name: '小明',
    birth: 1990,
    age: function () {
        var that = this; // 在方法内部一开始就捕获this
        function getAgeFromBirth() {
            var y = new Date().getFullYear();
            return y - that.birth; // 用that而不是this
        }
        return getAgeFromBirth();
    }
};

//xiaoming.age(); // 25
var fun = xiaoming.age;
fun();

15.要指定函数的this指向哪个对象,可以用函数本身的apply方法或者call方法,普通函数(将this绑定为null)调用示例如下:

// 调用Math.max(3, 5, 4)
Math.max.apply(null, [3, 5, 4]); // 5
Math.max.call(null, 3, 5, 4); // 5

注意:“利用apply方法还可以动态改变函数的行为”,方法部分涉及的this关键字和applycall方法需要重点理解学习,最好的做法是写代码的时候明确this的指向。

16.在js中高阶函数可以接收另一个函数作为参数。map方法定义在数组中,通过数组变量.map(函数名),可以将map方法的函数参数作用于数组的每个元素,返回处理后的新数组;“Array的reduce()把一个函数作用在这个Array[x1, x2, x3...]上,这个函数必须接收两个参数,reduce方法把结果继续和序列的下一个元素做累积计算”,效果是:

[x1, x2, x3, x4].reduce(f) = f(f(f(x1, x2), x3), x4)
// 用reduce对数组进行求和
var arr = [1, 3, 5, 7, 9];
arr.reduce(function (x, y) {
    return x + y;
}); // 25

17.“想办法把一个字符串13579先变成Array——[1, 3, 5, 7, 9],再利用reduce()就可以写出一个把字符串转换为Number的函数”,练习不要使用JavaScript内置的parseInt()函数,利用map和reduce操作实现一个string2int()函数:

'use strict';

function string2int(s) {
    var str = [];
    // 把字符串中的字符放入数组中
    // 方法一:用var...of遍历字符串
    for (var c of s) {
        str.push(c);
    }
    // 方法二:常规操作
    /* for (var i = 0; i < s.length; ++i) {
        str[i] = s[i];
    }*/
    var str2array = str.map(function (x) {
        return x - '0';
    });
    var result = str2array.reduce(function (x, y) {
        return 10 * x + y;
    });
    return result;
}
// 测试:
if (string2int('0') === 0 && string2int('12345') === 12345 && string2int('12300') === 12300) {
    if (string2int.toString().indexOf('parseInt') !== -1) {
        console.log('请勿使用parseInt()!');
    } else if (string2int.toString().indexOf('Number') !== -1) {
        console.log('请勿使用Number()!');
    } else {
        console.log('测试通过!');
    }
}
else {
    console.log('测试失败!');
}

练习请把用户输入的不规范的英文名字,变为首字母大写,其他小写的规范名字。输入:['adam', 'LISA', 'barT'],输出:['Adam', 'Lisa', 'Bart'],代码如下:

'use strict';

function normalize(arr) {
    var result = arr.map(function (s) {
        str = s[0].toUpperCase() + s.substring(1).toLowerCase();
        return str;
    });
    return result;
}
// 测试:
if (normalize(['adam', 'LISA', 'barT']).toString() === ['Adam', 'Lisa', 'Bart'].toString()) {
    console.log('测试通过!');
}
else {
    console.log('测试失败!');
}

注意:“由于map()接收的回调函数可以有3个参数:callback(currentValue, index, array),通常我们仅需要第一个参数,而忽略了传入的后面两个参数,在以下代码中parseInt(string, radix)没有忽略第二个参数,导致实际执行的函数分别是:parseInt(‘1’, 0); // 1, 按十进制转换、parseInt(‘2’, 1); // NaN, 没有一进制、parseInt(‘3’, 2); // NaN, 按二进制转换不允许出现3。”:

'use strict';

var arr = ['1', '2', '3'];
var r;
r = arr.map(parseInt);
console.log(r);

结果:
1,NaN,NaN

可以将parseInt函数换成Number函数(仅接受一个参数)解决问题。

18.“数组的filter()把传入的函数依次作用于每个元素,然后根据返回值是true还是false决定保留还是丢弃该元素”,filter函数和map函数一样,其回调函数可以接收多个参数,但一般只用第一个参数(代表数组元素)

19.练习用filter函数筛选出100以内的素数,因为任何一个数都不可能分解成两个大于其平方根的数的乘积,只能分解为一个大于或等于其平方根的质因子,另一个小于或等于其平方根的质因子,即一个合数一定有小于它平方根的质因子,因此只需要判断到一个数的平方根即可,代码如下:

'use strict';

function get_primes(arr) {
     var result = arr.filter(function (x) {
        if (x === 0 || x === 1) {
            return false;
        }
        if (x === 2) {
            return true;
        }
        for (var i = 2; i <= Math.sqrt(x); ++i) {
            if(x % i === 0) {
                return false;
            }
        }
        return true;
    });
    return result;
}
// 测试:
var
    x,
    r,
    arr = [];
for (x = 1; x < 100; x++) {
    arr.push(x);
}
r = get_primes(arr);
if (r.toString() === [2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97].toString()) {
    console.log('测试通过!');
} else {
    console.log('测试失败: ' + r.toString());
}

20.注意“Arraysort()方法默认把所有元素先转换为String再排序(字符串默认根据ASCII码进行排序)”,但sort函数也可以接收一个比较函数(有两个参数)来实现自定义的排序,sort方法直接改变数组。

21.“数组的every方法可以判断数组的所有元素是否满足测试条件;find方法用于查找符合条件的第一个元素,如果找到了,返回这个元素,否则,返回undefinedfindIndex()find()类似,也是查找符合条件的第一个元素,不同之处在于findIndex()会返回这个元素的索引,如果没有找到,返回-1forEach方法简化代码示例如下:”。

'use strict';

var arr = ['Apple', 'pear', 'orange'];
arr.forEach(console.log); // 依次打印数组每个元素

22.注意“返回闭包时,返回的函数不要引用任何循环变量,或者后续会发生变化的变量”,重点学习一定要引用循环变量的情况,示例代码:

function count() {
    var arr = [];
    for (var i=1; i<=3; i++) {
        arr.push((function (n) {
            return function () {
                return n * n;
            }
        })(i));
    }
    return arr;
}

var results = count();
var f1 = results[0];
var f2 = results[1];
var f3 = results[2];

f1(); // 1
f2(); // 4
f3(); // 9

创建一个匿名函数并立刻执行(重点学习),示例代码:

(function (x) {
    return x * x;
})(3); // 9

闭包还可以封装私有变量,“闭包就是携带状态的的函数,且其状态可以完全对外隐藏”;“闭包还可以把多参数的函数变成单参数的函数”。

23.“脑洞大开”部分一开始没咋看明白,翻了下评论,果然人才辈出,以下是大佬原回复(像俄罗斯套娃,手动狗头):

zero(f)(x) = x;
one(f)(x) = f(x);
two(f)(x) = one(f)(one(f)(x)) = one(f)(f(x)) = f(f(x));
three(f)(x) = two(f)(one(f)(x)) = two(f)(f(x)) = f(f(f(x)));
...
five(f)(x) = f(f(f(f(f(x)))));
// 尝试定义four,当然不止这一种
// 计算数字4 = 2 + 2
var four = add(two, two);

// 推理four
four(f)(x) = two(f)(two(f)(x)) = two(f)(f(f(x))) = one(f)(one(f)(f(f(x)))) = one(f)(f(f(f(x)))) = f(f(f(f(x))));

24.箭头函数相当于匿名函数(重点学习),箭头左边是参数,右边是返回的值,注意返回对象时需要加括号:

x => { foo: x } // 报错
x => ({ foo: x }) // 没问题

25.在箭头函数中this永远指向词法作用域,即外层调用者,注意“用call()或者apply()调用箭头函数时,无法对this进行绑定,即传入的第一个参数(对象)被忽略”。

26.练习使用箭头函数简化排序时传入的函数:

'use strict'
var arr = [10, 20, 1, 2];
// 方法一
arr.sort((x, y) => {
    if (x < y) return -1;
    if (x > y) return 1;
    return 0;
});

// 方法二
arr.sort((x, y) => {
    return x - y;
});

console.log(arr); // [1, 2, 10, 20]

27.“生成器generatorfunction*定义,除了return语句,还可以用yield返回多次”,可以理解其为可以返回多次的函数,有两种调用generator对象的方法(重点学习),(1)调用生成器的next方法,一直执行到return语句结束;(2)类似for (var x of f(3))循环迭代生成器对象f(3)时,可以理解x是生成器返回的多个返回值之一,但这种方法不会返回return语句定义的值,不管是undefined还是其他。generator有两个重要用处:(1)“可以实现需要用面向对象才能实现的功能”;(2)“把异步回调代码变成“同步”代码”,到ajax部分重点学习。

28.练习用generator生成一个自增的ID,有点没太明白,翻了下评论区的代码,理解了一点:yield语句只在生成器调用next方法时才会执行,并且存储当前状态(也就是自增的变量ID)、跳出函数,下一次调用next方法时会跟在上一次yield部分后继续执行,(但仍然是一脸蒙比,可能学到后面实践用的时候会明白一点?待学习)参考代码如下:

'use strict';
// 第一种
function* next_id() {
    var id = 1;
    while (true) {
        yield id++;
    }
    return;
}

// 第二种,x就是后面代码里的for循环自增的x
function* next_id() {
    while (true) yield x;
}

// 测试:
var
    x,
    pass = true,
    g = next_id();
for (x = 1; x < 100; x ++) {
    if (g.next().value !== x) {
        pass = false;
        console.log('测试失败!');
        break;
    }
}
if (pass) {
    console.log('测试通过!');
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值