函数
函数(有返回值的函数)可以被用作值进行赋值,或者参与运算
函数的使用价值是将一段重复使用的功能用函数进行封装,以后可以直接进行调用,可以反复调用很多次
基本形式
JavaScript
函数是被设计为执行特定任务的代码块, 函数会在代码调用它时被执行
function name(参数 1, 参数 2, 参数 3) { // 具名函数
要执行的代码;
}
在函数中,参数是局部变量;当 JavaScript
到达 return
语句时,函数将停止执行;函数通常会计算出返回值,这个返回值会返回给函数的调用者
函数可以做为对象的方法使用:针对于某个功能,可能需要很多的函数来完成,一般将函数封装成对象的方法
let user = {
name: null,
setUserName(name){
this.name = name;
},
getUserName(){
return this.name;
}
};
user.setUserName("jlc");
console.log(user.getUserName()); // jlc
JavaScript
函数定义不会为参数(parameter
)规定数据类型,参数的改变在函数之外是不可见的
定时器函数
setTimeout
:延迟多长时间执行该函数
setTimeout(function() {
console.log("111");
}, 1000) // 延迟1秒执行该函数
setInterval
:每隔多久执行一次函数
setInterval(function() {
console.log("111");
}, 1000) // 每隔1秒执行该函数
Function()
构造器
函数也可以通过名为 Function()
的内建 JavaScript
函数构造器来定义
var myFunction = new Function("a", "b", "return a * b");
var x = myFunction(4, 3);
函数的作用域
局部变量:变量在函数内声明,变量为局部变量,具有局部作用域,只能在函数内部访问,因为局部变量只作用于函数内,所以不同的函数可以使用相同名称的变量,局部变量在函数开始执行时创建,函数执行完后局部变量会自动销毁
全局变量:变量在函数外定义,即为全局变量,全局变量有全局作用域: 网页中所有脚本和函数均可使用全局变量,如果变量在函数内没有声明(没有使用
var
关键字),该变量为全局变量
在JavaScript
中,函数内部的局部变量通常不可以直接被外部作用域访问,但有几种方式可以将函数内的局部变量暴露给外部作用域:
- **通过全局对象:**在函数内部,可以通过将局部变量赋值给
window
对象的属性来使其成为全局可访问的。例如,使用window.a = a;
语句,可以在函数外部通过window.a
访问到这个局部变量的值 - **定义全局变量:**在函数内部不使用
var
、let
或const
等关键字声明变量时,该变量会被视为全局变量,从而可以在函数外部访问。但这种做法通常不推荐,因为它可能导致意外的副作用和代码难以维护 - **返回值:**可以通过在函数内部使用 return 语句返回局部变量的值,然后在函数外部接收这个返回值,这样,虽然局部变量本身不会被暴露,但其值可以通过函数调用传递到外部
- 闭包:
JavaScript
中的闭包特性允许内部函数访问外部函数的局部变量,即使外部函数执行完毕后,其局部变量仍然可以被内部函数引用 - **属性和方法:**定义在全局作用域中的变量和函数都会变成
window
对象的属性和方法,因此可以在调用时省略window
,直接使用变量名或函数名
全局函数
当创建完函数后,默认情况会将函数压入到window
全局对象当中(window
也能调用该函数)
function hd(){
console.log("jlc");
}
window.hd(); // jlc
这种情况是不合适的(万一window
下有同名称的方法,那么声明函数后,原方法就会被覆盖),这种情况是历史的遗留问题,所以建议创建函数的时候,不要将函数进行独立存放,一般是将其编到类中进行存放
函数表达式(匿名函数)
匿名函数:没有给函数体声明函数的名字;匿名函数在对象方法中使用较多(箭头函数)
JavaScript
函数也可以使用表达式来定义,函数表达式可以在变量中存储:
var x = function (a, b) {return a * b}; // 将匿名函数的返回值赋值给了变量
var z = x(4, 3);
let cms = function() {
console.log("jlc");
};
cms(); // jlc
// 这样就不能通过window.cms()进行调用了,但是如果通过var声明,那还是可以通过window.cms()进行调用的
函数的提升
Hoisting
是 JavaScript
将声明移动到当前作用域顶端的默认行为,Hoisting
应用于变量声明和函数声明
所以,JavaScript
函数能够在声明之前被调用,但是,使用表达式定义的函数(匿名函数)不会被提升
函数是对象
JavaScript
中的 typeof
运算符会为函数返回 "function
",但是最好将JavaScript
函数描述为对象(引用类型),JavaScript
函数是有属性和方法的
arguments
JavaScript
函数有一个名为arguments
的内置对象,arguments
对象包含函数调用时使用的参数数组,调用时所有的参数值都被放入这个列表中,这样就可以在声明函数的时候不设置参数了(很多情况下参数的数量是不确定的,这种情况下可以使用arguments
进行处理)
x = findMax(1, 123, 500, 115, 44, 88);
function findMax() { // 括号中不用设置参数了
var i;
var max = -Infinity;
for (i = 0; i < arguments.length; i++) {
if (arguments[i] > max) {
max = arguments[i];
}
}
return max;
}
以上函数不属于任何对象,但是在JavaScript
中,始终存在一种默认的全局对象,在 HTML
中,默认全局对象是 HTML
页面本身,所有上面的函数“属于”HTML
页面,在浏览器中,这个页面对象就是浏览器窗口,myFunction()
和 window.myFunction()
是同一个函数
// arguments.length 会返回函数被调用时收到的参数个数
function myFunction(a, b) {
return arguments.length;
}
myFunction(1); // 1
arguments
是伪数组:
function sum() {
return arguments.reduce((a, b) => a + b); // 报错
return [...arguments].reduce((a, b) => a + b); // 正确
}
console.log(sum(1, 2)); // 3
目前,在新版本的js
中,一般通过点语法来代替arguments
方法来接收数组:
function sum(...args) {
console.log(args);
return args.reduce((a, b) => a + b);
}
sum(1, 2, 3);
// [1, 2, 3]
// 6
toString()
方法以字符串返回函数
var txt = myFunction.toString(); // 返回的是声明函数的整段字符串
// 'function myFunction(a, b) {\n return arguments.length;\n}'
定义为对象属性的函数,被称为对象的方法
立即执行函数
函数自动调用表达式(立即执行函数:不需要刻意的调用):(function(){})()
封装在括号里面的子函数名就不属于全局了
函数的参数
形参和实参
function sum(a, b) {
return a + b;
}
console.log(sum(1, 2));
// 1和2为实参,具体要进行计算的参数
// a和b为形参,需要由实参进行赋值的
一般情况下,实参的数量要对应形参的数量,如果形参数量多于实参,多余的形参为undefined
;如果实参的数量多余形参,不影响函数的使用,多余的实参会被忽略掉
默认参数
function sum(a, b = 2) {
return a + b;
}
console.log(sum(1)); // 3
// 此时b就是默认参数,如果调用函数时,没有给定b,那么b就使用默认的值,如果给定了b,那就使用给定的值
默认参数要放在所有形参的后面
函数参数
函数作为参数进行传递,函数参数是不加括号的
let arr = [1, 2, 3, 4, 5, 6, 7].filter(hd);
function hd(a) {
return a <= 3;
}
console.log(arr); // [1, 2, 3]
箭头函数
箭头函数允许使用简短的语法来编写函数表达式
// 正常匿名函数编写方式
let hd = function() {
return 1 + 2;
};
// 箭头函数的简写方式
let hd = ()=>{
return 1 + 2;
};
具体例子:
let hd = [1, 2, 3, 4, 5, 6].filter(function(value) {
return value <= 3;
});
// 箭头函数
let hd = [1, 2, 3, 4, 5, 6].filter((value) => {return value <= 3;});
// 第一步优化:如果只有一个参数时,可以不用括号包裹,如果一个参数都没有的话,就要带一个括号()
let hd = [1, 2, 3, 4, 5, 6].filter(value => {return value <= 3;});
// 第二步优化:如果只有一行的表达式,可以不加花括号,return和分号
let hd = [1, 2, 3, 4, 5, 6].filter(value => value <= 3);
// 虽然没有加return,但是要知道会进行return
箭头函数看起来非常简洁,但是箭头函数不是万能的,它也有缺点,它不能完全的替代传统的function
关键字,比如说在递归函数(箭头函数没有函数名字,在做递归回调处理的时候也是不方便的),构造函数和事件处理器的时候,是不方便使用箭头函数的,因为那个时候要考虑作用域和this
关键词
箭头函数没有自己的
this
,它们不适合定义对象方法箭头函数未被提升,它们必须在使用前进行定义
箭头函数形式函数自调用(立即执行函数):
(()=>{})()
递归函数
递归的原理:不断重复的做一件事情,同时找到一个合适的时机去退出递归的过程
递归的应用(一般用于重复数量不定的情况),如数的阶乘:
function factorial(num) {
if(num == 1){
return 1;
}
return num * factorial(num - 1);
}
// 函数精简:
function factorial(num) {
return num == 1 ? 1 : num * factorial(--num);
}
console.log(factorial(5)); // 120
// 通过递归来进行求和
function sum(...args){
if(args.length == 0){
return 0;
}
return args.pop() + sum(...args);
}
console.log(sum(1, 2, 3)); // 6
回调函数
回调函数:在其他函数中调用的函数
let hd = [1, 2, 3, 4];
hd.map(function(value, index, array){
array[index] += 10;
});
console.log(hd); // [11, 12, 13, 14]
map()
是一个对象函数,map
里面的function
匿名函数,其实就是一个回调函数(在其他函数中又调用的函数)
函数中的展开语法/点语法
展开语法的功能是收和放
let hd = [1, 2, 3];
let [a, b, c] = [...hd]; // 展开语法作为值(在等号的右边),这时候的功能就是放;
console.log(a, b, c); // 1 2 3
let [a, ...edu] = [1, 2, 3, 4]; // (在等号的左边),这时候的功能就是收;
console.log(edu); // [2, 3, 4]
函数中使用展开语法:
function sum(...args){
console.log(args); // [1, 2, 3, 4]
return args.reduce((a ,b) => a + b);
}
console.log(sum(1, 2, 3, 4)); // 10
在函数中,展开语法和默认参数都是放在形参的后面的,一般展开语法放在默认参数的后面
this
关键词
在 JavaScript
中,被称为 this
的事物,指的是“拥有”当前代码的对象,this
的值,在函数中使用时,是“拥有”该函数的对象
this是关键词,无法改变this的值
this
表示对当前对象的引用:
let hd = {
name: "jlc",
show: function(){
function render(){
console.log(this); // 这里的this指的是window
}
// 如果使用hd.name,当对象的名字改变,这里也要跟着改变,如果方法一多,就会很麻烦
// this就是当前的对象{name: "jlc", show: f}
return this.name;
}
};
console.log(hd.show());
// 总结:如果函数是对象的属性(类方法),那么this就是当前的对象;如果只是在普通的函数中使用this,那么this就是全局对象window(可以在函数的外部加上const self = this;)
当不带拥有者对象调用对象时,this
的值成为全局对象,在 web 浏览器中,全局对象(window)就是浏览器对象
在全局环境下,this
就是window
// 将列表的每个元素前面加上jlc-
let Lesson = {
title: "jlc",
lists: ["js", "css", "mysql"],
// 写法一:
show: function(){
const self = this; // 为了使不是方法的函数可以使用this
return this.lists.map(function(value){
return `${self.title}-${value}`;
});
}
// 写法二:
show: function(){
return this.lists.map(function(value){
return `${this.title}-${value}`;
}, this); // map()方法特有的第二个参数的传递,将this传入
}
};
箭头函数给this
带来的变化
在this
的指针上,箭头函数会有些不同,箭头函数中的this
,指向的是父级作用域中的this
(上下文)
// 通过箭头函数,将列表的每个元素前面加上jlc-
let Lesson = {
title: "jlc",
lists: ["js", "css", "mysql"],
show: function(){ // 箭头函数中的this,指向父级作用域中的this
return this.lists.map((value) => `${this.title}-${value}`);
}
};
箭头函数中使用this
也会有不方便的地方:
<!--需求,取按钮中的值,加上Dom中site的值-->
<body>
<button>my name is jlc</button>
</body>
<script>
let Dom = {
site: "jlc",
bind: function(){
const button = document.querySelector('button'); // 找到按钮元素
console.log(button); //<button>my name is jlc</button>
button.addEventListener('click', function(){ // 给按钮加上点击事件,点击时执行函数
// 点击函数是作为按钮的方法,this指向的是button
console.log(this); //<button>my name is jlc</button>
});
//通过普通函数实现上述的功能
const self = this;
button.addEventListener('click', function(){
console.log(this); //<button>my name is jlc</button>
console.log(self.site + this.innerHTML); //jlcmy name is jlc
});
//如果将点击函数换成箭头函数,那么this就会变成上下文,会变到父级作用域中的this
button.addEventListene('click', () => {
console.log(this); //{site: "jlc", bind: f}
console.log(this.site); //jlc
});
// 为了实现上述的功能,箭头函数的this不能指向标签元素,需要对其进行改进
button.addEventListene('click', event => {
console.log(event.target); //<button>my name is jlc</button>
console.log(this.site + event.targe.innerHTML); //jlcmy name is jlc
});
}
};
Dom.bind();
</script>
也可以在对象函数中写一个方法,进行绑定:
let Dom = {
site: "jlc",
handeEvent: function(event){ // 绑定的特殊方法,通过this进行调用
console.log(this); // 指向Dom对象 {site: "jlc", handeEvent: f, bind: f}
console.log(event.targe); // <button>my name is jlc</button>
console.log(this.site + event.targe.innerHTML); //jlcmy name is jlc
},
bind: function(){
const button = document.querySelector('button'); // 找到按钮元素
button.addEventListener('click', this); // 点击按钮时,会自动调用handeEvent方法
}
};
Dom.bind();
总之,如果函数中需要大量的调用html元素,推荐使用普通函数的方法;如果需要大量的调用父级的方法,推荐使用箭头函数,但是最好的情况是,能使用箭头函数就使用箭头函数
函数方法
构造函数:this最开始的时候是空的,通过构造产生出一个对象
function User(name){
this.name = name;
} // 一开始的时候this是空的,通过上述的构造产生了一个空的对象{}
let lisi = new User("李四");
console.log(lisi); // 加工完后; {name: "李四"}
this是可以被改变的,apply,call,bind方法都是可以改变this的
let myAge = {age: "24"};
User.call(myAge, "我的年龄"); // 通过call方法调用构造函数,第一个参数是要改变的this,此时this不为空
console.log(myAge); // {age: "24", name: "我的年龄"}
apply
和call
apply
与call
方法的区别仅仅在于传递参数的时候不同
// 定义两个对象
let lisi = {
name: "李四"
};
let wangwu = {
name: "王五"
};
function User(web, url){
console.log(web + url + this.name);
}
//call的作用:1.将对象传递给了this,改变了this的指针;2.它会立刻执行这个函数
//第一个参数是指要改变this指针的对象,后面的就是要传递的参数,call方法多个参数要用逗号相隔,apply方法传递一个数组即可,会将参数分布到各个形参当中,两个方法都会立刻执行
User.call(lisi, "百度", "baidu.com"); //百度baidu.com李四
User.call(wangwu, ["百度", "baidu.com"]); //百度baidu.com王五
//apply和call的区别就是:一个要方括号,一个不需要
apply
和call
方法的应用:
<!--点击不同的按钮弹出按钮的名称-->
<body>
<button>jlc123</button>
<button>jlc456</button>
</body>
<script>
function show(){
alert(this.innerHTML); // this指向window
}
let buttons = document.querySelectorAll("button");
for(let i = 0; i < buttons.length; i++){
buttons[i].addEventListener("click", event => {
show.apply(event.target); // 我们需要将this进行改变,使其指向到按钮对象
});
}
</script>
我们知道数值是可以通过数值方法Max()
获取最大值的,但是对于一个数组,我们是不能通过Max()
来获取最大值,为了解决这个情况,我们可以通过使用apply()
方法来使数组变成多个参数:
// 数组借助数学运算,来获得元素中的最大值
let arr = [1, 2, 3, 4, 5];
// 方法一:将数据展开
console.log(Math.max(...arr)); // 5
// 方法二:通过apply方法传入数值,键数组拆分
console.log(Math.max.apply(Math, arr)); // 5
// 注意:该情况使用call()方法是不行的
如果使用apply
和call
方法不传递this时,可以通过输入null
代替:
<!--折叠面板,点击<dt>标签时进行切换和显示隐藏-->
<body>
<dl>
<dt>第一层</dt>
<dd>111</dd>
<dt>第二层</dt>
<dd hidden="hidden">222</dd> <!--默认是隐藏的-->
</dl>
</body>
<script>
function panel(i){
let dds = document.querySelectorAll("dd"); // 找到所有的dd标签
dds.forEach(dd => dd.setAttribute("hidden", "hidden")); // 让所有dd标签都消失
dds[i].removeAttribute("hidden"); // 将选择的标签去掉隐藏
}
document.querySelectorAll("dt").forEach((dt, i) => {
dt.addEventListener("click", () => panel.call(null, i)); // 调用函数不需要传递this
});
// 不传this调用函数和传统的调用函数本质上没有什么区别
</script>
bind
bind
作用上和call
,apply
是一样的,都是用来调用函数,来改变this
的
只不过使用call
和apply
方法是立刻执行的,使用bind
方法时,是不执行的,但是会返回一个新的函数,需要自调用这个函数才能执行
function show(){
console,log(this.name);
}
show.bind({name: "jlc"})();
let a = function(){};
let b = a; // 函数属于对象,是进行传址的
console.log(a === b) // true a和b共用一个内存地址,所以是完全相等的
b = a.bind();
console.log(a === b) // false 将a函数赋值给了b,内存地址改变了
参数传递
因为bind的方法不是立刻执行的,所以对于参数传递,就有两个渠道进行传递
比如call和apply是立即执行的,调用的时候需要及时的将参数放上
但是bind不是立即执行的,可以在调用的时候传参数,也可以在最终返回函数调用的时候传参数
function hd(a, b){
return this.f + a + b;
}
console.log(hd.call({f: 1}, 1, 1)); // 3
let func = hd.bind({f: 1}, 1, 1); // 此时hd函数中a为1,b为1
func(2, 3); // 因为之前bind传递过参数了,后续传参是没用的,hd函数中a还是为1,b还是为1
// 如果let func = hd.bind({f: 1});不传递参数,那么func(2, 3);传递参数是有用的
let func = hd.bind({f: 1}, 1); // 只传递了一个参数,a为1
func(2, 3); // 传递另一个参数,bhd函数中,b为2,3就没用了
bind
的优势在于,之后的函数调用有用
<!--点击按钮,显示一段话加按钮文本,点击按钮后显示:baidu.comjlc-->
<body>
<button>jlc</button>
</body>
<script>
document.querySelector("button").addEventListener("click", function(event){
document.write(this.url + event.target.innerHTML);
}.bind({url: "baidu.com"}));
</script>