是对象,是一个引用数据类型
1.函数的本质
首先我们知道函数是引用类型数据,使用函数是为了提高代码的复用性
从本质上讲,函数是一种特殊对象(数组也是一种特殊的对象),函数内部数据私有。
👇这部分关于原型的问题先不细说,总之理解函数的本质是对象即可。
prototype:每个函数都有一个
prototype
属性,默认指向一个空对象(原型对象)给原型对象添加属性(一般添加方法)作用:函数的所有实例对象自动拥有原型中的属性(方法)
__proto__: 每个实例对象都有一个
__proto__
属性,该属性指向当前实例对象的原型对象(隐式原型对象)
//默认情况下,一个函数对应两块内存,一块是函数本身,一块是函数原型。一个函数都会与一个原型函数与之对应,函数中有个指针prototype指向原型对象,原型对象中有个constructor指向函数,你中有我,我中有你。
function foo(){}
foo.prototype.constructor===foo; //true
console.log(foo.prototype); //空对象,即原型对象
👇 函数内存图解:
👇 原型继承图解
紫色画笔部分
2.函数分类
本质上是完全一样的,唯一的区别在于调用方式。
- 普通函数【方法】
- 构造函数【类-在面向对象中学习】(为了区分将构造函数的函数名大写)
3.函数定义
函数存在变量提升,但需要注意两种方式下变量提升的区别(匿名函数不会被提升)
方式一--函数声明写法
function 函数名(形参){}
//-------------------------------------
function add(int a,int b){} //❌
function add(a,b){} //✅
//-------------------------------------
let result=add(1,2);
function add(a,b){
return a+b
} //✅
//由于存在变量的提升,整个函数都被提升
方式二--函数表达式写法
(即匿名函数,类似于普通赋值表达式)
bar(); //bar is not defined
//-------------------------------------
add();
var add=function(a,b){
return a+b
} //❌ add is not a function
//-------------------------------------
var add; //undefined
add(); //❌报错
add=function(a,b){
return a+b
}
//由于存在变量的提升,只提升了变量
4.函数调用
本质上任何函数的执行都是某个对象调用的
1.构造函数调用 通过new来调用
function Person(name,age){
this.name=name;
this.age=age;
}
var people1= new Person('kobe',42);
new 操作符 的相关 👉 【手写代码】new 操作符_Chailo的博客-优快云博客
2.普通函数
1.函数自调用
add(1,2);
2.回调函数
作为参数的函数。自己定义但没有调用,在一定条件下最后执行了。(同步没有回调,异步一定会回调)
例子🌰:DOM事件回调、定时器回调、Ajax回调函数
document.getElementById('btn').onclick=function(){
console.log('事件回调');
}
setTimeourt(function(){
console.log('定时器回调');
},1000)
3.强制绑定this的调用
👉【手写代码】call,apply,bind 的区别和实现原理_Chailo的博客-优快云博客
- 函数名.call(this,实参列表);
add.call(this,1,2);
- 函数名.apply(this,实参数组)
add.apply(this,[1,2]);
- 函数名.bind(this,实参列表);
add.bind(this,1,2);
var obj = {
name:'chailo',
age:'12'
}
function fun(msg){
this.msg = msg;
console.log(this);
console.log(msg);
}
fun(); //自调用 this-->window
fun.call(obj,'call传入参数');
fun.apply(obj,['apply传入参数']);
fun.bind(obj,'bind传入参数')();
//bind绑定完this不会立即调用函数,而是将函数返回
//-->等价于
var fun2=fun.bind(obj,'bind传入参数');
fun2();
4.IIFE 立即执行函数(匿名函数)
特点:①代码执行到函数位置,不会被提升 ②只执行一次 ③内部数据私有
IIFE的优点:
- 创建块级作用域(隐藏内部实现),避免了多人开发中全局变量和函数的命名冲突(不污染外部命名空间 );
- IIFE中定义的任何变量和函数,都会在执行结束时被销毁。因此可以减少闭包占用的内存问题
5.函数内部属性
只有在函数的执行过程中,内部属性才能被确定: argument,this,形参
1.arguments
保存函数的所有实参,是一个类数组对象
arguments.callee() 指向当前函数,常用于递归函数。但是在严格模式下无法使用。
👇 补充介绍一些类数组对象(我当时的学习笔记真的好细节,不愧是我!给个大拇指吧的👍🏾)
//类数组对象:访问方式和数组相似,但不是数组
var arr={
"0"="terry"; //属性名加"":当出现特殊字符
"1"="lerry";
"2"="tom";
length:3
}
Object.defineProperty(arr,'length',{
configurable:true;
enemerable:false;
value:3
}) //使length迭代不出来,但是可以访问到
for(var k in arr){ //迭代
console.log(k,arr[k]);
}
Array.isArray(arr); //判断是否为数组
var foo=function(a,b){
console.log(a,b); //只输出 1,2
console.log(arguments); //输出1,2,3,4,∵形参只能访问到部分,使用arguments可以访问所
for(var i=0;i>arguments.length;i++){ //输出1,2,3,4
console.log(arguments[i]);
}
}
foo(1,2,3,4);
实际应用:
//n的阶乘,递归
function foo(num){
if(num===1){
return 1;
}else{
return foo(num-1)*num;
}
}
--------------使用arguments.callee(),但是在严格模式下受限制,无法使用
function bar(num){
if(num===1){
return 1;
}else{
return arguments.callee(num-1)*num;
//∵函数名是可能改变的,两处函数名必须一样,所以可以使用arguments.callee()
//哈哈哈原来如此,当时的我好细节哦,要是没这标记我现在也不知道原因
}
}
2.this
内置变量,用于指向一个对象,this的指向与调用方式有关。
- 函数自调用this-->window
- 如果使用
()
调用函数,查看()
前是否是函数名。如果是,查看函数名前是否有.
,,有则指向.
前面的那个对象没有则this
指向全局对象。 - 构造函数的this指向当前构造函数的实例对象
var fn1 = new fn();
this-->fn1
- 箭头函数的this看外层是否有函数。如果有,箭头函数的this=外层函数的this;如果没有,this--->window
- 使用call,apply,bind强制绑定this 👉【手写代码】call,apply,bind 的区别和实现原理【手写代码】call,apply,bind 的区别和实现原理
(箭头函数是一种特殊的函数形式后文会介绍,别急,宝~)
//举几个例子🌰-----------------------------
function foo(){
console.log(this); //全局 global
}
//相当于
var foo=function(){} //引用型,把一个匿名函数赋值给变量
//🌰-------------------------------------
var obj={
name:"terry";
foo:foo;
}
console.log(obj.foo===foo); //true
obj.foo(); //this指向obj
//🌰-------------------------------------
var arr=[1,2,3,foo];
arr[3]();
//🌰-------------------------------------
var a=1;
function test(){
var a=2;
function bar(){
var a=3;
console.log(this.a); //global Undefined/window 1
}
bar(); //∵this的取值跟调用方式有关,bar()的调用方式,函数名前没'.',指向全局变量。
}
6.箭头函数
是一种特殊的函数形式,多用来定义回调函数(使用场景)
this属性:箭头函数没有自己的this,箭头函数定义的时候处在的对象就是它的this,不是调用的时候决定的。(上面👆this那一部分有讲到!再来巩固一次)即,箭头函数的this看外层是否有函数。如果有,箭头函数的this=外层函数的this;如果没有,this--->window
//使用场景一及this指向
var foo=function(a,b){
return a+b;
}
var f=fool(1,2);
console.log(f); //结果:3
//写成箭头函数的形式-------------------------------------
var foo=(a,b)=>{ //简写:(a,b)=>a+b;
console.log(this); //结果:node下{}空对象,浏览器下window;∵没有包含该箭头函数的外部函数
return a+b;
}
console.log(foo(1,2)); //结果:3
-----------------------------------------------------------------------------------------
//使用场景二--做回调函数及this指向
var arr=[{name:"teery",age:12},{name:"teery",age:12}];
arr.forEach(function(item){ //遍历数组
console.log(this); //结果很多种,不是自己写的不讨论回调函数的this
console.log(item);
})
//forEach+箭头函数-------------------------------------
function foo(){
var arr=[1,2,3];
arr.forEach((item)=>{
console.log(this); //就是foo的this
console.log(item);
})
}
//对于foo的this又和它的调用方式有关
foo(); //函数调用方式①:foo(实参列表);this为全局变量,∵前面没'.'
foo.call({name:"terry"}); //函数调用方式②:foo.call(this,实参列表);可指定this
7.函数应用
1.作为参数(回调函数---匿名内部类)
2.作为返回值(eg:闭包)
🎉over~
补充:匿名函数这部分 👉JavaScript匿名函数知多少
JavaScript并不是面向对象的,所以它不支持封装。但是在不支持封装的语言里同样可以实现封装。而实现的方法就是匿名函数。
因为没有指向匿名函数的引用。只要函数执行完毕,就可以立即销毁其作用域链了
javascript引擎规定,如果function关键字出现在行首,一律解释成函数声明语句;而函数声明后面是不能跟圆括号的(匿名函数是函数声明的一种)。然而,函数表达式的后面可以跟圆括号。所以可以将函数声明转换成函数表达式。
所以,解决方法就是不要让function出现在行首,让引擎将其理解成一个表达式 最常用的两种办法
(function(){
console.log(123);
}());
(function(){
console.log(123);
})();
(function keith() {
console.log(123);
})()