- 作用:主要是用于减少重复代码
创建(定义、声明)函数
function 函数名(){
//函数体
}
- 函数体的代码不会直接运行,必须手动调用。
调用函数
//运行函数体
函数名();
//例子
function test(){
console.log('这是一个函数的输出');
};
test()
// function test(){}其中 function 是函数关键字,test 是函数名,必须有(){},参数可有可
// 没有,参数是写在()括号里面的。
// 如果写成 function test(a,b){},相当于隐式的在函数里面 var a,var b 申明了两个变量,()括号里面不能直接写 var
函数提升
- 通过字面量声明的函数,会提升到脚本块的顶部。
- 通过字面量声明的函数,会成为全局对象的属性
其他特点
通过typeof 函数名,得到的结果是”function”
函数内部声明的变量:
- 如果不使用var声明,和全局变量一致,表示给全局对象添加属性
- 如果使用var声明,变量提升到所在函数的顶部,函数外部不可以使用该变量
参数
- 参数表示函数运行的未知条件,需要调用者告知的数据
- (1)形参(形式参数):指的是 function sum(a,b){}括号里面的 a 和 b
- (2)实参(实际参数):指的是 sum(1,2);里面的 1,2
- 形参可以比实参多,实参也可以比形参多
- 如果实参没有传递,则对应的形参为undefined
// 参数的有效返回在函数体中
function 函数名(形参1, 形参2, ...){
}
函数名(实参)
函数表达式
(1)命名函数表达式
var test = function abc(){
console.log('a');
}
上面这个函数的函数名 name 是 abc
在控制台 console 直接输出 test 就会出现→
在控制台 console 直接输出 abc 会报错,表达式就会忽略他的名字 abc。
在上面例子中,fuction abc(){console.log(‘a’)}这一部分叫表达式,是会忽略
abc 这个地方的名字,会变成匿名函数表达式,不如直接写成匿名函数
函数是一个引用类型,将其赋值给某个变量时,变量中保存的是函数的地址
(2)匿名函数表达式(常用,一般说的函数表达式就是匿名函数表达式)
function test(){
console.log('abc')
};
test();
返回值
函数运行后,得到的结果,调用函数时,调用表达式的值就是函数的返回值
return 会直接结束整个函数的运行
return 后面如果不跟任何数据,返回undefined
如果函数中没有书写return,则该函数会在末尾自动return undefined。
function test(a,b){
return a + b
};
cosole.log(test(2,3));
作用域和闭包
作用域
- 定义:变量(变量作用于又称上下文)和函数生效(能被访问)的区域
全局、局部变量 - 全局作用域:直接在脚本中书写的代码,在全局作用域中声明的变量,会被提升到脚本块的顶部,并且会成为全局对象的属性。
- 函数作用域:在函数作用域中声明的变量,会被提升到函数的顶部,并且不会成为全局对象的属性.
- 函数外面不能用函数里面的。里面的可以访问外面的,外面的不能访问里面的,彼此独立的区间不能相互访问
- 因此,函数中声明的变量不会导致全局对象的污染
var a = 1;
function sun(){
var b = 1;
console.log(a + b);
};
sun();
立即执行函数
- 定义:此类函数没有声明,在一次执行过后即释放(被销毁)。适合做初始化工作。针对初始化功能的函数:只想让它执行一次的函数
(function() {
function helper1() {
}
function helper2() {
}
})();
// 例
(function (){ //写成(function abc(){}())也调用不到
var a = 123;
var b = 234;
console.log(a + b);
}())
(function (a, b, c){
console.log(a + b + c * 2);
}(1, 2, 3))
// 这一行里面的(1,2,3)是实参
var num = (function (a, b, c){
var d = a + b + c * 2 – 2;
return d;
}(1, 2, 3))
// 答案 num = 7
闭包
- 闭包(closure),是一种现象,内部函数,可以使用外部函数环境中的变量。
- 当内部函数被保存到外部时,将会生成闭包。闭包会导致原有作用域链不释放,造成内存泄露
- 但凡是内部的函数被保存到外部,一定生成闭包
var g = "Abc";
function A() {
var a = 234;
console.log(a, g);
function B() {
var b = 567;
console.log(b, a, g);
}
B();
}
A();
this关键字
this无法赋值
- 在全局作用域中,this关键字固定指向全局对象。
- 在函数作用域中,取决于函数是如何被调用的
- 函数直接调用,this指向全局对象
- 通过一个对象的属性调用,格式为
对象.属性()
或对象["属性"]()
,this指向对象
var obj = {
firstName: "小",
lastName: "明",
age: 18,
sayHello: function() {
console.log(`我叫${this.firstName}${this.lastName},今年${this.age}岁了`);
}
};
obj.sayHello();
构造函数
构造函数专门用于创建对象
对象中的属性,如果是一个函数,也称该属性为对象的方法
new 函数名(参数);
function User(name, age, gender) {
this.name = name;
this.age = age;
this.gender = gender;
this.sayHello = function() {
console.log(`我叫${this.name},年龄${this.age}岁,性别${this.gender}`);
}
}
var u1 = new User("张三", 18, "男");
- 如果使用上面的格式创建对象,则该函数叫做构造函数。
- 函数名使用大驼峰命名法
- 构造函数内部,会自动创建一个新对象,this指向新创建的对象,并且自动返回新对象
- 构造函数中如果出现返回值,如果返回的是原始类型,则直接忽略;如果返回的是引用类型,则使用返回的结果
- 所有的对象,最终都是通过构造函数创建的
arguments
- 在函数中使用,获取该函数调用时,传递的所有参数
- 是一个类数组(也称为伪数组:没有通过Array构造函数创建的类似于数组结构的对象),伪数组会缺少大量的数组实例方法
- arguments数组中的值,会与对应的形参映射
new.target
- 该表达式在函数中使用,返回的是当前的构造函数,但是,如果该函数不是通过new调用的,则返回undefined
- 通常用于判断某个函数是否是通过new在调用。
函数的本质
- 函数的本质就是对象。
- 构造函数又称之为构造器
- 所有的对象都是通过关键字new出来的,
new 构造函数()
- 所有的函数,都是通过
new Function
创建。 - 由于函数本身就是对象,因此函数中,可以拥有各种属性。
包装类
- JS为了增强原始类型的功能,为boolean、string、number分别创建了一个构造函数:
- new Boolean()
- new String();
静态成员
- fromCharCode:通过unicode编码创建字符串
实例成员
- length:字符串长度
字符串是一个伪数组
- charAt:得到指定位置的字符
- charCodeAt: charCodeAt() 方法返回 0 到 65535 之间的整数,表示给定索引处的 UTF-16 代码单元
- concat: 字符串合并
- includes: 查找字符中是否含有某个元素
- endsWith: 判断是否中是否以某个元素结尾
- startsWith: 判断是否中是否以某个元素开头
- indexOf:方法返回调用它的 String 对象中第一次出现的指定值的索引,从 fromIndex 处进行搜索。如果未找到该值,则返回 -1
- lastIndexOf:lastIndexOf() 方法返回调用String 对象的指定值最后一次出现的索引,在一个字符串中的指定位置 fromIndex处从后向前搜索。如果没找到这个特定值则返回-1
- padStart:字符串填充由开头开始(重复填充);可传两个参数,一个为填充的目标长度,第二个为填充字符串
- padEnd: 字符串填充由末尾开始(重复填充);可传两个参数,一个为填充的目标长度,第二个为填充字符串
- repeat: repeat() 构造并返回一个新字符串,该字符串包含被连接在一起的指定数量的字符串的副本
- slice:从某个位置取到某个位置;位置可以是负数;
- substr: 从某个位置开始取,取指定的长度;位置可以是负数;
- substring:从某个位置取到某个位置;不可以是负数;参数位置是可调换的。
- toLowerCase 将字符串里的大写字母转为小写
- toUpperCase 将字符串里的小写字母转为大写
- split:分割字符串
- new Number()
静态成员
- isNaN
- isFinite
- isInteger:判断一个数据是否是整数
- parseFloat: 将一个数据转换为小数
- parseInt:将以一个数据转换为整数,直接舍去小数部分
- parseInt、parseFloat要求参数是一个字符串,如果不是字符串,则会先转换为字符串。
- 从字符串开始位置进行查找,找到第一个有效的数字进行转换,如果没有找到,则返回NaN,左右空白字符会忽略;parseInt,可以传入第二个参数,表示将给定的字符串,识别为多少进制。
实例成员
- toFixed方法:会有四舍五入
- toPrecision:以指定的精度返回一个数字字符串
- 如果语法上,将原始类型当作对象使用时(一般是在使用属性时),JS会自动在该位置利用对应的构造函数,创建对象来访问原始类型的属性。
类:在JS中,可以认为,类就是构造函数
成员属性(方法)、实例属性(方法):表示该属性是通过构造函数创建的对象调用的。
静态属性(方法)、类属性(方法):表示该属性是通过构造函数本身调用的。
var num = new Nunber(123); //数字类型对象
var str = new String(‘abcd’); //字符串类型对象
var bol = new Boolean(‘true’); //布尔类型对象
递归
- 函数直接或间接调用自身,避免无限递归,无限递归会导致执行栈溢出。
对比死循环
死循环不会报错,也不会导致栈溢出
无限递归会导致栈溢出递归要找出口,没有出口的递归就是无限递归
//递归的例子 斐波拉契数列
function fb(n){
if(n === 1 && n === 2){
return 1;
}
return fb(n - 1) + fb(n - 2);
}
fb(10); //55
//阶乘
function jc(n,total = 1){
if(n === 0 || n === 1){
return total
};
return jc(n - 1, n * total)
};
jc(5); //120
//汉诺塔
function hannuo(no1, no2, no3, n) {
if (n === 1) {
console.log(`${no1}->${no3}`);
} else {
hannuo(no1, no3, no2, n - 1);
console.log(`${no1}->${no3}`);
hannuo(no2, no1, no3, n - 1);
}
}
hannuo('A', 'B', 'C', 5);
执行栈
任何代码的执行都必须有一个执行环境,执行环境为代码的执行提供支持
执行环境是放到执行栈中的。
每个函数的调用,都需要创建一个函数的执行环境,函数调用结束,执行环境销毁。
执行栈有相对固定的大小,如果执行环境太多,执行栈无法容纳,会报错
function A(){
console.log("A begin");
B();
console.log("A over");
}
function B(){
console.log("B begin");
C();
console.log("B over");
}
function C(){
console.log("C begin");
console.log("C over");
}
console.log("global begin")
A();
console.log("global over");
尾递归
如果一个函数最后一条语句是调用函数,并且调用函数不是表达式的一部分,则该语句称为尾调用,如果尾调用是调用自身函数,则称为尾递归。
某些语言或执行环境会对尾调用进行优化,它们会理解销毁当前函数,避免执行栈空间被占用。
在浏览器执行环境中,尾调用没有优化。但在nodejs环境中有优化。
函数的实例成员
- length属性,得到函数形参数量
- apply方法:调用函数,同时指定函数中的this指向,参数以数组传递
- call方法:调用函数,同时指定函数中的this指向,参数以列表传递
- bind方法:得到一个新函数,该函数中的this始终指向指定的值。
通常,可以利用apply、call方法,将某个伪数组转换伪真数组。
//改变this 指向
function sayHello(a, b) {
console.log(this.name, this.age);
console.log(a, b);
}
var user1 = {
name: "asfd",
age: 123
};
var user2 = {
name: "546345",
age: 11
};
var newFunc = sayHello.bind(user1, 1, 2);
newFunc();
// sayHello.apply(user1, [1, 2]);
// sayHello.call(user2, 1, 2);
function test() {
console.log(arguments);
//将arguments转换为真数组
var newArr = [].slice.call(arguments)
console.log(newArr);
}
test(23, 5, 6, 2, 233, 5, 6, 7);