1. 函数对象的属性
// 定义对象并给对象添加属性和方法 var obj = {}; obj.name = "zgc"; obj.bar = function () {}; // console.log(obj); // 注意: 在 JS 中函数也属于对象的一种,所以可以给函数添加属性和方法 function foo(a, b, c) { console.log(a, b, c); } var bar = function (m, n, ...args) {}; function test(x, y = 0) {} // 1. 自定义属性 foo.message = "Hello"; console.log("访问foo的属性", foo.message); // Hello // 2. 默认函数对象中已经有两个自己的属性了 // (1) name属性 (了解) console.log(foo.name, bar.name); // foo bar // 将多个函数放入数组中,可以以name区分 var fns = [foo, bar]; for (var fn of fns) { console.log(fn.name); } // (2) length属性, 参数个数的长度 // 这个长度是指本来因该获取的参数的个数, 即形参的个数(因为函数在调用时参数可能多传或少传, 所以实参的个数未必等于形参的个数) // 剩余参数是不会算在length里面的, 赋默认值的参数也不会算在length里面 console.log(foo.length, bar.length, test.length); // 3,2,1 foo(1, 2); // 1 2 undefined foo(1, 2, 3, 4); // 1 2 3
2. 函数中arguments的使用
// 1. arguments是一个类数组对象, 并不是真正的数组 // 2. arguments包含所有的参数, 拥有length属性, 可以通过index获取参数 // 3. 类数组不能够调用数组的内置方法, 如map, filter等 function foo(m, n) { // console.log(m, n); // console.log(arguments); for (var i of arguments) { console.log(i); // 10 20 30 40 } for (var i in arguments) { console.log(i); // 0 1 2 3 } } foo(10, 20, 30, 40); // 4. 类数组转数组 // (1) 遍历 arguments每个属性放入一个新数组 function bar(a, b, c) { const newArr1 = []; for (var i of arguments) { newArr1.push(i); } console.log(newArr1); // [2, 4, 6] } bar(2, 4, 6); // (2) ...扩展运算符 function bar(a, b, c) { const newArr1 = [...arguments]; console.log(newArr1); // [2, 4, 8] } bar(2, 4, 8); // (3) Array.from(arguments) function bar(a, b, c) { const newArr1 = Array.from(arguments); console.log(newArr1); // [1, 3, 5] } bar(1, 3, 5); // (4) slice function bar(a, b, c) { // 注意, slice是实例方法, 不能通过Array之间调用 // 能通过Array之间调用的都是类方法 // console.log(typeof Array); // function const newArr1 = [].slice.apply(arguments); const newArr2 = Array.prototype.slice.apply(arguments); console.log(newArr1, newArr2); // [1, 3, 9] [1, 3, 9] } bar(1, 3, 9); // 4. 箭头函数没有 arguments const baz = () => { // console.log("箭头函数", arguments); // arguments is not defined }; baz(); function test(a, b) { const baz = () => { console.log("箭头函数", arguments); // 这里的arguments是test的arguments // 当箭头函数本身找不到arguments时, 会去它的上层作用域下寻找 }; baz(); } test(1, 2);
3. 函数的剩余参数
// 剩余参数(亦称 rest 参数) 用于获取函数的多余参数,这样就不要使用 arguments 对象了 // 如果函数的最后一个形参是...为前缀的,且搭配的变量是一个数组,该变量将多余的参数放入数组中 // 注意1: 和参数对象不同, 剩余参数只包含那些没有对应形参的实参, 而且是真实的数组,可直接使用所有数组方法 // 注意2: 函数剩余参数之后不能再有其他参数(即 只能是最后一个参数),否则会报错 // 注意3: 函数的 length 属性,不包括函数剩余参数 function add(x, y, ...values) { let sum = 0; console.log(x, y); // 2,4 console.log(values); // [6, 8] for (var val of values) { sum += val; } console.log(sum); // 14 } add(2, 4, 6, 8); console.log(add.length); // 2
4. 纯函数
- 函数式编程: 通常我们对函数作为
头等公民
的编程方式, 称之为函数式编程
- 函数可以赋值给变量(函数表达式写法)
- 函数可以在变量之间来回传递
- 函数可以作为另一个函数的参数
- 函数作为另一个函数的返回值
- 函数存储在另一个数据结构中
- 函数式编程中有一个非常重要的概念叫做纯函数, JS符合函数式编程的范式 所以也有纯函数的概念
- 纯函数: 相同的输入,总是会的到相同的输出,并且在执行过程中没有任何副作用。
- 副作用: 指的是执行一个函数时,除了返回函数值之外,还对
调用函数产生了附加的影响
- 网络请求
- 输出数据
console.log()打印数据
- 修改了 全局变量、参数、外部存储
DOM
查询、操作Math.random
- 获取当前时间
// 纯函数,符合函数在相同的输入值时,需产生相同的输出 function sum(a, b) { return a + b; } // 不是一个纯函数,因为在我们程序执行的过程中,变量num很可能会发生改变 let num = 1; function add(x) { return x + num; } add(1); // 不是一个纯函数 var address = "北京"; function printInfo(info) { console.log(info.name); // 有输出数据 info.age = 18; // 对参数进行修改 address = info.address; // 修改全局变量 } var obj = { name: "zgc", address: "上海", }; printInfo(obj); // 在JavaScript中内置的API也存在有纯函数,我们拿Array对象中的方法来说 // filter 过滤数组中的元素,它不对原数组进行操作是一个纯函数 var names = ["张三", "李四", "王五", "赵六"]; var newNames1 = names.filter((n) => n !== "张三"); console.log(newNames1); // [ '李四', '王五', '赵六' ] console.log(names); // [ '张三', '李四', '王五', '赵六' ] // splice 截取数组的时候会对原数组进行操作,所以不是一个纯函数。 var newNames2 = names.splice(2); console.log(newNames2); // [ '王五', '赵六' ] console.log(names); // [ '张三', '李四' ]
5. 函数的柯里化(Currying)
- 柯里化是一种关于函数的高阶技术。它不仅被用于 JavaScript,还被用于其他编程语言。
- 柯里化是一种将使用多个参数的一个函数转换成一系列使用一个参数的函数的技术。
- 柯里化是一种函数的转换,它是指将一个函数从可调用的 f(a, b, c) 转换为可调用的 f(a)(b)(c)。
- 柯里化不会调用函数, 它只是对函数进行转换。
// 普通函数 function foo(x, y, z) { console.log(x + y + z); } foo(10, 20, 30); // 60 // 柯里化函数 function bar(x) { return function (y) { return function (z) { console.log(x + y + z); }; }; } bar(1)(2)(3); // 6 // 柯里化箭头函数写法 // const baz = (x) => { // return (y) => { // return (z) => { // console.log(x + y + z); // }; // }; // }; var baz = (x) => (y) => (z) => console.log(x + y + z); baz(1)(3)(5); // 9 // 封装一个函数: 自动生成柯里化函数 function currying(fn) { function curryFn(...args) { if (args.length >= fn.length) { // return fn(...args); return fn.apply(this, args); } else { return function (...newArgs) { // return curryFn(...args.concat(newArgs)); return curryFn.apply(this, args.concat(newArgs)); }; } } return curryFn; } const curryFn1 = currying(foo); curryFn1(10, 20)(30); // 60 curryFn1(10)(20)(30); // 60 curryFn1(10).call("this", 20)(30); // 60
6. 组合函数
组合函数是在JS开发过程中一种对函数的使用技巧, 模式
- 函数组合是指将多个函数按顺序执行,前一个函数的返回值作为下一个函数的参数,最终返回结果。
- 比如我们现在需要对某一个数据进行函数的调用,执行两个函数fn1和fn2,这两个函数是依次进行的;
那么如果每次我们都需要进行两个函数的调用,操作上就会显得很重复;
那么是否可以将这两个函数组合起来,自动依次调用呢?
这个过程就是对函数的组合,我们称之为 组合函数(Compose Function)
。
// 要求: 需要将N个数字分别进行调用double方法乘以2,再调用square方法平方。 var count = 10; // 普通情况下: function double(num) { return num * 2; } function square(num) { return num * num; } function add(num) { return num + 10; } var result = square(double(count)); console.log("普通函数", result); // 组合函数情况下: function getNum(num) { return square(double(count)); } var result = getNum(count); console.log("组合函数", result); // 封装通用性组合函数: function getComposeFn(...fns) { // 边界判断: // if (fns.length <= 0) return; // for (var fn of fns) { // if (typeof fn !== "function") throw new Error("传入的参数必须为函数"); // } return function (...args) { console.log(args); var result = fns[0].apply(this, args); for (var i = 1; i < fns.length; i++) { result = fns[i].apply(this, [result]); } return result; }; } var composeFn = getComposeFn(double, square, add); const res = composeFn(10); console.log("res", res); // 410
7. with & eval(几乎不用)
// 1. with: 扩展一个语句的作用域链 // 不建议使用with语句,因为它可能是混淆错误和兼容性问题的根源 // with (expression) { // statement; // } // expression: 将给定的表达式添加到在评估语句时使用的作用域链上。表达式周围的括号是必需的。 // statement: 任何语句。要执行多个语句,请使用一个块语句 ({ ... }) 对这些语句进行分组。 var obj = { userName: "zgc", userAge: 18, }; // console.log(userName); // userName is not defined // console.log(userAge); // userAge is not defined with (obj) { console.log(userName); // zgc console.log(userAge); // 18 } // 2. eval: 允许执行一个代码字符串 // eval是一个特殊的函数, 它可以将传入的字符串当作JS语句执行 // 调用 eval(code) 会运行代码字符串,并返回最后一条语句的结果 var x = 10; var y = 20; var a = eval("x * y;") var b = eval("2 + 2;") var c = eval("x + 17; console.log(1111);") // 1111 var res = a + b; console.log(res, a, b, c); // 204 200 4 undefined
8. 严格模式
JavaScript 严格模式(strict mode)即在严格的条件下运行。
使用 "use strict" 指令:
- "use strict" 指令在 JavaScript 1.8.5 (ECMAScript5) 中新增。
- 它不是一条语句,但是是一个字面量表达式,在 JavaScript 旧版本中会被忽略。
- "use strict" 的目的是指定代码在严格条件下执行。
- 严格模式下你不能使用未声明的变量。
为什么使用严格模式:
- 明确禁止一些不合理、不严谨的语法,减少 JavaScript 语言的一些怪异行为。
- 增加更多报错的场合,消除代码运行的一些不安全之处,保证代码运行的安全。
- 提高编译器效率,增加运行速度。
- 为未来新版本的 JavaScript 语法做好铺垫。
"严格模式"体现了Javascript更合理、更安全、更严谨的发展方向,包括IE 10在内的主流浏览器,都已经支持它,许多大项目已经开始全面拥抱它。 另一方面,同样的代码,在"严格模式"中,可能会有不一样的运行结果;一些在"正常模式"下可以运行的语句,在"严格模式"下将不能运行。掌握这些内容,有助于更细致深入地理解Javascript,让你变成一个更好的程序员。
// 1. 在js文件下给整个文件开启"严格模式": "use strict"; // 2. 给某个函数开启"严格模式": function foo() { "use strict"; } // 注意: 严格模式要在文件或者函数的开头使用"use strict";来开启 // 3. 如 class/module 默认是在严格模式运行的 // 4. 常见的严格模式限制 // (1) 无法意外创建全局变量 // 在正常模式中,如果一个变量没有声明就赋值,默认是全局变量。严格模式禁止这种用法,全局变量必须显式声明。 // v = 1; // 报错,v未声明 // for (i = 0; i < 2; i++) { // // 报错,i未声明 // } // (2) 禁止this关键字指向全局对象 // 正常模式下,函数内部的this可能会指向全局对象,严格模式禁止这种用法,避免无意间创造全局变量。 // 正常模式 function f() { console.log(this === window); } f(); // true // 严格模式 function f() { "use strict"; console.log(this === undefined); } f(); // true // (3) 对象不能有重名的属性 // 正常模式下,如果对象有多个重名属性,最后赋值的那个属性会覆盖前面的值。严格模式下,这属于语法错误。 var o = { p: 1, p: 2, }; // 语法错误 // (4) 函数不能有重名的参数 // 正常模式下,如果函数有多个重名的参数,可以用arguments[i]读取。严格模式下,这属于语法错误。 function f(a, a, b) { // 语法错误 }