函数(Function)
语法:
function 函数名(){
语句...
}
调用函数:
函数对象()
使用 typeof
检查函数对象时会返回 function
三种方式
function 函数名(){
语句...
}
示例:
function fn(){
console.log("函数声明所定义的函数~")
}
const 变量 = function(){
语句...
}
示例:
const fn2 = function(){
console.log("函数表达式")
}
() => {
语句...
}
示例:
const fn3 = () => {
console.log("箭头函数")
}
const fn4 = () => console.log("箭头函数")
形式参数
在定义函数时,可以在函数中指定数量不等的形式参数(形参)
在函数中定义形参,就相当于在函数内部声明了对应的变量但是没有赋值
实际参数
在调用函数时,可以在函数的()
传递数量不等的实参
实参会赋值给其对应的形参
参数:
如果实参和形参数量相同,则对应的实参赋值给对应的形参
如果实参多于形参,则多余的实参不会使用
如果形参多于实参,则多余的形参为undefined
参数的类型
// 计算两数之和
function sum(a, b){
console.log(a + b)
}
function 函数名([参数]){
语句...
}
const 变量 = function([参数]){
语句...
}
([参数]) => {
语句...
}
例子:
const fn = (a, b) => {
console.log("a =", a);
console.log("b =", b);
}
fn(123, 456)
箭头函数只有一个参数的时候可以省略()
const fn2 = a => {
console.log("a =", a);
}
定义参数的时候,可以指定默认值,默认值会在没有对应实参时候生效
const fn3 = (a=10, b=20, c=30) => {
console.log("a =", a);
console.log("b =", b);
console.log("c =", c);
}
fn3(1, 2) // a = 1, b = 2, c = 30
对象可以作为参数传递,传递的是变量中存储的值,也就是对象的内存地址,所以形参和实参指向的就是同一个对象
这个必须得区分清楚
function fn(a){
console.log("a =", a)
// a = {} // 修改变量时,只会影响当前的变量
a.name = "猪八戒" // 修改对象时,如果有其他变量指向该对象则所有指向该对象的变量都会受到影响
console.log(a)
}
let obj = {name:"孙悟空"}
fn(obj)
如果形参的默认值是一个对象的话,那么在函数每次调用的时候,都会重新创建新对象
// 函数每次调用,都会重新创建默认值
function fn2(a = {name:"沙和尚"}){
console.log("a =", a)
a.name = "唐僧"
console.log("a =", a)
}
fn2() // 沙和尚 唐僧
fn2() // 沙和尚 唐僧
但是如果是下面这个样子的话,每次指向的都是obj2,不会重新创建对象
let obj2 = {name:"沙和尚"}
// 函数每次调用,都会重新创建默认值
function fn2(a = obj2){
console.log("a =", a)
a.name = "唐僧"
console.log("a =", a)
}
fn2() // 沙和尚 唐僧
fn2() // 唐僧 唐僧
在JS中,函数也是一个对象(一等函数),别的对象能做的事情函数也可以
所以别的对象可以作为参数传递到函数中,函数也可以作为参数传递到函数中
function fn(a) {
console.log("a =", a)
a()
}
function fn2() {
console.log("fn2被调用了")
}
fn(fn2)
有了这种功能之后,我们就可以动态的执行代码
fn(() => console.log("我是箭头函数"))
function exec(desc, fn, arg1, arg2) {
console.log(desc + " = " + fn(arg1, arg2))
}
function add(arg1, arg2) {
return arg1 + arg2
}
function sub(arg1, arg2) {
return arg1 - arg2
}
exec("3 + 5", add, 3, 5)
exec("5 - 3", sub, 5, 3)
exec("5 * 3", (arg1, arg2) => {return arg1 * arg2}, 5, 3) // 使用匿名函数作为参数
在函数中,可以通过return
关键字来指定函数的返回值,返回值就是函数的执行结果,函数调用完毕返回值便会作为结果返回
任何值都可以作为返回值使用(包括对象和函数之类),如果return后不跟任何值,则相当于返回undefined
,如果不写return,那么函数的返回值依然是undefined
function sum(a, b) {
// console.log(a + b)
// 计算完成后,将计算的结果返回而不是直接打印
return a + b
}
let result = sum(2,3) // 5
return一执行函数立即结束
function sum(a, b) {
return a + b
console.log(a + b) // 不会执行这条语句
}
箭头函数的返回值可以直接写在箭头后,如果直接在箭头后设置对象字面量为返回值时,对象字面量必须使用()括起来
const sum = (a, b) => {
return a + b
}
const sum1 = (a, b) => a + b
const fn = () => ({name: "张三"})
function fn(){
let a = "fn中的变量a"
console.log(a)
}
fn()
console.log(a) // undefined
当我们使用一个变量时,JS解释器会优先在当前作用域中寻找变量,就近原则
参照下面的例子理解
报错的原因看下一节提升相关概念
let a = 10
{
console.log(a) // 报错,Cannot access 'a' before initialization
let a = "第一代码块中的a"
console.log(a) // "第一代码块中的a"
{
console.log(a) // 报错,Cannot access 'a' before initialization
let a = "第二代码块中的a"
console.log(a) // "第二代码块中的a"
}
console.log(a) // "第一代码块中的a"
}
console.log(a) // 10
let b = 33
function fn() {
console.log(b) // 报错,Cannot access 'b' before initialization
let b = 44
console.log(b) // 44
function f1() {
console.log(b) // 报错,Cannot access 'b' before initialization
let b = 55
console.log(b) // 55
}
f1()
}
fn()
练习1
var a = 1
function fn() {
a = 2
console.log(a) // 2
}
fn()
console.log(a) // 2
练习2
var a = 1
function fn() {
console.log(a) //undefined
var a = 2
console.log(a) // 2
}
fn()
console.log(a) // 1
练习3
var a = 1
function fn(a) {
console.log(a) //undefined
a = 2 // 修改的是形参
console.log(a) // 2
}
fn()
console.log(a) // 1
练习4
var a = 1
function fn(a) {
console.log(a) //10
a = 2
console.log(a) // 2
}
fn(10)
console.log(a) // 1
练习5
var a = 1
function fn(a) {
console.log(a) //1
a = 2
console.log(a) // 2
}
fn(a)
console.log(a) // 1
练习6
console.log(a) // a指向的是第五行的函数,var只是声明,并不赋值,如果声明过不会重复赋值
var a = 1
console.log(a) // 1
function a() {
alert(2)
}
console.log(a) // 1
var a = 3
console.log(a) // 3
var a = function () {
alert(4)
}
console.log(a) // 打印11行函数
var a // 已经声明过了,根本不执行
console.log(a) // 打印11行函数
window.对象名
访问,也可以直接通过对象名访问window.a = 50
console.log(window.a) // 50
console.log(a) // 50
var:用来声明变量,作用和let相同,但是var不具有块作用域
在全局中使用var声明的变量,都会作为window对象的属性保存
var b = 20
等价于window.b = 20
使用function声明的函数,都会作为window的方法保存
function fn(){ alert('我是fn') } fn() // 等价于window.fn()
- 1
- 2
- 3
- 4
使用let声明的变量不会存储在window对象中,而存在一个秘密的小地方(无法访问)
var虽然没有块作用域,但有函数作用域
function fn2(){ var d = 10 // var虽然没有块作用域,但有函数作用域 m = 10 // 在局部作用域中,如果没有使用var或let声明变量,则变量会自动成为window对象的属性 也就是全局变量 } fn2() console.log(d) // d is not defined console.log(m) // 10
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
使用var声明的变量,会在所有代码执行前被声明(而不是赋值),所以就可以在变量声明前就访问变量
let声明的变量实际也会提升,但是在赋值之前解释器禁止对该变量的访问
console.log(a) // 打印undefined
var a = 10
console.log(b) // 报错 b is not defined
b = 10
console.log(c) // Cannot access 'c' before initialization
let c = 10
使用函数声明创建的函数,会在其他代码执行前被创建,所以我们可以在函数声明前调用函数
只有以var, let或者function开头的变量才会提升
fn() // 可以正常调用执行
fn2() // 报错fn2 is not a function
function fn(){
alert("我是fn函数~")
}
var fn2 = function(){
alert("我是fn函数~")
}
是为了解决性能问题,要根据变量和函数的数量决定开多大的内存空间
两种方式
https://www.bilibili.com/video/BV1mG411h7aD?p=67
直接在代码中写debugger
,在浏览器中打开控制台刷新页面后会停到这个位置
debugger // 在代码中打了一个断点
console.log(a) // 2
var a = 1
console.log(a) // 1
直接打开浏览器,F12 -> 源代码 -> 指定位置点击行号 -> 刷新
在开发中应该尽量减少直接在全局作用域中编写代码!
所以我们的代码要尽量编写的局部作用域
如果使用let声明的变量,可以使用{}
来创建块作用域,这样也不会相互干扰
{
let a = 10
}
{
let a = 20
}
而如果现在函数里面,哪怕是使用var声明的变量也不会相互干扰,但是使用函数的时候需要再单独调用一下
function fn(){
var a = 10
}
fn()
function fn2(){
var a = 20
}
fn2()
我们希望可以创建一个只执行一次的匿名函数
立即执行函数(IIFE)
IIFE
来创建一个一次性的函数作用域,避免变量冲突的问题(xxx)(xxx)
(function(){
let a = 10
console.log(111)
}());
(function(){
let a = 20
console.log(222)
}());
this
1.以函数形式调用时,this指向的是window
2.以方法的形式调用时,this指向的是调用方法的对象
但实际上都是指向调用的对象,以函数方式调用的时候默认调用的对象是window
function fn() {
// console.log(this === window)
console.log("fn打印", this)
}
fn() // 就相当于 window.fn()
const obj = { name: "孙悟空" }
obj.test = fn
obj.test()
案例:为两个对象添加一个方法,可以打印自己的名字
方法一:
const obj3 = {
name: "沙和尚",
sayHello: function () {
console.log(this.name)
},
}
const obj4 = {
name: "唐僧",
sayHello: function(){
console.log(this.name)
}
}
// 为两个对象添加一个方法,可以打印自己的名字
obj3.sayHello()
obj4.sayHello()
方法二:
const sayHello = function(){
console.log(this.name)
}
const obj3 = {
name: "沙和尚",
sayHello: sayHello
}
const obj4 = {
name: "唐僧",
sayHello: sayHello
}
// 为两个对象添加一个方法,可以打印自己的名字
obj3.sayHello()
obj4.sayHello()
箭头函数:([参数]) => 返回值
例子:
无参箭头函数:() => 返回值
一个参数的:a => 返回值
多个参数的:(a, b) => 返回值
只有一个语句的函数:() => 返回值
只返回一个对象的函数:() => ({...})
有多行语句的函数:
() => {
....
return 返回值
}
箭头函数没有自己的this,它的this由外层作用域决定,箭头函数的this和它的调用方式无关,外面的this是谁,它就是,如果在最外层,那么它的this就是window
function fn() {
console.log("fn -->", this)
}
const fn2 = () => {
console.log("fn2 -->", this) // 总是window
}
fn() // window
fn2() // window
const obj = {
name:"孙悟空",
fn, // fn:fn的简写
fn2,
sayHello(){
console.log(this.name)
function t(){
console.log("t -->", this)
}
t() // window
const t2 = () => {
console.log("t2 -->", this)
}
t2() // obj
}
}
obj.fn() // obj
obj.fn2() // window
obj.sayHello()//
JS运行代码的模式有两种:
正常模式
严格模式
在严格模式下,语法检查变得严格
使用方式
"use strict" // 全局的严格模式
let a = 10
function fn(){
"use strict" // 函数的严格的模式
}
// 如果在严格模式下定义如:
a = 10 // 则会报错a is not defined
在开发中,应该尽量使用严格模式,这样可以将一些隐藏的问题消灭在萌芽阶段,同时也能提升代码的运行性能