函数是对象,是引用类型(传址类型) 对象、类中的函数称为方法
声明定义
new Function(name, body) 不推荐
**_desc_**对象创建函数_**params**_{ string } name 函数名称_**params**_{ string } body 函数体_**return**_{ function }
var fn = new Function("add", "return 1 + 1");var total = fn();console.log(total); // 2
function name(...args) {}
* 全局变量,具名函数默认存在 window 中
* 可能导致 函数提升(可以在函数声明前,调用该函数)
**_desc_**具名函数_**params**_{ string } name 函数名称_**params**_{ any } args 函数参数_**return**_{ function }
var total = add(1, 2); // 此时产生了 函数提升function add(a, b) {return a + b;}console.log(total); // 3
const name = function(...args) {} 👍
* 使用 var 声明时,函数会挂载到最顶端作用域中,即 window
* 使用 let / const 声明时,函数不会挂载到 window 上
**_desc_**匿名函数_**params**_{ string } name 函数名称_**params**_{ any } args 函数参数_**return**_{ function }
// var total = add(1, 2); // error:add is not a functionconst add = function(a, b) {return a + b;}const total = add(1, 2);console.log(total); // 3
参数类型
形式参数
函数声明时设置的参数,用于占位
* 行参数量 > 实参数量时,没有传入值的行参默认为 undefined
* 行参数量 < 实参数量时,多余的实参将被忽略且不会报错
const fn = function(a, b) {}; // a 和 b 为形式参数
实际参数
调用函数时实际传入的值
const fn = function(a, b) {}; // a 和 b 为形式参数fn(1, 2); // 1 和 2 为实际参数
默认参数
形式参数的默认值
* es5 通过 || 实现默认值
* es6 通过行参赋值实现默认值。默认参数一般放在最后方。只占位不覆盖默认值可传入 undefined
|| 与 ?? 的区别
* || 当左侧值转换成布尔类型为 false 时,返回右侧的值
* ?? 当左侧值为 null 或 undefined 时,返回右侧的值
const fn1 = function(a, b) {a = a || 100; // es5 默认值return a + b;};const fn2 = function(a, b = 100) { // es6 默认值return a + b;};
不定参数
数量不确定的行参 使用 …args 表示不定参数,args 是传入的所有参数组成的数组
const fn = function(...args) {console.log(args);}fn(1, 2, 3, 4, 5); // [1, 2, 3, 4, 5]
arguments
函数内置的属性,效果等同于不定参数 是一个伪数组
const fn = function() {console.log(arguments);};fn(1, 2, 3, 4, 5); // Arguments(5) [1, 2, 3, 4, 5]
函数类型
箭头函数
箭头函数是函数声明的简写方式 箭头函数是匿名函数
* 普通函数和箭头函数内部的 this 不同
* 普通函数内部拥有属于自己的 this。this 指向调用者,即谁调用指向谁。默认为 window,严格模式下为 undefined
* 构造函数是普通函数中的例外,其内部 this 指向实例对象
* 箭头函数内部没有属于自己的 this。 其内部的 this 实际上是从外部获取的,内部的 this 始终与上一层的 this 保持一致。箭头函数会一直向上寻找 this,直到找到最近的一个 this
* 普通函数内置有 arguments 属性。箭头函数没该内置属性
* 普通函数具有原型对象。箭头函数没有原型对象
* 普通函数可以作为构造函数,使用 new 关键字构造实例。箭头函数不能作为构造函数,使用 new 关键字时会抛出异常。
* 由于箭头函数没有原型对象,原型链上没有 constructor 构造方法,所以无法作为构造函数使用
/*** this 指向说明*/const fn1 = function() {console.log(this);};const arrowFn1 = () => {console.log(this);};fn1(); // window,严格模式为 undefinedfunction Human(name) {this.name = name;this.eat = function() {console.log(this); // 该函数是普通函数,内部 this 指向调用该方法的 this,即 Human 实例},this.run = function() {fn1(); // 等同于 window.fn1()},this.sing = () => {arrowFn1(); // 该函数是箭头函数,内部 this 与上一层函数内部的 this 保持一致。上一层也是箭头函数,则继续向上找,直到最顶层函数。顶层函数是 Human,为构造函数,构造函数 this 指向其实例,故该箭头函数的 this 也指向实例},}var human = new Human("张三");human.eat(); // { name: "张三" }human.run(); // window,严格模式为 undefinedhuman.sing(); // { name: "张三" }/*** 普通函数内置有 arguments 属性。箭头函数没该内置属性*/const fn2 = function() {console.log(arguments); // [1, 2, 3]};const arrowFn2 = () => {console.log(arguments); // error: arguments is not defined};fn2(1, 2, 3);arrowFn2(1, 2, 3);/*** 普通函数具有原型对象。箭头函数没有原型对象*/const fn3 = function() {};const arrowFn3 = () => {};console.log(fn3.prototype); // { constructor: f() }console.log(arrowFn3.prototype); // undefined/*** 普通函数可以作为构造函数,使用 new 操作符构造实例。箭头函数不能作为构造函数,使用 new 操作符时会抛出异常*/const Fn4 = function() {};const ArrowFn4 = () => {};const fn4 = new Fn4();console.log(fn4); // objectconst arrowFn4 = new ArrowFn4(); // error: ArrowFn4 is not constructor
递归函数
递归指函数内部调用自身
* 主要用于数量不确定的循环操作
* 必须明确退出循环的条件,避免发生死循环
var width = 5;function triangle(width) {if (width <= 0) return;console.log("*".repeat(width));triangle(--width);}triangle(5);***************
回调函数
A callback is a function that is passed as an argument to another function and is executed after its parent function has completed. 回调是一个函数,是另一个函数的参数,是在该函数完成后执行执行的
* 回调与同步、异步没有直接关系。但使用场景主要是异步回调
const getRunnerTime = function(callback) {var count = 0;var time = Date.now();var interval = setInterval(() => {count = Math.random();if (count > 0.5) {clearInterval(interval);callback(Date.now() - time);}}, 500);}getRunnerTime(function(time) {console.log(time);});
立即执行函数
立刻执行,无需调用
**(function(...args) {})(...args)**
_**params**_{ any } args 传入的参数_**return**_{ void }
(function(a) {console.log(a); // 100})(100)
构造函数
使用 new 关键字调用的函数,称为构造函数 构造函数首字母一般大写
* 构造函数中的 this 指向其实例对象
* 构造函数的返回值始终是 this,人为修改返回值无效
* 构造函数不能使用箭头函数,箭头函数内部无法创建自己的 this
* 可以判断构造函数内部的 this 是否在其原型链上,实现不使用 new 关键字调用构造函数
/*** 不使用 new 关键字实现调用构造函数*/function Male(name) {this.name = name;}function Female(name) {if (!(this instanceof Female)) {return new Female(name);}this.name = name;}var male1 = new Male("zhangsan");var male2 = Male("lisi");var female1 = new Female("wangwu");var female2 = Female("maliu");console.log(male1); // { name: "zhangsan" }console.log(male2); // undefinedconsole.log(window.name); // "lisi"console.log(female1); // { name: "wangwu" }console.log(female2); // { name: "maliu" }
标签函数 了解,很少见
函数解析模版字符串
* 调用标签函数时,不需要使用 (),直接方法名 + 模版字符串即可
* 标签函数本质是将模版字符串中的 ${} 符号作为分割符,将模版字符串分割为常量和变量数组后,再传入函数
**function name(constant, ...variable) {}**
_**params**_{ string } name 函数名称_**params**_{ array } constant 模版字符串中常量组成的数组_**params**_{ array } variable 模版字符串中变量组成的数组_**return**_{ any }
const fn = function(constant, ...variable) {console.log(constant);console.log(variable);};fn `Hello${1}World${2}`; // ["Hello", "World"], [1, 2]
this
this 指函数的上下文环境 this 是动态可变的 this 在函数声明时是无法确定的,只有在函数执行时才能确定函数的指向
* 普通函数内部拥有属于自己的 this。this 指向调用者,即谁调用指向谁。默认为 window,严格模式下为 undefined
* 构造函数是普通函数中的例外,其内部 this 指向实例对象
* 箭头函数内部没有属于自己的 this。 其内部的 this 实际上是从外部获取的,内部的 this 始终与上一层的 this 保持一致。
PS:代码示例见 函数类型 - 箭头函数**
动态设置 this
即对象方法借用
fn.apply(ctx, [...args])
* 立即执行
* 使用数组传参
* 本质:对象 ctx 借用 fn 方法
_**params**_{ function } fn 被调用的方法_**params**_{ object } ctx 借用方法的对象_**params**_{ any } args 参数_**return**_{ void }
function Human() {this.run = function(a, b) {console.log(`Human run~${a + b}`);}}function fn () {};var human = new Human();human.run.apply(fn, [1, 2]); // "Human run~3"
fn.call(ctx, ...args)
* 立即执行
* 使用不定参数传参
* 与 apply 作用完全相同,唯一不同是传参格式不同
_**params**_{ function } fn 被调用的方法_**params**_{ object } ctx 借用方法的对象_**params**_{ any } args 参数_**return**_{ void }
function Human() {this.run = function(a, b) {console.log(`Human run~${a + b}`);}}function fn () {};var human = new Human();human.run.call(fn, 1, 2); // "Human run~3"
fn.bind(ctx, ...args)
* 不会立即执行
* bind 的本质是复制函数行为,并返回新的函数
_**params**_{ function } fn 被调用的方法_**params**_{ object } ctx 借用方法的对象_**params**_{ any } args 参数_**return**_{ function }
function Human() {this.run = function(a, b) {console.log(`Human run~${a + b}`);}}function fn () {};var human = new Human();var bindFn = human.run.bind(fn);console.log(bindFn === fn); // false, bindFn 是复制出的具有 human.run() 方法体的新函数,并非原 fn 函数bindFn(1, 2); // "Human run~3"
**bind 传参**
* bind 绑定时已传递全部参数,调用时再传参,此时传递的参数无效
* bind 绑定时不传递参数,调用时再传参,此时传递的参数有效
* bind 绑定时传递部分参数,调用时再传参,此时传递的参数会在绑定时传递的参数后面进行补位,多出的参数自动被删除
function sum(a, b, c) {console.log(a + b + c);}function baseFn() {}var fn1 = sum.bind(baseFn, 1, 2, 3);var fn2 = sum.bind(baseFn);var fn3 = sum.bind(baseFn, 1);fn1(4, 5); // 6。绑定时已传递全部参数,此时参数无效fn2(1, 2, 3); // 6。绑定时未传递参数,此时参数有效fn3(4, 5, 6); // 10。绑定时传递部分参数,此时实参4和5有效,6已超出参数个数,自动删除
函数命名冲突
在同一个作用域中,相同名称的函数名会产出冲突(具名函数/var 会覆盖已有函数,let/const 会抛出异常) 解决思路:使相同名称的函数处于不同的局部作用域中
立即执行函数
(function(window) {function add() {console.log("fn1.add");}window.fn1 = {add}})(window);(function(window) {function add() {console.log("fn2.add");}window.fn2 = {add}})(window);fn1.add(); // "fn1.add"fn2.add(); // "fn2.add"
块状作用域
{function add() {console.log("fn1.add");}window.fn1 = {add}}{function add() {console.log("fn2.add");}window.fn2 = {add}}fn1.add(); // "fn1.add"fn2.add(); // "fn2.add"
Proxy 代理函数 ❓
* 在调用函数前添加一层拦截,可以对调用操作进行统一的处理。类似拦截器的作用
new Proxy(target, hander)
_**params**_{ object } target 使用 Proxy 代理的目标对象_**params**_{ object } hander 定义代理行为的对象_**return**_{ object } 代理对象实例
function add(a, b) {console.log(a + b);}const proxy = new Proxy(add, {apply(fn, ctx, args) {for (let key in args) {args[key] = args[key] * 2;}fn.apply(ctx, args);}})add(1, 2); // 3proxy.apply(window, [1, 2]); // 6
