函数
命名函数创建
- function fn(a,b){
console.log(a+b);
}
fn(3,4); - 在当前函数在script标签中创建时,优先放置函数在堆中,函数名放在栈中。
- 当前代码所在script标签上面的script的标签中任意函数和全局变量都是可以调用的,但是其下面script标签中的就不能调用了。
- 函数名:驼峰式命名法,首字母一般小写;如果是构造函数,首字母大写。
- 函数名后的()执行当前函数需要传入的参数。
- { } 里面是函数的语句块,执行函数时该语句块被执行。
- 尽量不要函数嵌套函数。
匿名函数
-
定义匿名函数
var fn = function(){
//代码运行到定义匿名函数这行后才可以调用该匿名函数
} -
自执行函数,定义就执行
执行完,就会立即被销毁。 -
自执行函数的两种形式:
(function(){
console.log(“i am number one”);
})();
//W3C推荐下面这一款~
(function(){
console.log(“i am number two”);
}());
包括自执行函数,函数都有执行上下文AO,都要经历预编译。
()为执行符号,只有表达式才能被执行符号执行,函数一旦被执行符号执行,就会忽略函数名。
()也算是数学符号,可以把函数变成表达式,所以(function (){}());这就是自执行函数了。
构造函数 定义函数
- var fn = new Function(“a”,“b”,“console.log(a+b)”);
fn(3,5); - 运行速度缓慢。但灵活。
- 动态构建函数。
作用域
- 函数外定义的变量,函数中可以调用。
函数内定义的变量,不能被函数外部调用。
var 定义变量,如果函数中没有用var定义变量,直接使用该变量,则此变量为全局变量。
window.a的window可以省略,但a还是全局变量
function fn(){a = 10;}此函数内外没有使用var a = 10;所以,a是window下的属性(ES6中是不允许不定义变量就使用)。
在函数中任意位置使用var定义的变量,在该函数的任意位置都认为该变量是局部变量。
有局部变量时,先调用局部变量,没有局部变量才带哦用全局变量。
参数就是局部变量。
var a = 1; //函数外定义的变量a
var d = 4; //全局变量
function fn(e) {
console.log("a=" + a); //a=1 访问函数外定义的变量a
var b = 2; //函数内定义的变量
c = 3; //直接使用该变量,c为全局变量
console.log("d=" + d); //d=undefined,函数任意位置使用var定义了的变量,在次函数中,都认为是局部变量,此时系统认为d未赋值
var d = 44;
console.log("d=" + d); //d=44,函数中有局部变量时,先调用局部变量d;没有局部变量,才会像a一样,调用全局变量
console.log("e="+e); //e=6 参数也是局部变量
var e=66; //相当于重新定义了一个局部变量e
console.log("e="+e); //e=66
}
fn(6);
// console.log("b="+b); //报错:b is not defined;说明函数内定义的变量,函数外不能直接访问
console.log("c=" + c); //c=3 c未被定义而直接使用,意味着在直接在window中增加属性,乃window.c,不过是window被省略了
- 补充:
局部变量:函数内用var定义的变量。
全局变量:函数外用var定义的变量;函数内不使用var,直接给变量赋值,相当于给window增加一个属性,此属性也是全局变量。
局部变量 > 全局变量(权重)。
this = window (全局作用域window)。
函数运行完成后,局部变量就会被销毁。
作用域属于一个函数,一个函数产生一个作用域。
[[scope]]:代表作用域。
每个js函数都是一个对象,作用域是不能访问的属性,仅供js引擎存取。
[[scope]]指的是我们所说的作用域,其中存储了运行期上下文的集合。
(运行期上下文AO,一个函数每调用一次,就会产生一个执行期上下文AO,当函数执行完毕,执行期上下文AO就会被销毁)
- 示例
1、判断a、b的作用域
var a = "a=1";
function fn1() {
var a = "a=11";
function fn2() {
var b = "b=2";
var a = "a=111";
console.log(a); //a=111;可调用自身的变量
}
fn2();
// console.log(b); //错误:b is not defined;外部不能调用函数fn2中的变量
}
fn1();
2、地狱b模式
// 函数预先放入堆中;函数b,外部变量b都是全局变量,下面b="b=2"还没有运行,所以在这里全局变量就是函数b,我们把函数b当前参数填入;
b(b); //ƒ b(b){console.log(b);var b="b=22";} 打印的是函数b
var b="b=2"; //此时外部变量b覆盖函数b,所以此时b不在代表函数b
console.log(b); //b=2
function b(b){
console.log(b);
var b="b=22";
}
b(b); //错误:b is not a function
参数
- 实参与形参一一对应,如果没有对应填入,该形参即为undefined。
function fn(a,b){
console.log(a+b);
}
fn(3,5);
- 填入参数 == 给参数赋值(相当于)。
- js是弱类型语言,so,参数不能强制约定类型。
- ES5中不允许设置参数的默认值。ES6可以。
function(a,b=3){
console.log(a+b);
//就是这种,ES5报错,ES6算出结果
}
fn(2);
- 对象作为参数
function fn(obj){
// 相当于给obj赋值一个o,obj和o的引用地址相同
// 当修改obj的属性时,全局的o属性也会被修改
obj.a=10;
}
var o={a:1};
fn(o);
function fn(obj){
// 重新设置了obj的新的引用地址
obj={
a:3
}
}
var o={a:1};
fn(o);
-
如果参数的数量不确定,就不设置参数(ES5);
function getMaxValue(){
console.log(arguments);//可以打印出当前传入的实参,ES5以上版本尽量少用;
}
getMaxValue(1,2,3,4,5);//Arguments(5) [1, 2, 3, 4, 5, callee: ƒ, Symbol(Symbol.iterator): ƒ]arguments参数集 (有 伪数组 之称);
arguments.callee 函数本身;用于匿名函数,调用自身函数;
arguments.callee.caller 调用当前函数的环境函数。示例:没有指定参数个数和大小的情况下,比较大小,选出最大的 function getMaxValue(){ //尽量少用if else语句,多些单条件判断语句即可 if(arguments.length===0) return; //如果没有可比较的参数,就不用比了 var max=arguments[0]; //从第一个开始判断 for(var i=0;i<arguments.length;i++){ max=max>arguments[i]?max:arguments[i]; } console.log("max="+max); } getMaxValue(4,2,5,7,43,0); //max=43
-
临时变量(也是局部变量),临时变量一般用 _variable (下划线开头),一般用在函数参数里。
-
堆栈溢出:递归或者回调的次数过多,没有阻止递归或者回调的条件;要避免这种情况。
回调函数
- 参数调入函数,执行别的函数
1、回调函数-基础
function fn(f){
var a=1;
f(a); //以函数为参数,执行
}
function fn2(a){
console.log(a+100);
}
fn(fn2); //101
2、用计时器来回调函数
i=0;
function callBack(){
console.log("i="+i);
i++;
}
setInterval(callBack,1000); //用计时器每隔1s执行一次;结果是,i从0开始,每1s加1
- 示例:信号灯的变化,功能简单,就三个灯的顺序更迭
function getLight(first,second,third){
first(second,third);
}
function getRedLight(fn,fn1){
var f=arguments.callee; //被调用者,此处指函数getRedLight
var ids=setTimeout(function(){
console.log("red");
clearTimeout(ids); //清除计时器,用一次清一次,以免内存泄漏
fn(fn1,f); //执行回调函数
},1000); //每个1000ms即1s更迭一次
}
function getYellowLight(fn,fn1){
var f=arguments.callee;
var ids=setTimeout(function(){
console.log("yellow");
clearTimeout(ids);
fn(fn1,f);
},1000);
}
function getGreenLight(fn,fn1){
var f=arguments.callee;
var ids=setTimeout(function(){
console.log("green");
clearTimeout(ids);
fn(fn1,f);
},1000);
}
getLight(getRedLight,getYellowLight,getGreenLight);
递归
- 单函数递归:执行自己
1、调用自己
var i=0;
function fn(){
i++;
if(i<10) fn();
}
fn();
console.log("i="+i);//i=10
2、匿名函数中调用自身
var j=0;
(function(){
j++;
if(j<10) arguments.callee();
})();
console.log("j="+j);//j=10
- 双函数递归:arguments.callee与arguments.callee.caller可以完成双函数递归。
var y=0;
function fn1(f){
console.log("aaa");
f();
}
function fn3(f){
console.log("ccc");
f();
}
function fn2(f){
// arguments.callee.caller 当前函数的环境函数,调用当前函数的函数
console.log("bbb");
y++;
if(y<10)arguments.callee.caller(arguments.callee);
}
fn3(fn2); //会不断打印ccc bbb,直到不再满足y<10
//但是耦合度太高,不建议使用
注意:有一“随机块 随机颜色 随机长度 随机排列”的示例在20191228的随堂练习及课后作业有展示,可自行参考。
- 示例:递归在对象中的应用
var obj = {
a: 1,
b: 1,
c: {
a: 3,
b: 4,
c: {
a: 5,
b: 6,
c: {
a: 7,
b: 8,
}
}
}
}
1、遍历对象中的元素一般用for in来遍历; 浅复制
var obj1 = {};
for(var prop in obj){
obj1[prop]=obj[prop];
}
obj.a=10; //obj1与obj为不同对象
obj.c.a=1000; //obj1与obj中存入属性c对象的引用地址一致,故,一个变,皆变
console.log(obj1,obj);
2、用递归来遍历,深复制
function cloneObj(target, source) {
for (var prop in source) {
//每次将属性为对象的分离出来处理
if (typeof source[prop] === "object" && source[prop] !== null) {
target[prop] = {};
cloneObj(target[prop], source[prop]);
} else {
target[prop] = source[prop];
}
}
return target;
}
cloneObj(obj1,obj);
obj.a=10;
obj.c.a=1000;
console.log(obj1,obj); //只有obj里的内容改变,说明此时二者存入的c的引用地址亦不相同了
3、同样的属性 使用**引用关系**的方式深度遍历,深复制
function cloneObj(target, source) {
for (var prop in source) {
if (typeof source[prop] !== "object" || source[prop] === null) {
target[prop] = source[prop];
}
}
// 使用引用关系的方式深度遍历
var o = source.c;
target.c = {};
var o1 = target.c;
while (o) {
for (var prop in o) {
if (typeof o[prop] !== "object" || o[prop] === null) {
o1[prop] = o[prop];
}
}
o = o.c;
if (o) {
o1.c = {};
o1 = o1.c;
}
}
return target
}
var obj1 = {};
obj1 = cloneObj(obj1, obj);
obj.c.c.a = 1000;
console.log(obj1,obj); //obj.c.c.a变了,obj1没变
- 二叉树遍历
var obj3={};
function addValue(obj,left,right){
obj.left={
value:left
}
obj.right={
value:right
}
}
//依次向二叉树obj3(对象)中添加左右元素
addValue(obj3,1,2);
addValue(obj3.left,3,4);
addValue(obj3.right,5,6);
addValue(obj3.left.left,7,8);
addValue(obj3.left.right,9,10);
addValue(obj3.right.left,11,12);
addValue(obj3.right.right,13,14);
console.log(obj3); //{left: {…}, right: {…}}
//开始遍历
function mapTree(obj){
if(!obj) return; //直到遍历完,才结束
if(obj.value) console.log(obj.value);
//先打印完左边的,再去打印右边的
mapTree(obj.left);
mapTree(obj.right);
}
mapTree(obj3);
函数 - 多态
- 代码的复用性高
- 代码耦合度要尽可能的低
- 代码解耦:减小代码耦合度
return
return的返回值
- 阻断作用
function fn(n){
if(n<5) return;
console.log(“aa”);
}
fn(3);
- 可以允许函数返回一个值,仅一个,但可以用对象、数组返回多个值;
- 返回函数执行的结果,如果函数中没有return,就会返回 undefined。
- return分支返回,典例:输入数字,返回汉字。
var arr=["零","一","二","三","四","五","六","七","八","九"];
// console.log(arr[5])
// var str="123";
// console.log(str[0],str[1],str[2])
// 5 "五";
// 10 "十";
// 15 "十五";
// 20 "二十";
// 36 "三十六";
// 100 "一百";
// 105 "一百零五";
// 110 "一百一十";
// 135 "一百三十五";
// 350 "三百五十";
function getCNNumber(n){
if(n<0 || n>1000) return "错误的消息";
if(n<10) return arr[n];
if(n===10) return "十";
if(n<20) return "十"+arr[String(n)[1]];
if(n>=100 && n%100===0) return arr[String(n)[0]]+"百";
if(n>100 && n%10===0) return arr[String(n)[0]]+"百"+arr[String(n)[1]]+"十";
if(n%10===0) return arr[String(n)[0]]+"十";
if(n<100) return arr[String(n)[0]]+"十"+arr[String(n)[1]];
if(n%100<10) return arr[String(n)[0]]+"百零"+arr[String(n)[2]];
return arr[String(n)[0]]+"百"+arr[String(n)[1]]+"十"+arr[String(n)[2]];
}
console.log(getCNNumber(175));
- 返回多个值,通过对象返回;
function fn(w,h){
// var perimeter=(w+h)*2;//一般不是都这么写嘛
// var area=w*h;
return {
perimeter:(w+h)*2,//其实也可以这么写,以对象的形式输出多个值
area:w*h
}
}
console.log(fn(3,9));
- 如果对象不存在,可以创建一个即o=o||{},不过这种每次执行函数都会创建一个新对象,称这种为工厂模式;单例。
- 每次执行函数创建一个新对象 工厂模式
function fn(){
var o={};
o.a=10;
o.b=20;
return o;
}
console.log(fn()===fn());
- 单例
var o;
//console.log(o); //undefined
function fn(){
//console.log(o); //{a: 10, b: 20}
o=o || {}; //对象o不存在就新建一个,若参数有o,或者在函数中另外新定义了,就无需新建。
o.a=10;
o.b=20;
return o;
}
console.log(fn()===fn()); //true
console.log(fn()); //{a: 10, b: 20}
- 返回函数体
注意:相关示例请看20191228随堂练习的“函数try”
return break continue 的区别
- return只能使用在函数中;无视任何内容,直接跳出函数。如果函数最后没有返回值,尽量不要写return。
return有时在循环中可以替代break。
function fn(){
for(var i=0;i<10;i++) if(i===5) return;
console.log(“a”);
}
fn();
- break 用于switch或者循环语句中,跳出当前循环或者锚点循环或者switch语句,循环外的语句继续执行。
- continue 只能用在循环语句中,仅跳出当前次循环,继续下一次循环。
计时器
计时器:setInterval setTimeout ;
一般使用他们的两个参数(执行的函数,间隔的时间);
他俩还有第三个参数,可以给相关函数传入数值;
setInterval是能够间隔一段时间自动再次执行某函数,直到被清除为止;
setTimeout是间隔一段时间后执行,仅执行一次,可以把它放到循环里面;
他俩都能返回一个数值,根据这个数值,分别用clearInterval(ids)与clearTimeout(ids)来清除掉(假设将返回的数值赋值给ids哈);
1、setInterval
var i=1;
var ids = setInterval(function(){
i++;
if(i>5) clearInterval(ids);//仅仅是停掉计时器,程序未结束
console.log("aa",i);
},1000);
2、setTimeout
var i=1;
var dis = setTimeout(function(){
i++;
// if(i>5) clearTimeout(dis);//延迟1秒输出i=2,说明,不循环执行
clearTimeout(dis);//清除时间间隔,每次用完一定记得清除
console.log(i);
},1000);
3、用计时器实现一个方块从左到右行走
var dists=0,bool=false;//表示出事距离
init();
function init(){
var div = document.getElementById("div0");
div.style.width="100px";
div.style.height="100px";
div.style.backgroundColor="teal";
div.onclick=function(){
bool=!bool;
}
setInterval(timer,16,div);
}
function timer(div){
if(!bool) return;
dists++;
div.style.marginLeft=dists+"px";
}