文章目录
一、定义函数
1.函数声明
- 语法
function funName(){};
//在一个代码块中,使用函数声明这种形式定义的函数会在所有代码执行之前最先执行
- 问题答疑
if (true) {
function fun() { console.log(2); }
}
fun();//2
//这里的代码块是if代码块,按照(1语法)的注释来说应该是报错的
//答案:未使用var,let,const定义的变量或者函数都默认使用var
//其实上面的fun()完整写法应该是window.fun();
2.函数表达式
- 语法
const funName = function(){};
3.箭头函数
- 语法(箭头函数一般作为参数传递给另一个函数)
const fun = (parameter1,parameter2) => {函数体};
//1.如果函数体有且只有一个retrun语句则可以省略{},只有一个参数可以省略();
const fun = parameter => 函数体;//这个函数体会被求值返回
//2:没有参数的箭头函数()不能丢,必须写上。
const fun = () => 'test'
//3:如果箭头函数的函数体只有一条return语句,并且返回的是对象字面量形式的对象,那么对象字面量必须加{}
//4:当箭头函数函数体有{}时,如果里面只有一条语句,这条语句不会被返回
- 箭头函数从定义自己的环境继承
this
(实例代码在嵌套函数) - 箭头函数没有
prototype
属性(待补充)
4.嵌套函数
- 嵌套的内部函数可以修改和访问外部函数的参数
function outrer(x, y) {
function inner() {
[x, y] = ['a', 'b']
}
inner();
console.log(x);//a
console.log(y);//b
}
outrer(1, 2);
- 嵌套函数不会继承包含函数的
this
,而是根据调用者来确定this
解释:函数是一个对象,而嵌套函数可以看作是包含函数的属性。嵌套函数当做方法调用时,this就是
调用它的对象(或函数)。当嵌套函数作为普通函数调用时,this就是window。如果嵌套函数继承了this,
那当作普通函数调用的时候this也是包含函数的this而不是window,这是不合理的
注意:以上解释不包括箭头函数,箭头函数的this是它定义环境的this。可以使用这个特点来解决上述缺陷
------------------------------------------------------------------
let obj = {
m: function () {
let self = this;
console.log(this === obj);//true
fun();
/*想在嵌套函数中使用this必须把this赋值个一个变量*/
function fun() {
console.log(this === obj);//false
console.log(self === obj);//true
}
----------------------使用箭头函数解决this不同的问题----------------
let fun = () => {
console.log(this === obj);//true
}
fun();//函数表达式不会被提升,所以执行需要放在定义之后
}
}
二、调用函数的五种方式
1.作为函数调用
function funName(){};
----------------------
funName();
- 非严格模式下,'函数调用’的
this
是window
。严格模式下则是undefined
。可以使用this
来判断是否处于严格模式
2.作为方法调用
- 当一个函数是对象的属性时,我们称这个函数为方法。此时的
this
是调用它的对象
obj.funName();
或者
obj['funName']()//这种也可以调用,但是好奇怪
- 小技巧:当写一个方法不需要返回值的时候,可以让考虑它返回
this
(确实秒,背下来。)
想象下列情景:某一个对象,我们需要调用它5个不同的方法。此时需要写5行代码
obj.funName1();
obj.funName2();
obj.funName3();
...
----------------------------------------------------------------------
此时我们给每一个方法返回一个this则可以使用方法调用链,类似jQuery的链式调用
obj.funName1().funName2().funName3()...;
3.作为构造函数调用
解释:不做笔记,几乎不用。创建对象使用对象字面量语法较多,不会使用构造函数。
4.间接调用
- 函数也有方法:
call()
和apply()
,在’函数的方法’解释
5.隐式函数调用
- 举个例子:当进行字符串拼接式会隐式调用
toString()
,等等…
三、函数的实参与形参
1.可选形参与默认值
- 实参少余形参,多余的形参值为
undefined
- 在
ES6
之后,可以直接在定义形参的时候赋默认值,并且可以使用前面参数的值来定义后面参数的值,这种语法在箭头函数中也可以使用。
function fun(para = 1,para2 = para1*2...) {
console.log(para);//1
}
2.剩余形参与可变长度实参列表
- 剩余形参(restParametert):名词,代表的是一个参数,而不是多个参数
- 剩余形参前面有3个点,而且必须是最后一个参数。调用函数时,实参会按照顺序给形参赋值,多余的实参全部保存在剩余形参即中,剩余形参的值始终为数组(没有多余的参数给它赋值则为空数组),不会为
undefined
。
function fun(first = Infinity, ...restPara) {
let maxValue = first;//此时实参1会赋值给first
for (let n of restPara) {
if (n > maxValue) maxValue = n;
}
return maxValue;
}
console.log(fun(1, 2, 3, 4, 100));//100
// 注意:不能给剩余形参赋默认值。不合法
//也要区分剩余形参...与数组扩展操作符...的区别
- 有剩余形参的函数叫
变长函数
,可变参数函数
等
3.Arguments对象
解释:虽然我也不知道是什么,但是好像没什么用,在ES6
中被淘汰了。
4.数组扩展操作符与剩余形参结合使用
let funName = function (...args) {
console.log(...args);//这里是扩展操作符:1 2 3 4
console.log(args);//[1 2 3 4]
}
funName(1, 2, 3, 4);
5.函数实参解构为形参
解释:感觉好鸡肋
let funName = function ([x, y], [i, j]) {
console.log(x, y, i, j);//1,2,3,4
}
funName([1, 2], [3, 4])
5.参数类型
- JavaScript不会检查函数参数的类型和长度,会按照程序期望的数据类型进行转换。转换失败则报错
四、闭包
- 把函数对象与它的作用域组合绑定起来解析变量的机制叫闭包(看3的代码解释)
JavaScript
函数使用的是定义时的作用域,而不是调用时的作用域。它会捕获定义自身作用域所在的变量绑定。
--------------------------------------------------------
例1
let str = "outTer";
function funName() {
let str = "inner";
function fun() {
return str;
}
return fun();
}
let s = funName();//inner
--------------------------------------------------------
例2
let str = "outTer";
function funName() {
let str = "inner";
function fun() {
return str;
}
return fun;
}
let s = funName()();//inner
//返回一个函数fun,然后在外部执行,但是fan函数是在funName内部定义的。所以使用funName的函数
//作用域,在这个函数作用域中,捕获到变量str并与之绑定。即使最后调用是在外部也不会改变str是
//内部的str
- 每调用函数一次都会创建一个新的函数作用域以及作用域中的变量等代码,并且这两个函数作用域不会相互影响
function counter() {
let n = 0;
return {
count: function () { return n++; },
reset: function () { n = 0; }
}
//解释什么是闭包:这里的count和reset函数的作用域是counter函数作用域,count和reset函数
//绑定了counter作用域的n变量。对应了'把函数对象与它的作用域组合绑定起来解析变量的机制叫闭包'
}
let c = counter();
let d = counter();
//这里调用了两次函数,但是它们的n变量不会相互影响,因为创建了两个函数作用域(理解为两个函数对象)。
//c函数作用域的变量n进行自增或者重置操作并不会影响d函数作用域。看似共享了一个同一个变量,其实
//作用域是分开的。
五、补充get和set的两种写法
1.普通写法
{
get funName(){},
set funName(newValue){},
---------------使用---------------------------
获取:obj.funName
设置:obj.funName = value;
}
2.使用对象扩展语法(这种语法更清晰)
这种语法适合近学习过Java的人使用,不然容易混淆
{
getName(){},
setName(newValue){},
---------------使用---------------------------
获取:obj.getName();
设置:obj.setName();
}
//这种写法其实与key:value一样,只不过对象扩展语法允许通过上面这种方式简写。
完整写法:
getName:function(){},
setName:function(newValue){}
六、函数的属性,方法以及构造器
1.函数的属性
- length:表示函数的形参个数,但不包括剩余形参
- name:函数名
- prototype:函数原型,箭头函数没有这个属性。
2.函数的方法
call()
和apply()
的第一个参数是调用者,它会变成函数体内部的this
值。可以不传。不传就是window
。
解释:`call()`和`apply()`方法允许间接调用函数,就像这个函数是某个对象的方法一样。
function funName(x, y) {
console.log(this);//this是obj
console.log(x);//1
console.log(y);//2
}
let obj = { x: 1 };
funName.call(obj, 1, 2);
funName.apply(obj, [1,2]);
//箭头函数的this是定义它的环境,并不能通过这种方式重写this。
//调用的时候第2到n个参数都会作为函数的参数使用
//调用apply不同,调用它时第2个参数参数需要填数组
bind()
方法。主要作用是把函数绑定到对象,此时this
就是绑定的对象。不会因为把这个函数作为其他对象的方法而改变this
,对箭头函数无用,箭头函数的this
是定义它的环境。
function funName() {
console.log(this);
}
let obj = {};
let newFun = funName.bind(obj);//把funName绑定到obj,调用bind方法会返回一个新函数
newFun()//输出this 是 obj
let newObj = {newFun}//把新函数作为新对象的方法
newObj.newFun();//此时this还是obj而不是newObj。不会改变
解释:
(1)不管是作为函数调用新函数还是作为方调用新函数其this永远都是第一次绑定的对象;
问题:
(1)调用新新函数和旧函数有什么关系,为什么旧函数执行了?
个人理解:
newFun()当作函数的形式调用可以看作第一次绑定的对象在把旧函数当作方法调用,即: obj.funName();
我估计应该是在函数在第一次绑定对象的时候返回的新函数记住了这个对象,并用了一个变量保存起来当作this
(不然解释不通为甚新函数当作其他对象的方法调用的时候this永远是第一个绑定的对象),然后每次调用这个新函数,
在新函数的内部就调用旧函数并把参数和保存起来的this即第一个对象传递过去
//以上个人理解如有错误请指正!
toString()
返回函数源代码- 以及构造函数。