在国外的一个问答网站stackover flow中,常常会使用这几个次进行变量,函数,对象等等声明,地位如同张三,李四,王五。foo、bar这些名词最早从什么时候、地方流行起来的一直是由争论的,有兴趣可以自己了解一下。
函数、变量、文件
的名词,随着使用的普及,目前已经是计算机编程中术语的一部分,但实际上没有特别的用途和意义
备注:foo、bar、baz已经是编程领域非常常用的名词,习惯在写一些变量、函数名词
时使用这些词汇
在之前我们已经使用过很多函数,如 alert()
浏览器弹出一个弹窗,prompt函数
在浏览器弹窗中接收用户的输入,console.log函数
在控制台输入内容,还有 String/Number/Boolean函数
等
对某段代码的封转
,这段代码能帮助我们完成某一个功能,默认情况下JavaScript引擎或者浏览器会给我们提供一些已经实现好的函数,我们也可以自己编写函数
。函数的使用包含两个步骤:
声明函数(定义函数)
:声明函数的过程是对某些功能的封转过程,我们可以根据自己的需求定义很多自己的函数,函数定义完后里面的代码是不会执行的,函数必须调用才会执行
调用函数(函数调用)
: 调用已经存在的函数,我们可以调用自己封装好的某个功能函数,也可以使用浏览器默认提供或其它三方库定义好的函数函数的作用: 在开发程序时,使用函数可以提高编写的效率以及代码的重用
// 声明一个函数
// 制作好一个工具, 但是这个工具默认情况下是没有被使用
function sayHello() {
console.log("Hello!")
console.log("My name is Coderwhy!")
console.log("how do you do!")
}
// 调用一个函数
sayHello()
// 函数可以在任何你想要使用的时候, 进行调用
sayHello()
函数的声明:
函数的调用: 通过函数名()
即可:比如test()
// 练习一: 定义一个函数, 打印自己的个人信息
function printInfo() {
console.log("my name is why")
console.log("age is 18")
console.log("height is 1.88")
}
printInfo()
printInfo()
// 练习二: 定义一个函数, 在内部计算10和20的和
function sum() {
var num1 = 10
var num2 = 20
var result = num1 + num2
console.log("result:", result)
}
sum()
因此,我们可以在函数的内部,把参数当作变量使用。在我们进行调用时,按照声明函数是所定义的参数顺序,把对应的参数传递给函数。同时参数也分为两个概念:
定义
函数时,小括号中的参数,是用来接收参数用的,在函数内部 作为变量使用
调用
函数时,小括号中的参数,是用来把数据传递到函数内部
用的// name/age/height称之为函数的参数(形参, 形式参数, parmaters)
function printInfo(name, age, height) {
console.log(`my name is ${name}`)
console.log(`age is ${age}`)
console.log(`height is ${height}`)
}
// why/18/1.88称之为函数的参数(实参, 实际参数, arguments)
printInfo("why", 18, 1.88)
printInfo("kobe", 30, 1.98)
// 另外一个案例也做一个重构
function sum(num1, num2) {
var result = num1 + num2
console.log("result:", result)
}
sum(20, 30)
sum(123, 321)
// 练习一: 和某人打招呼
function sayHello(name) {
console.log(`Hello ${name}`)
}
sayHello("Kobe")
sayHello("James")
sayHello("Curry")
// 练习二: 给某人唱生日歌
function singBirthdaySong(name) {
console.log("happy birthday to you")
console.log("happy birthday to you")
console.log(`happy birthday to ${name}`)
console.log("happy birthday to you")
}
singBirthdaySong("Kobe")
singBirthdaySong("Why")
在刚开始学习js时,我们经常使用的prompt()函数,函数需要接受参数,并且会返回用户的输入,所以函数不仅仅可以有参数, 也可以有返回值
:
在函数中我们使用return关键字来返回结果
, 一旦在函数中执行return操作
,那么当前函数会终止
。
// var result = prompt("请输入一个数字:")
// 1.理解函数的返回值
// function sayHello(name) {
// console.log(`Hi ${name}`)
// }
// var foo = sayHello("Kobe")
// console.log("foo:", foo)
// 2.返回值的注意事项
// 注意事项一: 所有的函数, 如果没有写返回值, 那么默认返回undefined
// function foo() {
// console.log("foo函数被执行~")
// }
// var result = foo()
// console.log("foo的返回值:", result)
// 注意事项二: 我们也可以明确的写上return
// 写上return关键字, 但是后面什么内容都没有的时候, 也是返回undefined
// function bar() {
// console.log("bar函数被执行~")
// return
// }
// var result = bar()
// console.log("bar的返回值:", result)
// 注意事项三: 如果在函数执行到return关键字时, 函数会立即停止执行, 退出函数
// function baz() {
// console.log("Hello Baz")
// return
// console.log("Hello World")
// console.log("Hello Why")
// }
// baz()
// 函数的具体返回值
function sum(num1, num2) {
var result = num1 + num2
return result
}
var total = sum(20, 30)
console.log("total:", total)
arguments变量:arguments变量的类型是Object对象
类型,它是一个类数组array-like但不是数组,它的用法和数组很相似。
arguments的应用:
arguments对象
是所有(非箭头)函数中都可以用的局部变量
存放着所有调用者传入的参数
,从0开始,一依次存放
。传入的参数多余函数接受的参数
,我们也可以通过arguments去获取所有的参数
。// 1.arguments的认识
function foo(name, age) {
console.log("传入的参数", name, age)
// 在函数中都存在一个变量, 叫arguments
console.log(arguments)
// arguments是一个对象
console.log(typeof arguments)
// 对象内部包含了所有传入的参数
// console.log(arguments[0])
// console.log(arguments[1])
// console.log(arguments[2])
// console.log(arguments[3])
// 对arguments来进行遍历
for (var i = 0; i < arguments.length; i++) {
console.log(arguments[i])
}
}
foo("why", 18, 1.88, "广州市")
// 2.arguments的案例
function sum() {
var total = 0
for (var i = 0; i < arguments.length; i++) {
var num = arguments[i]
total += num
}
return total
}
console.log(sum(10, 20))
console.log(sum(10, 20, 30))
console.log(sum(10, 20, 30, 40))
函数的递归:在函数的内部,我们可以调用另外一个函数,而如果在函数内部调用自己,那么这样的函数调用叫做递归(Recursion)
function bar() {
console.log("bar函数被执行了~")
console.log("----------")
}
function foo() {
// 浏览器默认提供给我们的其他函数
console.log("foo函数执行")
console.log("Hello World")
// alert("Hello Coderwhy")
// 调用自己定义的函数
bar()
// 其他代码
console.log("other coding")
}
foo()
// 递归调用
// 默认情况下会产生无限调用的情况
function foo() {
console.log("foo函数被执行了")
foo()
}
foo()
递归的练习: 封装一个函数, 函数可以实现x的n次方法
**操作符
实现(新语法)function pow1(x, n) {
return x ** n
}
console.log(pow1(2, 3))
console.log(pow1(3, 3))
console.log(Math.pow(2, 3))
console.log(Math.pow(3, 3))
// 一. for循环实现方式
// x² = x * x
// x³ = x * x * x
function pow2(x, n) {
var result = 1
for (var i = 0; i < n; i++) {
result *= x
}
return result
}
console.log(pow2(2, 3))
console.log(pow2(3, 3))
function pow(x, n) {
// 结束条件
if (n === 1) {
return x
}
// 2,3==> 2* pow(2,2)=> 2*(2* pow(2,1))=>2*(2*2))
// 递归
return x * pow(x, n - 1)
}
递归总结:
// 什么是斐波那契数列
// 数列: 1 1 2 3 5 8 13 21 34 55 ... x
// 位置: 1 2 3 4 5 6 7 8 9 10 ... n
// 1.斐波那契的递归实现
function fibonacci(n) {
if (n === 1 || n === 2) return 1
return fibonacci(n-1) + fibonacci(n-2)
}
// 2.斐波那契的for循环实现
function fibonacci(n) {
// 特殊的情况(前两个数字)
if (n === 1 || n === 2) return 1
// for循环的实现
var n1 = 1
var n2 = 1
var result = 0
for (var i = 3; i <= n; i++) {
result = n1 + n2
n1 = n2
n2 = result
}
return result
}
console.log(fibonacci(5))
console.log(fibonacci(10))
console.log(fibonacci(20))
作用域(Scope):表示一些标识符的作用有效范围,决定了一个变量在哪一个范围内可以被使用,根据有效范围,我们对作用域进行了分类:
var message = "Hello World"
if (true) {
console.log(message)
}
function foo() {
console.log("在foo中访问", message)
}
foo()
在函数内部定义的变量,只有在函数内部可以被访问到
// 2.ES5之前是没有块级作用域(var定义的变量是没有块级作用域)
{
var count = 100
console.log("在代码块中访问count:", count)
}
console.log("在代码块外面访问count:", count)
// for循环的代码块也是没有自己的作用域
for (var i = 0; i < 3; i++) {
var foo = "foo"
}
console.log("for循环外面访问foo:", foo)
console.log("for循环外面访问i:", i) // 3
// 3.ES5之前函数代码块是会形成自己的作用域
// 意味着在函数内部定义的变量外面是访问不到的
function test() {
var bar = "bar"
}
test()
// console.log("test函数外面访问bar:", bar)
// 函数有自己的作用域: 函数内部定义的变量只有函数内部能访问到
function sayHello() {
var nickname = "kobe"
console.log("sayHello函数的内部:", nickname)
function hi() {
console.log("hi function~")
console.log("在hi函数中访问nickname:", nickname)
}
hi()
}
sayHello()
// console.log("sayHello外面访问nickname:", nickname)
外部变量
是相对于局部变量
的,它特指从局部作用域内访问外部声明的变量。// 1.全局变量(global variable): 在全局(script元素中)定义一个变量, 那么这个变量是可以在定义之后的任何范围内被访问到的, 那么这个变量就称之为是一个全局变量.
var message = "Hello World"
// 在函数中访问message
function sayHello() {
// 外部变量(outer variable): 在函数内部去访问函数之外的变量, 访问的变量称之为外部变量
console.log("sayHello中访问message:", message)
// 2.局部变量(local variable): 在函数内部定义的变量, 只有在函数内部才能进行访问, 称之为局部变量
var nickname = "coderwhy"
function hi() {
console.log("hi function~")
// message也是一个外部变量
console.log("hi中访问message:", message)
// nickname也是一个外部变量
console.log("hi中访问nickname:", nickname)
}
hi()
}
sayHello()
从最近的局部变量开始查找,依次从外部变量到全局变量,没有找到就报错。
定义函数的两种方式:
function foo() {
console.log("foo函数被执行了~")
}
函数表达式允许省略函数名。
// 函数表达式允许省略函数名。
// var baz = function baz01() {
// console.log("baz函数被执行了~")
// }
var bar = function () {
console.log("bar函数被执行了~")
}
备注: 无论函数是如何创建的,函数都是一个值(这个值的类型是一个对象,对象的概念后面会讲到)
JavaScript 准备 运行脚本
时,首先会在脚本中寻找全局函数声明,并创建这些函数
,因此我们可以在在函数声明被定义之前就可以调用
在代码执行到达时被创建
,并且仅从那一刻起可用
头等公民(第一公民)
函数作为头等公民(第一公民)有以下几个特点:
var foo1 = function() {
console.log("foo1函数被执行~")
}
foo1()
var foo2 = foo1
foo2()
function bar(fn) {
console.log("fn:", fn)
fn()
}
bar(foo1)
function sayHello() {
function hi() {
console.log("hi kobe")
}
return hi
}
var fn = sayHello()
fn()
// 存储在对象或者数组结构中
var obj = {
name: "why",
eating: function() {
console.log("eating")
}
}
obj.eating()
function bar1() {
console.log("bar1函数被执行~")
}
function bar2() {
console.log("bar2函数被执行~")
}
function bar3() {
console.log("bar3函数被执行~")
}
// 事件总线的封装
var fns = [bar1, bar2, bar3]
函数式编程
前面介绍了头等函数,我们知道函数可以作为一个值互相赋值传递,也可以传递给另外一个函数,
// 1.函数回调的概念理解
function foo(fn) {
// 通过fn去调用bar函数的过程, 称之为函数的回调
fn()
}
function bar() {
console.log("bar函数被执行了~")
}
foo(bar)
/ 2.函数回调的案例
// function request(url, callback) {
// console.log("根据URL向服务器发送网络请求")
// console.log("需要花费比较长的时间拿到对应的结果")
// var list = ["javascript", "javascript学习", "JavaScript高级编程"]
// callback(list)
// }
// function handleResult(res) {
// console.log("在handleResult中拿到结果:", res)
// }
// request("url", handleResult)
// 3.函数回调的案例重构
function request(url, callback) {
console.log("根据URL向服务器发送网络请求")
console.log("需要花费比较长的时间拿到对应的结果")
var list = ["javascript", "javascript学习", "JavaScript高级编程"]
callback(list)
}
// 传入的函数是没有名字, 匿名函数
request("url", function(res) {
console.log("在handleResult中拿到结果:", res)
})
立即执行函数:Immediately-Invoked Function Expression
,简称 IIFE 立即调用函数表达式),表示一个函数定完后立即执行
。
定义立即执行函数:
()
定义了一个匿名函数,这个函数有自己独立的作用域()
,表示这个函数被执行了 // 1.普通函数的使用过程
function foo() {
console.log("foo函数被执行~")
}
foo()
foo(); // ()[]{}
// 2.定义函数, 定义完这个函数之后, 会要求这个函数立即被执行
// {} 代码块/对象类型
// () 控制优先级(2+3)*5/函数的调用/函数的参数
// [] 定义数组/从数组-对象中取值/对象的计算属性
// 立即执行函数(常用的写法)
(function() {
console.log("立即执行函数被调用~")
})()
// 3.立即执行函数的参数和返回值
var result = (function(name) {
console.log("函数立刻被执行~", name)
return "Hello World"
})("why")
console.log(result)
备注:在代码编写时,若有(),[],{}连续代码,却未加分号,可能会被认为是一个整体的语句
// 应用场景一: 防止全局变量的命名冲突
// 立即执行函数和普通的代码有什么区别?
// 在立即执行函数中定义的变量是有自己的作用域的
(function() {
var message = "Hello World"
// console.log(message)
})()
// console.log(message)
// var message = "Hello World"
// console.log(message)
// 1.获取一个按钮监听点击
// 1.拿到html元素
var btnEl = document.querySelector(".btn")
console.log(btnEl)
// 2.监听对应按钮的点击
btnEl.onclick = function() {
console.log("点击了按钮1")
}
// 2.获取所有的按钮监听点击
// 没有使用立即执行函数
debugger
var btnEls = document.querySelectorAll(".btn")
for (var i = 0; i < btnEls.length; i++) {
var btn = btnEls[i];
btn.onclick = function() {
console.log(`按钮${i+1}发生了点击`)
}
}
分析
:此时的i由于是全局变量,自增后值为数组长度,但是我们的点击事件的回调函数中的${i+1}是根据触发事件后i的值决定的,因此这种写法存在一定的错误,我们可以通过立即执行函数解决
var btnEls = document.querySelectorAll(".btn")
for (var i = 0; i < btnEls.length; i++) {
var btn = btnEls[i];
(function(m) {
btn.onclick = function() {
console.log(`按钮${m+1}发生了点击`)
}
})(i)
}
console.log(i)
我们知道立即执行函数的写法中包含两个小括号,其中第一个括号
把函数看成一个表达式,第二个括号
表示调用函数,因此我们有以下几个写法:
(function(形参){
代码块
})(实参)
(function(形参){
代码块
}(实参))
(了解)
// +(正号)-(符号)!(取反) - 了解
+function foo() {}()