目录
0.概念
一个完整的函数包括关键字、函数名、形式参数、函数体、返回值(声明)等
1.使用函数 = 声明函数 + 调用函数
使用函数 -----> 函数名() ----->函数不调用, 自己不执行
2.形参与实参
- 形参:声明函数时写在函数名右边小括号()里面的叫做形参(形式上的参数)等同于声明变量
- 实参:调用函数时写在函数名右边小括号()里面的叫做实参(实际上的参数)等同于为变量赋值
3.语法及其注意点
function 函数名(形参1,形参2,形参3...){ //声明函数,默认值为undefined,形参为了接收实参
//函数体
}
函数名(实参1,实参2,实参3...) //调用实参
//示例
function sayHi (a,b,c){
console.log(a+b+c)
}
sayHi('前端','后端','c语言') //形参=实参---->正常输出结果
sayHi('我是实参1','我是实参2') //实参 < 形参--->输出的结果和形参的个数相等,没有传入实参的形参为默认的undefined
sayHi('我是实参1','我是实参2','前端','后端','c语言') //实参 > 形参 ---->只返回和形参相同个数的实参被调用的结果
3.返回值(return)
function 函数名(){ //函数只是实现某种功能,最终结果需要返回给函数的调用者
return 需要返回的结果
} // 只要遇到return就把后面的结果返回给函数的调用者
- 在实际开发中,经常用一个变量来接收函数的返回结果
- return用来终止函数,return只返回一个值,且return后的语句不执行,若函数没有return则会返回undefined,有则返回return后的结果。
- return可以用来退出循环,函数内部只能出现一次return,在函数体内使用return能够将内部的执行结果交给函数外部使用。
4.动态参数与剩余参数
arguments的使用(函数内置对象,得到伪数组)
当不确定传递几个参数的时候用arguments获取 ,arguments是当前函数内置好的一个对象,arguments对象中存储了传递的所有实参
function fn(){
console.log(arguments) // 在控制台打印输出arguments
}
fn(1,2,3)
由返回结果截图可知,arguments返回的结果是以一个伪数组(有一种方法可以使得伪数组转换成真数组--->Array.from())的形式,伪数组不具有真数组的pop()和push()方法。
...other剩余参数(得到真数组)
- 剩余参数允许将一个不定量的参数表示为一个数组,函数参数使用,得到真数组
- 语法符号...,置于最末函数形参之前,用于获得剩余参数
展开运算符...
将一个数组展开,不会修改原数组,数组中使用
function fn(...other) {
console.log(other) // 在控制台打印输出剩余参数(真数组的形式)
console.log(...other) // 在控制台打印输出展开后的other数组
}
fn(1, 2, 3)
fn('a','b',1)
5.作用域(局部、全局、作用域链)
全局作用域:
作用于所有代码执行的环境<script>标签内部或者是一个js文件,处于全局作用域的变量称之为全局变量(函数内部未声明直接使用的变量也是全局变量,但是不支持这么做)
局部作用域:
作用于函数内的代码环境,成为局部作用域或者函数作用域,处于局部作用域的变量称之为局部变量(函数内部的形参也可以看作局部变量)
函数作用域:
在函数内部声明的变量只能在函数内部被访问,外部无法访问,函数的参数也是函数内部的局部变量, 不同函数内部声明的变量无法互相访问,函数执行完成后,函数内部的变量实际被清空
块作用域(es6新增):
在JS中使用了{ }包括的代码块,代码块内部声明的变量外部可能无法访问,let声明的变量才会产生块作用域,而var不会产生块作用域,但是var会有变量提升
变量提升
- 允许在声明之前即被访问变量的值为undefined。
- 而let和const定义的变量在未声明之前即被范围会报语法错误,因此他们不存在变量提升。
- 变量提升出现在同一作用域内,优先声明再访问变量。
- 推荐使用let和const声明变量,const多用于后期不会更改值的变量。
- 提到变量提升那就顺便讲讲函数提升
函数提升:
- 允许函数在声明之前就可以被调用,函数提升能够使得函数的声明调用更加灵活
- 函数表达式不存在提升的现象,函数提升出现在相同的作用域中
作用域链(底层的变量查找机制):
在函数被执行时会邮箱查找当前函数作用域中的变量,如果当前作用域找不到就会依次逐级查找父级作用域直至全局作用域中。
采取的是就近原则,若是未找到就返回undefined
内部函数访问外部函数的变量,采用链式擦护照的方式来决定取哪个值
- 只要是代码就至少有一个作用域
- 写在函数内部的数局部作用域
- 如果函数中还有函数,那么在这个作用域中又可以诞生一个作用域
- 根据在内部函数可以访问外部变量的这种机制,用链式查找决定哪些数据可以被内部访问就成为作用域链
闭包 = 内层函数+外层函数的变量:
作用:封闭数据,提供操作,外部也可以访问函数内部的变量
function outer(){
let i = '外部函数的变量'
return function (){
console.log(i)
}
}
const fun = outer()
fun()
6.函数类别
//具名函数
function fn() {
console.log("我是具名函数")
}
fn();
//匿名函数
let fn1 = function (){
console.log("我是匿名函数,用变量接收,否则无法被调用")
}
fn1();
//立即执行函数
(function(){
console.log('我是立即执行函数,使用多个记得把我用分号;与 其他的立即执行函数隔开哦')
}());
//箭头函数
const fn3 = () => {console.log('我是箭头函数')}
fn3();
//构造函数
function Fn(){
this.name = '现在时间'
this.time = new Date()
}
const a = new Fn()
console.log( '我是构造函数')
具名函数
匿名函数
立即执行函数
箭头函数
- 适用于本来需要匿名函数的地方
- 箭头函数不会创建自己的this,它只会沿用自己的作用域链上一层的this
- 箭头函数没有动态参数arguments,但是有剩余参数...args
- DOM事件回调函数一般不适用箭头函数,在事件回调中,this指向的是window
构造函数
注意点:
- 主要用于初始化对象,也可以利用构造函数快速的创建多个类似的对象
- 命名以大写字母开头,只能用new操作符来执行---称之为实例化
- 实例化构造函数没有参数的时候可以省略()---推荐不省略
- 构造函数内部不用写return,返回值是新创建的对象
- 内置的构造函数Object,Array,String,Number等
实例化过程:
- 创建新对象
- 构造函数this指向新对象
- 执行构造函数代码,修改this,添加新属性
实例成员与静态成员
实例成员 = 实例对象中的属性 + 方法
- 实例对象:通过构造函数创建的对象
- 实例成员:为构造函数传入参数,动态的创建结构相同但是值不相同的对象
- 构造函数创建的对象之间彼此独立不受影响
静态成员 = 构造函数的属性 + 方法
- 静态成员的this指向构造函数本身
- 静态成员就是公共特征的属性和方法
- 静态成员之间只能通过构造函数来访问
原型对象prototype
- 每一个构造函数都有一个prototype属性,指向另外一个对象,也称之为原型对象
- 把不变的方法定义在原型对象上,让所有对象的实例化都可以共享这些属性或者方法
- 构造函数和原型对象都指向实例化对象
//构造函数
function Fn(){
this.name = '现在时间'
this.time = new Date()
}
console.log('我是当前构造函数的原型对象',Fn.prototype)
constructor属性:
- 每个构造函数都有一个constructor属性,指向的是原型对象的构造函数
- 多个对象方法,原型对象采用对象赋值时可能会覆盖构造函数使得constructor不再指向当前原型对象的构造函数,我们可以使用在修改的原型对象上添加一个constructor指回原来的构造函数
//构造函数通过new完成实例化
function Fn() {
this.name = '现在时间'
this.time = new Date()
}
const a = new Fn()
console.log('我是通过new实例化之后的constructor指向,',a.constructor)
//创建一个构造函数Person
function Person() {
this.eyes = 2
this.head = 1
}
// 创建一个构造函数Woman
function Woman() {
this.name = '构造函数'
}
// 子类通过原型来继承父类
// 子类的原型 = new 父类
Woman.prototype = new Person()
// 原型对象的constructor不再指向构造函数Person
console.log('我是通过原型来继承父类的constructor指向Person',Woman.prototype.constructor)
// 指回原来的构造函数
Woman.prototype.constructor = Woman
console.log('我是修改之后指回原来的构造函数Woman',Woman.prototype.constructor)
对象原型__proto__(前后都是两个_)
对象都会有一个属性__proto__指向构造函数的prototype原型对象,对象之所以可以使用构造函数的prototype原型对象的属性和方法,就是因为对象原型__proto__的存在
__proto__对象原型里也有一个constructor属性指向创造该实例对象的构造函数
在浏览器中__proto__显示为[[ Prototype ]]
console.log(Lily.__proto__)
原型继承
- JS中借助原型对象prototype实现继承的特性
- 封装:抽取公共部分
- 继承:共享构造函数中的属性和方法(原型对象prototype中的)
原型链
原型链查找规则:
- 首先查找这个对象自身是否具有该属性/方法
- 没有则查找他的原型对象(__proto__指向的prototype原型对象)
- 再没找到就查找原型对象的原型(Object构造函数的原型对象prototype)
- 一直找到Object为止(null)
__proto__对象原型的意义是在于为对象成员查找机制提供一个方向(路线)
可以使用instanceof运算符用于检测构造函数的prototype属性是否出现在某个实例对象的原型链上。
处理this指向问题
- call()方法--->调用函数,同时指定被调用函数的this值-->fun.call(this.Arg,arg1,arg2...)
- apply()方法--->调用函数(和数组有关),同时指定被调用函数的this值-->fun.apply(this.Arg,[argsArray])
- bind()方法--->不调用函数,改变函数内部的this指向-->fun.bind(this.Arg,arg1,arg2...)
function sum(x,y){
return x+y
}
let sum1 = sum.call(null,5,9)
console.log(sum1)
//call()方法调用函数时,第一个参数是this指定的值
let sum2 = sum.apply(null,[5,9])
console.log(sum2)
//apply()方法调用函数时,第一个参数是this指定的值,第二个参数是数组
function sayHi(){
console.log('我是hi方法')
}
let user = {
name:'Lily'
}
let sayHello = sayHi.bind(user)
sayHello()
//bind()方法不会调用函数时,而是创建了一个this值的新函数