1.函数的分类
-
命名函数(具名函数)
-
具名函数和script标签同时解析,可以先调用
foo(); //不推荐这样 function foo(){ console.log(1); } //输出结果:1
-
具名函数,函数名的使用范围仅限于函数体内
var foo = function poo() { console.log(1); console.log(poo); } foo(); poo(); //此时会报错 //输出结果:1 ƒ poo() { console.log(1); console.log(poo); } //报错:22 Uncaught ReferenceError: poo is not defined
-
-
匿名函数
-
什么是匿名函数?
function(){ //由于没有名字,该函数无法调用 }
-
匿名函数如何运行?
(function(){ console.loe("匿名函数,立即调用"); //函数在定义的同时,立即执行,也叫做函数的自运行。 })
-
匿名函数无法提前调用
console.log(foo); //var声明并赋值的函数无法提前调用 foo(); var foo = function(){} //输出结果 undefined 提示错误:75 Uncaught TypeError: foo is not a function
-
函数的参数及返回值是什么 ?
参数不做类型检查
-
-
构造函数
当需要从服务器将字符串转为函数时,需要用到构造函数。
一般不使用,因为写入的都是字符串,所以执行时,需要将字符串转为语句块
var fn = new Function("a", "b", "c", "console.log(a+b+c+'d')"); //前面所有的都是函数的参数,最后一个是函数内部的语句块。
-
回调函数
将函数作为参数传入到另一个函数中,在需要执行时触发执行该函数 。
目的:在两个不同的地方,互相不能确定时,进行调用 。
function fn(f) { return f() + 1; } function fn1() { return 1; } console.log(fn(fn1)); // 2
2.函数的声明与赋值
-
函数的声明
function test(){ //声明会被提前,可以在任何地方调用 }
-
函数的赋值
var test = function(){ //只能在该函数以下的位置调用 }
-
两种创建方式的区别?
JS虽然是脚本语言,但是代码也需要编译;在编译的过程中,所有的声明语句,都会被提前
b();//当执行函数b的时候,由于声明被提前,可以执行 a();//当执行函数a的时候,由于a仍未定义,所以执行会报错 function b(){ document.write("aa"); } var a = function(){ document.write("123"); }
-
函数如何与事件结合?
事件 含义 事件 含义 onload 页面加载完成 onfocus 获得焦点 onclick 单击 onblur 失去焦点 ondblclick 双击 onmouseenter 鼠标划入 onkeydown 键盘被按下 onmouseleave 鼠标划出 onkeyup 键盘被松开 onsubmit 表单提交事件 onkeypress 键盘按下并松开
3.函数的参数
在函数外部定义的变量称为全局变量;内部定义的变量叫局部变量。
参数是变量,只作用于函数内部,外部不可以调用
function fn(a, b, c) {
a++;
b++;
c++;
console.log(a + b + c);
}
console.log(a); //报错:a is not defined
fn(1,null,3); //7 当中间的参数为空时候,不能是(1,,3),需要设置为null
console.log(fn.length); //3,输出的是实参的个数
- 不定参函数
function add() {
console.log(arguments); //伪数组,原产地是Object
// 可以当成数组遍历,但是不能完成数组的大部分操作
//(具有数组的数据结构,但是不具备数组配套操作方法的数据结构)
}
add();
// Arguments [callee: ƒ, Symbol(Symbol.iterator): ƒ]
- argument.callee
arguments.callee等于被调用函数本身
function fn(){
console.log(arguments.callee === fn); //true
}
可以用于,在不知道什么函数调用了自己的情况下,再执行一遍函数(例:自调用函数)
(function(){
var i = 0;
if(i < 2)
arguments.callee();
})();
- 获取当前正在执行函数的函数名
arguments.callee.name();
- 通过caller可以追溯到父函数的运行环境(在ES6中被抛弃)
function fn(){
console.log(arguments.callee.caller);
}
function fn1(){
fn(1,2,3,4);
}
fn1(5,6,7);
// [Function: fn1]
- 通过caller可以追溯到父函数的父函数的运行环境
function fn(){
console.log(arguments.callee.caller.arguments.callee.caller);
}
function fn2(){
fn1();
}
function fn1(){
fn(1,2,3,4);
}
fn2(5,6,7);
//ƒ fn2(){
//fn1();
//}
- 用arguments求若干个数中的最大值
function max() {
// 如果参数为0,直接返回
if (!arguments.length) return;
//如果参数为1,返回第一个,无需比较
var max = arguments[0];
if (arguments.length === 1) {
return max;
}
for (var i = 1; i < arguments.length; i++) {
max = max > arguments[i] ? max : arguments[i];
}
return max;
}
console.log(max(3, 1, 4, 2, 5));
-
对象作为函数参数
- 改变返回值
function fn(obj){ obj.a++; obj.b++; console.log(obj.a + obj.b); //5 } var obj1={ a:1, b:2 } fn(obj1); console.log(obj1.a,obj1.b); //2,3
- 不改变返回值
function fn(obj1) { // 这句之前obj和外面的obj引用地址相同 obj1 = { a: 2, b: 3 }; //重新将obj的引用地址改变,这时候就与外面的obj没有任何关系了 console.log(obj1.a + obj1.b); } var obj = { a: 1, b: 2 } fn(obj); console.log(obj); //{a: 1, b: 2}
4.函数的作用域
- 全局中调用this,指向window
var a = 3;
function fn(){
var a = 5;
console.log(this.a + a); //8 ,只适用于ES5
}
- 点击事件的函数中,this是被点击对象
var btns = document.getElementsByClassName("block");
for(var i = 0; i < bns.length; i++){
(function(n){
bns[n].onclick = function(){
// 这里打印的n变量就是自执行函数执行瞬间传入的参数值
console.log(n);
}
})(i)
}
- 对象中的方法,this是该对象
var fn = function(){
console.log(fn); //打印fn这个函数
}
fn();
var obj = {
fn2:function(){
//console.log(fn2); //想要输出fn2属性的值,但是会报错
console.log(this.fn2); //输出fn2这个函数
}
}
obj.fn2();
5.函数的调用
-
变量和函数重名的问题
var a = 10; function a(){} alert(typeof a); //number //以上写法 等价于 以下写法: var a; function a(){} a = 10; // 因此a最终被10覆盖,则为number类型
-
什么是变量声明提升?
var n = 2; (function(){ console.log(n); //undefined var n = 10; console.log(n); //10 })() //以上写法 等价于 以下写法: var n = 2; (function(){ var n; //变量的声明不管哪里写在,都会被提升到该作用域的最前面 console.log(n); //这里应该是undefined n = 10; //这里只是一个赋值动作 console.log(n); //毫无疑问等于10 })()
-
作用域链(向上找寻机制)
从当前作用域向外向上寻找,如果找到了返回结果并终止查找;没有找到则报错。
var a = 10; function foo(){ //var a = 10; //局部变量 console.log(a) //访问就近原则 } foo(); a = 20; //输出结果: 10
-
局部提升优先于整体
var的优先级高于
function
,所以var提升后会被function
覆盖var a = 20; function foo() { console.log(a); //undefined,如果是整体优先于局部提升,此处为20 var a = 10; function a() { } console.log(a); /10 } foo(); //输出结果: //[Function: a] //10
var fn1 = 10; function fn1() { } console.log(typeof fn1)//number
只定义没有赋值,意味着原有栈中fn1的结果没有改变
console.log(fn1) var fn1 = 10; function fn1() { } console.log(typeof fn1) //function
-
回调函数
当外部函数不清楚内部函数要调用的函数是谁,需要用到回调函数
function fn(){ f(); //由外部告知函数f()要调用谁 } function fn1(){ } fn(fn1);//调用者告诉fn()要调用谁;fn()只负责调用,不关心调用谁
function first(b, c, a) { setTimeout(function () { console.log("aaa"); b(c, a, b); }, 1000) } function second(c, a, b) { setTimeout(function () { console.log("bbb"); c(a, b, c); }, 1000) } function third(a, b, c) { setTimeout(function () { console.log("ccc"); a(b, c, a); }, 1000) } first(second, third, first);//循环打印函数
-
递归调用
先递推、再回归
递归调用——深度优先;循环——广度优先;
- 求斐波纳契数列
function fib(n) { if (n === 1 || n === 2) { return 1; } return fib(n - 1) + fib(n - 2); } console.log(fib(3));//8
- 递归调用二叉树
//工厂模式 function createObj(_value) { var o = {}; o.left = null; o.right = null; o.value = _value; return o; } var obj = createObj(1);; obj.left = createObj(2); obj.right = createObj(3); obj.left.left = createObj(4); obj.left.right = createObj(5); obj.right.left = createObj(6); obj.right.right = createObj(7);
- 先序遍历
function leftObj(o) { console.log(o.value); if (o.left) leftObj(o.left); if (o.right) leftObj(o.right); } //1245367
- 中序遍历
function centerObj(o) { if (o.left) centerObj(o.left); console.log(o.value); if (o.right) centerObj(o.right); } //4251637
- 后序遍历
function rightObj(o) { if (o.left) rightObj(o.left); if (o.right) rightObj(o.right); console.log(o.value); } //4526731
-
求最大公约数
- 方法一
function reduce(m, n) { //先算法 var r = m % n; m = n; n = r; if (r === 0) { return m; } else { return reduce(m, n); } //终止条件 } console.log(reduce(30, 5));
- 方法二:更相减损法
更相减损法原本是为了约分而设计的:可半者半之,不可半者,副置分母、子之数,以少减多,更相减损,求其等也。以等数约之。
1:任意给定两个正整数;判断它们是否都是偶数。
若是,则用2约简;若不是则执行第二步。
2:以较大的数减较小的数,接着把所得的差与较小的数比较,并以大数减小数。
继续这个操作,直到所得的减数和差相等为止。
第一步中约掉的若干个2,与第二步中等数的乘积就是所求的最大公约数。function reduce(x, y) { if (x % 2 === 0 && y % 2 === 0) { x = x / 2; y = y / 2; return reduce(x, y) * 2; //注意此处需要*2 } var red = Math.max(x, y) - Math.min(x, y); if (red === Math.min(x, y)) { return red; } else { return reduce(red, Math.min(x, y)); } } console.log(reduce(12, 20));
6.函数执行环境
为了储存局部变量,JS创建出了一个临时对象;我们称之为 AO (Active Object) 活动对象;
内存:垃圾回收机制: 1.IE 计数法; 2.现在浏览器 标记法
闭包:
function outerFn() { // = > AO{a : 10, fn : fn}
var a = 10;
return function innerFn() {
a++;
console.log(a);
}
}
var inner = outerFn();
inner(); //11
inner(); //12
inner(); //13
var inner2 = outerFn();
inner2(); //11
inner2(); //12
inner2(); //13
//执行环境(innerFN)(引用了AO{outerFn .a)
//当内部有函数引用了外部函数定义的变量,外部函数的AO不会被删除;
//闭包的目的 : 把局部变量生命周期变成永久; 数据保密;
//闭包的问题:内存泄漏 (内存之中的数据没有可用引用)
结论:循环次数非常大的程序之中使用闭包;
7.面试题
var a = 3;
function a(a){
console.log(a);
var a = 5;
}
a(0);
console.log(a); //报错,a is not a function
var a = {n:1};
var b = a;
a.x = a = {n:2}; //相当于:a.x = {n:2};a = {n: 2}
//a对象的地址被更改了,而b对象添加了一个属性
console.log(a,b); // {n: 2} {n: 1, x: {n: 2}}
var x = 10;
function fn(){
console.log(x); //10
}
function show(f){
var x = 20;
f();
}
show(fn);
var fn1;
//只定义没有赋值,意味着原有栈中fn1的结果没有改变
function fn1(){
}
console.log(typeof fn1); //打印函数function
在函数内部一旦使用var定义变量名或者参数名与全局变量名相同,在该函数中不再使用这个全局变量名,该函数中这个变量名是局部变量
var name = "World!";
(function(){
if(typeof name === 'undefined'){
var name = "Jack";
console.log(name);
}else
console.log(name);
})();
//Jack
var name = "World!";
(function(){
var name = "J"
if(typeof name === 'undefined'){
var name = "Jack";
console.log(name);
}else
console.log(name);
})();
//J
var name = "World!";
(function(){
var name;
if(typeof name === 'undefined'){
var name = "Jack";
console.log(name);
}else
console.log(name);
})();
//Jack
function fn1() {
var a = 2;
function fn2() {
a++;
console.log(a)
}
return fn2
}
var f = fn1();
f()//3
f()//4
var foo = function poo() {
console.log(123);
console.log(poo);
console.log(foo);
}
foo();
// poo(); //报错,函数名的使用范围仅限于 函数体内
console.log(foo);
//输出结果:
// 123 //调用函数不同于打印函数,它是函数执行的结果
// ƒ poo() {
// console.log(1);
// console.log(poo);
// console.log(foo);
// }
// ƒ poo() {
// console.log(1);
// console.log(poo);
// console.log(foo);
// }
// ƒ poo() { //最下面的console.log(foo);原样打印foo()函数
// console.log(1);
// console.log(poo);
// console.log(foo);
// }