6.函数
函数的天生属性length
函数的length等于行参的个数
function fn(a,b,c,d,e){}
console.log(fn.length) //5
函数执行过程
-
为函数创建一个执行环境
-
复制函数的 [[scopes]] 属性中的对象构建起执行环境的作用链域
-
创建函数活跃对象并推入执行环境作用链域的前端
-
执行代码
-
销毁执行环境和活跃对象(闭包情况下活跃对象仍被引用没被销毁)
函数的声明方式
表达式声明
表达式声明时,其实分为两步,先声明变量,再把函数赋值给这个变量,后面的函数作为值并不会发生声明提升
var fn=function (){
}
var fn =function fn(){
}
字面量声明
function fn(){
}
函数的参数
注意:
形参可以理解为在函数体内部声明了一个新的变量
形参与实参的关系可以理解为形参=实参(注意:实参是引用数据类型时,实际传的是引用数据类型的地址)
var a=1;
function fn(a){ //形参可以理解为var a=1;
a=2; //形参a重新赋值为2
}
fn(a)
var a=[1]; //假设[1]的地址为0X100,0x100指向[1];
function fn(a){ //形参可以理解为var a=0X100;
a=2; //形参a重新赋值为2
//假设[2]的地址为0x200
a=[2]; //形参a重新赋值为0x200
}
fn(a)
参数分类
函数的参数分为形参和实参:
形参可以理解为在函数体内部声明了一个新的变量
,形参的值可以理解为定义了一个新变量接收了实参的值(或地址)
实参是调用作用域内对应的同名数据(可以是任何类型),也可以是手动输入的数据,实参传递给函数时,函数体内部有个arguments属性会将实参收集起来组成一个类数组
形参和实参可以个数不相等,当形参的个数大于实参时,多余的形参为undefined,当实参的个数大于形参时,除了arguments有变化外没有任何影响
var a=1,b=1;
function fn(a,b){
}
fn(a,b) //把变量a和b的值当作实参传给形参
//类似这样
function fn(){
var a=a,b=b; //把变量a,b的值赋值给形参
}
函数的参数表达式
函数的形参如果有实参传值则为实参的值,如果没有则为表达式的值
var a=1,b=2;
function fn(c=a?b:a,d=b){
console.log(c,d);
}
fn(a)
函数的返回值
函数的return可以理解为函数调用表达式==return;
闭包函数
闭包是什么?
深层理解:
闭包是拥有外层函数对象
所对应的活动对象引用
的函数对象
一般理解:
闭包是嵌套函数的内部函数,包含外部函数的变量(对象)的函数对象
怎么判断闭包?
内部函数包含外部函数的变量,这个内部函数被返回并调用(通常会被赋值给别人);
闭包的形成
https://zhuanlan.zhihu.com/p/66701792
函数调用的时候会在栈空间开辟一块空间,这个空间叫函数执行环境,一般函数调用完毕出栈后,其内的活跃对象会被垃圾回收机制回收,但是当这个活跃对象被其他函数的scope引用时,活跃对象将无法释放,这样就形成了闭包(此时这个活跃对象只能被闭包函数找到)
闭包函数的执行过程
当闭包函数调用进栈时,产生一个新的活跃对象,这个活跃对象scope属性的__parent__
指向外部函数产生的活跃对象,当我们要修改某个变量(或引用类型)的时候,在当前作用域找不到,就往上一级找,一直到找到后修改这个变量(或引用类型)
当闭包调用结束出栈,闭包自身的活跃对象会被销毁,但是外部函数的活跃对象仍然被闭包函数自身的scope引用,依旧无法释放.
闭包代码加图分析
function addAge(){
var age = 21;
return function(){
age++;
console.log(age);
}
}
var clourse = addAge();
clourse();
第一阶段
第二阶段
第三阶段
第四阶段
第五阶段
递归函数
定义
直接或间接的调用自身的函数称为递归函数
分析
首先,函数的return可以理解为函数调用表达式==return;
分析如下代码:
function fn(n){
if(n==1){
return n;
}
return n+fn(--n);
}
console.log(fn(5));
函数执行过程为:
fn(5)=5+fn(4);
fn(4)=4+fn(3);
fn(3)=3+fn(2);
fn(2)=2+fn(1);
fn(1)=1;
//综上所述
fn(5)=5+4+3+2+1;
回调函数
什么是回调函数?
一个函数(假设为a)被作为参数传递给另一个函数(假设为b),a函数在b函数中被调用。这个a函数就是回调函数
回调函数特点
(1)自己定义的函数
(2)你没有调用
(3)最终它执行了
function fn(){
}
function fn1(func){
func();
}
fn1(fn); //这个fn就是回调函数
特殊的回调函数
dom0的事件回调函数是以以下形式写的
on事件=事件回调
构造函数
构造函数通过new关键字调用
new关键字的执行顺序
new关键字一直到遇到的第一个()为一个表达式,如果没有碰到括号,则默认调用表达式最后的方法
new a.b.c;
//等同于
//new a.b.c();
new new a.b().c();
//等同于
new ( new a.b() ).c();
new Person()
我认为的new Person()执行过程
1.new person()开辟一块执行环境
2.以person的prototype为原型创建一个对象
3.改变this指向,指向第二步创建的对象,调用person方法同时传参
4.person调用结束拿到person返回值同时销毁person的函数执行环境,判断person的返回值是否是引用数据类型,不是就返回第二步创建的对象(实例),执行完毕销毁执行环境.
构造函数当成普通函数图解
new做的事情图解
? 1、开辟内存空间(堆)
? 2、this指向该内存(让函数内部的this)
? 3、执行函数代码
? 4、生成对象实例返回
new关键字做了什么:
1.以构造函数的prototype属性为原型,创建新对象;
2.使用指定的参数调用构造函数,并将 this
绑定到新创建的对象
3.如果构造函数没有手动返回对象(引用类型),则返回第一步创建的对象(实例),否则返回手动设置的返回值
实现一个简单的new方法
// 构造器函数
let Parent = function (name, age) {
this.name = name;
this.age = age;
};
Parent.prototype.sayName = function () {
console.log(this.name);
};
//自己定义的new方法
let newMethod = function (Parent, ...rest) {
// 1.以构造函数的prototype属性为原型,创建新对象;
let child = Object.create(Parent.prototype);
// 2.使用指定的参数调用构造函数,并将 `this` 绑定到新创建的对象
let result = Parent.apply(child, rest);
// 3.如果构造函数没有手动返回对象(引用类型),则返回第一步创建的对象(实例),否则返回手动设置的返回值
return typeof result === 'object' ? result : child;
};
//创建实例,将构造函数Parent与形参作为参数传入
const child = newMethod(Parent, 'echo', 26);
child.sayName() //'echo';
//最后检验,与使用new的效果相同
child instanceof Parent//true
child.hasOwnProperty('name')//true
child.hasOwnProperty('age')//true
child.hasOwnProperty('sayName')//false
child.__proto__===Parent.prototype//true
let a=1,b=2,c=3;
function fn(a,b,c){
}
function new1(...str){
//1.创建一个空的简单JavaScript对象(即`{}`)
let obj={};
//2.为步骤1新创建的对象添加属性`__proto__`,将该属性链接至构造函数的原型对象
obj.__proto__=fn.prototype;
//3.将步骤1新创建的对象作为`this`的上下文
let result=fn.call(obj,...str);
//4.如果该函数没有返回对象,则返回`this`
return result instanceof Object==='object'?fn():obj;
}
let fn1=new1(a,b,c);
console.log(Object.getPrototypeOf(fn1)===fn.prototype);