前端入门09 -- JavaScript之函数,作用域链,闭包
2022-09-21 本文已影响0人
YanZi_33
函数
- 函数也是一个对象;
- 函数:把一个或者多功能通过
函数的方式封装起来
,对外只提供一个简单的函数接口; - 声明定义函数的三种方式:
- 利用函数关键字
function
自定义函数; -
函数表达式
,用一个变量接收函数表达式; -
箭头函数
,用一个变量接收定义的箭头函数;
- 利用函数关键字
<script>
///1.function关键字
///addNum是一个函数
function addNum(a,b) {
return a + b
}
addNum(100,200)
///2.函数表达式
///func是变量名 不是函数名 后面的表达式才是函数 属于匿名函数
var func = function(x) {
console.log(x)
}
func('yanzi')
///3.箭头函数
var func2 = () => {
console.log('我是一个箭头函数');
}
func2();
</script>
<script>
//定义声明函数
function getSum() {
var sum = 0
for (var i = 0; i < 100; i++) {
sum += i
}
console.log(sum);
}
//调用函数
getSum()
</script>
函数的参数
-
行参
:形式上的参数,函数定义的时候传递的参数 当前并不知道是什么; -
实参
:实际上的参数,函数调用的时候传递的参数 实参是传递给行参的; - 函数的参数可以有,也可以没有个数不限;
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script>
//定义声明函数
function getSum(num1,num2) {
console.log(num1 + num2)
}
//调用函数
getSum(100,200)
</script>
</head>
<body>
</body>
</html>
- 函数形参实参的个数匹配问题:
- 如果实参的个数和形参的个数一致,则正常输出结果;
- 如果实参的个数多于形参的个数,会取到形参的个数;
- 如果实参的个数小于形参的个数,多于的形参定义为undefined,最终的结果就是NaN;
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script>
//定义声明函数
function getSum(num1,num2) {
console.log(num1 + num2)
}
getSum(100,200) //300
getSum(100,200,300) //300
getSum(100) //NaN
</script>
</head>
<body>
</body>
</html>
箭头函数的参数与参数默认值
- 箭头函数中只有一个参数时,可以省略();
- 箭头函数中代码只有一行时,可以省略{};
- 参数没有传入实参时,参数默认值才会生效;
<script>
const fn = (a,b) => {
console.log('a =',a);
console.log('b =',b);
}
//省略() 与 {}
const fn1 = a => console.log('a =',a);
//参数默认值
const fn2 = (a,b,c = 30) => {
console.log('a =',a);
console.log('b =',b);
console.log('c =',c);
}
</script>
Object对象作为函数参数
- Object对象可以作为函数的参数;
<script>
function fn(a) {
console.log('a =', a);
}
let obj = { name: 'yanzi' };
fn(obj);
</script>
- Object对象字面量作为函数参数默认值,函数多次调用,会多次创建新的Object对象字面量;
- 自定义Object对象作为函数参数默认值,函数多次调用,都使用的同一个自定义Object对象;
<script>
function func(a = { name: '大神' }) {
console.log(a.name);
a.name = '小神';
console.log(a.name);
}
func(); // 大神 小神
func(); // 大神 小神
let obj = { name: '大神' }
function func1(a = obj) {
console.log(a.name);
a.name = '小神';
console.log(a.name);
}
func1(); // 大神 小神
func1(); // 小神 小神
</script>
函数作为函数参数
- 自定义函数,匿名函数表达式,箭头函数作为函数的参数;
<script>
function func(a) {
console.log('a =', a);
}
//自定义函数
function func1() {
console.log('自定义函数');
}
func(func1);
//匿名函数表达式
func(function () {
console.log('匿名函数表达式');
})
//箭头函数
func(() => {
console.log('箭头函数');
})
</script>
arguments的使用
- 当我们不确定有多少个参数传递的时候,可以用arguments来获取,在JavaScript中,arguments实际上它是 当前函数的一个
内置对象
,所有函数都内置了一个arguments对象,arguments对象中存储了传递的所有实参
; - arguments展示形式是一个
伪数组
,因此可以遍历,伪数组具有以下特点:- 具有length属性;
- 按索引方式储存数据;
- 不具有数组的push,pop等方法;
<script>
function fn() {
console.log(arguments)
console.log(arguments.length)
console.log(arguments[2])
for (var i = 0; i < arguments.length; i++) {
console.log(arguments[i]); //10,2,33
}
}
fn(10,2,33)
</script>
函数返回值
-
函数的返回值
:只要函数遇到return
就把后面的结果 返回给函数的调用者,且终止之后的代码执行,且只能返回一个值,是没有返回值类型的; - return什么都不返回 或者 不写return,那么函数的返回值为undefined;
<script>
//定义声明函数
function getArrMax(arr) {
var max = arr[0]
for (var i = 1; i < arr.length; i++) {
if (arr[i] > max) {
max = arr[i]
}
}
return max
}
var max = getArrMax([100,200,30,40,50,333,234])
console.log(max);
</script>
<script>
function getResult(num1,num2) {
//返回值为数组
return [num1+num2,num1-num2]
}
var result = getResult(100,30)
console.log(result);
</script>
- 箭头函数只有一行代码,且有返回值,可省略return关键字 和 {};
- 箭头函数只有一行代码,且返回值为Object对象字面量,那么需要加上(Object对象字面量);
<script>
const func = (a, b) => {
return a + b;
}
const func1 = (a, b) => a + b;
const func2 = (a, b) => ({ name: 'yanzi' });
</script>
JavaScript作用域
-
全局作用域
:整个script标签 或者是一个单独的js文件; -
局部作用域
:存在两种分别为块级作用域和函数作用域;-
块级作用域
:在代码块执行时创建,在代码块执行完毕时销毁; -
函数作用域
:在函数调用时创建,在函数调用完成时销毁;
-
-
全局变量
:在全局作用域中的变量,注意在函数内部 没有声明直接赋值的变量 也属于全局变量
,全局变量只有在浏览器关闭的时候才会被销毁,比较占内存资源; -
局部变量
:在局部作用域中的变量,当函数执行完成时就会被销毁,比较节约内存资源;
<script>
//全局作用域 全局变量
var num = 100
function addNum(a,b) {
//局部作用域 局部变量
var sum = 0
sum = a + b
//sum1没有声明直接赋值 属于全局变量
sum1 = 1000
return sum
}
addNum(100,200)
</script>
-
作用域链
:内部函数访问外部函数变量,JS解释器会优先在当前对象中寻找目标变量,若找到直接使用,若没有找到,则去上一层作用域中去寻找,依次类推,若找到全局作用域都没有找到目标变量,就会报错xxx is not defined
,在作用域中寻找目标变量,遵循就近原则
;
<script>
var num = 10
function func() { //外部函数
var num = 20
function func1() { //内部函数
console.log(num); // 20
}
}
let a = '全局作用域中的a';
{
let a = '第一个块级作用域中的a';
{
let a = '第二个块级作用域中的a';
console.log('a =', a);
}
}
</script>
JavaScript预解析
- JavaScript代码是由浏览器中的JavaScript解析器来执行的,JavaScript解析器在运行JavaScript代码的时候分为两个步骤,分别为:
预解析
与代码执行
; - 预解析:js引擎会将js里面
所有的var和function
提升到当前作用域的最前面
; - 代码执行:按照代码书写的顺序从上到下依次执行;
- 变量var的提升:将所有
变量var声明提升
到当前作用域的最前面,不会提升赋值操作
; - 函数的提升:将所有
函数声明(定义)提升
到当前作用域的最前面,不调用函数; - 变量let的提升:会将所有
变量let声明提升
到当前作用域的最前面,但是在赋值之前解释器禁止对该变量的访问;
<script>
console.log(a); //a is not defined
a = 100;
console.log(b); //undefined
var b = 100;
console.log(c); //Cannot access 'c' before initialization
let c = 100;
</script>
<script>
//第一种:
console.log(num) //报错
//第二种:
console.log(num) //undefine
var num = 10
//第三种
func() //111
function func() {
console.log(111)
}
//第四种
fun() //报错
var fun = function() {
console.log(222)
}
</script>
- 案例一:
<script>
var a = 18
f1()
function f1() {
var b = 9
console.log(a)
console.log(b)
var a = '123'
}
//上述代码等价于
var a
function f1() {
var b
var a
b = 9
console.log(a) //undefine
console.log(b) //9
a = '123'
}
a = 18
f1()
</script>
- 案例二:
<script>
f1()
console.log(c)
console.log(b)
console.log(a)
function f1() {
// var a = 9; b = 9;c = 9 b与c没有声明 直接赋值 属于全局变量
// var a = b = c = 9 与 var a = 9,b = 9, c = 9 是不同的
var a = b = c = 9
console.log(a)
console.log(b)
console.log(c)
}
//以上代码等价于
function f1() {
var a
a = b = c = 9
console.log(a) //9
console.log(b) //9
console.log(c) //9
}
f1()
console.log(c) //9
console.log(b) //9
console.log(a) //报错 a是局部变量
</script>
立即执行函数
- 在开发中应该尽量减少直接在全局作用域中编写代码,尽量在局部作用域中编写代码;
- 立即执行函数:
(function(){})()
,是一个匿名函数,直接执行,且只会执行一次;
<script>
(function(){
console.log('立即执行函数');
})()
<script>
函数中的this关键字
- 函数在执行时,JS解析器每次都会传递一个隐含参数,此参数叫做this;
- this会指向一个对象,其所指的对象会根据函数调用方式的不同而不同;
- 以函数形式调用时,this指向的是window;
- 以方法形式调用时,this指向的是调用方法的对象;
<script>
function func() {
console.log(this);
}
//以函数的形式调用时,this指向window
func(); //window
//以方法的形式调用时,this指向调用方法的对象
const obj = { name: '小神' };
obj.test = func;
obj.test(); //obj
const obj2 = { name: '大神', test: func };
obj2.test(); //obj2
</script>
- 箭头函数没有自己的this,内部打印的this是由外层作用域决定的,等于外层作用域的this;
- 箭头函数的this和它的调用方式无关;
<script>
function func() {
console.log(this);
}
//以函数的形式调用时,this指向window
func(); //window
//箭头函数
const func3 = (a, b) => {
var num = a + b;
console.log(this);
return num;
}
func3(); //外层作用域为全局作用域 this指向window
const obj3 = {
name: '如来',
fn: func,
fn2: func3,
sayHello() {
console.log(this.name);
function t() {
console.log('t -->', this);
}
t(); //window
const t2 = () => {
console.log('t2 -->', this);
}
t2(); //obj3
}
}
obj3.fn(); //obj3
obj3.fn2(); //window
obj3.sayHello();
</script>
高阶函数
- 高阶函数:若一个函数的参数或者返回值为函数,那么这个函数就是高阶函数;
- 将函数作为参数,就意味着可以对另一个函数动态的传递代码;
<script>
//高阶函数 -- 参数为一个函数
function filter(arr, cb) {
const newArr = [];
for (let index = 0; index < arr.length; index++) {
const element = arr[index];
if (cb(arr[index])) {
newArr.push(arr[index]);
}
}
return newArr;
}
const arr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
var result = filter(arr, a => a % 2 === 0);
console.log(result);
</script>
闭包
- 闭包:能访问到外部函数作用域中变量的函数;
- 构成闭包的必要条件:
- 存在函数的嵌套;
- 内部函数要引用外部函数的变量;
- 内部函数要作为返回值;
<script>
function outer() {
let num = 0;
return () => {
num++;
console.log(num);
}
}
const newFn = outer;
newFn();
</script>
- 函数的外层作用域(词法作用域),在函数创建时就已经确定了;
- 闭包的实现原理就是利用了词法作用域;
<script>
let a = '全局变量a';
function fn() {
console.log(a);
}
function fn2() {
let a = 'fn2中的a';
fn();
}
fn2(); //fn() --> 打印的是全局变量a
function fn3() {
let a = 'fn3中的a';
function fn4() {
console.log(a);
}
return fn4;
}
let result = fn3();
fn4(); //fn4() --> 打印的是fn3中的a
</script>
- 闭包的生命周期:
- 闭包在外部函数调用时产生,外部函数每次调用都会产生一个全新的闭包;
- 闭包在内部函数回收时销毁;
- 闭包的注意事项:
- 闭包住要用来隐藏一些不希望外部访问的内容,这就意味着闭包需要占用一定的内存空间;
- 相比较于类Class来说,闭包比较浪费内存空间,因为类可以使用原型而闭包不可以;
<script>
function outer() {
let someValue = 'some';
return function () {
console.log(someValue);
}
}
</script>