大标题 | 小节 |
---|---|
一、js 基础 | 1. javascript的组成; 2. 运行js; 3. 打印信息; 4. 关键字var; 5. js中的数据类型; 6. NaN(not a number); 7. js运算符; 8. 数据类型转换; 9. 解决精度缺失问题; 10. 进制的转换; 11. undefined 和 not a defined |
二、程序的结构-单双分支 | 2.1 分支结构 1. 单分支;2. 双分支;3. 多分支; 2.2 循环结构 1. 循环三要素;2. 循环语句;3. do-while和while的区别;4. 死循环;5. 循环中的关键字 |
三、函数 | 1. 函数的使用; 2. 函数的分类; 3. 函数的参数; 4. arguments ;5. return ;6. 作用域; 7. 变量提升; 8. 递归 |
一、js 基础
网页的组成:
(1)结构:html
(2)样式:css
(3)行为:JavaScript
1. JavaScript(简称“js”)的组成:
(1)DOM: 文档对象模型,操作 html(在 js 中叫 DOM),网页结构;
(2)BOM: 浏览器对象模型,js 要运行在浏览器中,意味着 js 不仅要遵循自己的规则,还要遵循浏览器的规则(即BOM),提供了操作浏览器的方法;
(3)ECMAScript: 语法规范,规定了 js 怎么写,写什么;
2. 运行 js
js 也是一种脚本语言,有两种方式在HTML页面进行引入。
(1)外联 js: <script src="相对路径"></script>
(2)内部 js: <script type="text/javascript">//...</script>
3. 打印信息
跟编程没有任何关系,用来观察数据处理的结果,用来测试,用来调bug。
(1)向 页面 打印信息:document.write();
操作的是 DOM,影响页面(坑: 页面正在加载时执行 docment.write(),页面正常;页面加载完执行docment.write(),页面被覆盖);
(2)向 控制台 打印信息:console.log();
操作的是 浏览器,既不影响页面,也不影响操作;
(3)向 浏览器弹出框 打印信息:alert();
操作的是 BOM,需要手动关闭;
4. 关键字 var
(1)原理: 我们所写的程序运行在内存中,当我们使用关键字 var 声明一个变量时,计算机会从内存中分一个空间,为我们存放 不同类型 的内容做准备。
(2)变量命名规则:
- 只能以 字母、_ 、$ 开头;
- 尽量使用数据类型做前缀;
- 不允许使用 关键字 和 保留字;
5. js 中的数据类型
(1)字符型(String): 所有用引号引起来的数据 “” 和 ‘’ ;
(2)数值型(Number): 0123456789;
(3)布尔型(Boolean): true 和 false;
(4)undefined: undefined;
(5)对象型(object): {} 就是对象;
(6)数值(arr): [] 就是数值
-
检测数据类型的方法:
(1)typeof
:用来检测数据类型;
typeof 返回七种可能的值:“number”、“string”、“boolean”、“object”、“symbol”、“function”和“undefined”。console.log("基础数据类型");console.log(typeof ""); //stringconsole.log(typeof 1); //numberconsole.log(typeof true); //booleanconsole.log(typeof undefined); //undefinedconsole.log(typeof null); //objectconsole.log(typeof []); //objectconsole.log(typeof {}); //objectconsole.log("引用数据类型");console.log(typeof function(){}); //functionconsole.log(typeof new Function()); // functionconsole.log(typeof new Date()); //object console.log(typeof new RegExp()); //object
- ① 对于基本类型,除 null 以外,均可以返回正确的结果。
- ② 对于 null ,返回 object 类型。
- ③ 对于引用类型,除 function 以外,一律返回 object 类型。
- ④ 对于 function 返回 function 类型。
其中,null 有属于自己的数据类型 Null , 引用类型中的 数组、日期、正则 也都有属于自己的具体类型,而 typeof 对于这些类型的处理,只返回了处于其原型链最顶端的 Object 类型,没有错,但不是我们想要的结果。
(2)
instanceof
,具体参考instanceof 是用来判断 A 是否为 B 的实例,表达式为:A instanceof B,如果 A 是 B 的实例,则返回 true,否则返回 false。 在这里需要特别注意的是:instanceof 检测的是原型。
(3)
constructor
,具体参考(这里依然抛开null和undefined)乍一看,constructor似乎完全可以应对基本数据类型和引用数据类型,都能检测出数据类型,事实上并不是如此:声明了一个构造函数,并且把他的原型指向了Array的原型,所以这种情况下,constructor也显得力不从心了
(4)
Object.prototype.toString.call()
jQuery源码就是采用这种方法检测的。
var a = Object.prototype.toString; console.log(a.call("aaa")); //[object String] console.log(a.call(1)); //[object Number] console.log(a.call(true)); //[object Boolean] console.log(a.call(null)); //[object Null] console.log(a.call(undefined)); //[object Undefined] console.log(a.call([])); //[object Array] console.log(a.call(function() {})); //[object Function] console.log(a.call({})); //[object Object] console.log(a.call(new Date())); //[object Date] console.log(a.call(new RegExp())); //[object RegExp]
6. NaN
(not a number)
(1) 不是一个数字的数值型数据,非法的运算 会得到 NaN;
(2)NaN 不等于 NaN;
(3)isNaN()
是不是一个数字;检测 NaN;
得到 NaN 时,返回 true,如果是数字,返回 false。
console.log(isNaN(NaN));//true
console.log(isNaN(123));//false
7. js 运算符:
(1)算术运算符:
-、*、/、%
,会把两边的数据,当成数值进行运算;- 而
+
号两边只要出现一个字符,那么就不是加法了,而是字符串的拼接;
(2)关系运算符:
- 只要关系运算符两边,存在数值,会把两边的数据,当成数值进行运算;
- 两边都是字符,会按照字符的比较规则,比较(逐位比较);
===
除了比较值,还比较了类型,不存在类型转换;console.log(1 == true); //true console.log(1 === true); //false
(3)逻辑运算符:||
、&&
、!
;
(4)赋值运算符 = += -+ *= /= %=
;
(5)自增 ++
和自减 --
每次变化1:
- ++a 先运算,再执行
- a++ 先执行,再运算
8. 数据类型转换
(1)隐式类型转换:
① 字符转数值: -
、*
、/
、%
;
② 数值转字符:+
;
③ 其他转 boolean:if(){}
括号中的隐式转换
- 非 0 为 true,0 为 false;
- 所有非空字符(即
""
,而不是" "
)为 true,空字符为 false; - undefined 为 false;
- 所有对象和数组(包括
{}
,[]
,除了null
)都为 true; - NaN 为 false;
- null 为 false;
④ 其他
- true 会转成1,false 会转成0;
- NaN 与任何值(除了字符)运算得到 NaN;
console.log(1 + "1");//11 console.log(2 + false);//2 false -> 0 console.log(1 + true);//2 true-> 1 console.log(true + false);//1 console.log(undefined + 1);//NaN console.log(undefined + NaN);//NaN console.log(undefined + null);//NaN console.log(null + 1);//1 null -> 空
(2)显式(强制)类型转换
① 字符转数值:Number()
、parseInt()
取整、parseFloat()
取浮点数、Math.round()
四舍五入取整
console.log(parseInt("123abc456"));//123
console.log(parseInt("abc456"));//NaN
console.log(parseInt("12.34"));//12
console.log(parseInt("12.84"));//12console.log(parseFloat("123.1415avc"));//123.1415
console.log(parseFloat("123.14avc15"));//123.14
console.log(parseFloat("123.am1415"));//123
console.log(parseFloat("123am.1415"));//123
console.log(parseFloat("12am3.1415"));//12
console.log(parseFloat("am123.1415"));//NaNconsole.log(Number("123a"));//NaN
console.log(Number("123.12"));//123.12console.log(Math.round(123.4));//123
-
parseInt() 和 parseFloat() 转换规则:
从左到右依次检测字符,遇到数字就转,遇到字母就停,第一位是字母就是 NaN; -
Number():
严格转换,小数点也能转换,只要出现一个非数字的字符,无论在哪个位置,都是 NaN。
② 数值转字符: +""
、toString()
转成字符、toFixed(n)
四舍五入且保留n(n>=0)位小数
var num = 10;
var str = num + "";
var str = num.toString();
var str = num.toFixed();
9. 解决精度缺失问题
var n = 0.1 + 0.7;
n = parseFloat(n.toFixed(1))
10. 进制的转换
(1)十进制转其他: toString()
var num = 110;
console.log(num.toString(2));//十进制转二进制 1101110
console.log(num.toString(8));//十进制转八进制 156
console.log(num.toString(16));//十进制转十六进制 6e
(2)其他转十进制: parseInt()
var str = "110";
console.log(parseInt(str,2));//二进制的转十进制 6
console.log(parseInt(str,8));//八进制转十进制 72
console.log(parseInt(str,16));//十六进制转十进制 272
(3)其他转其他
先转成十进制,再用 toString() 进行转换;
11. undefined
和 not a defined
(1)undefined: 定义了没赋值;
(2)not a define: 未定义、一种报错;
二、程序的结构-单双分支
逻辑的不同,程序分为三种结构:顺序结构、分支结构、循环结构;
2.1 分支结构
1. 单分支
if(判断条件){}
,只要“判断条件”为真,就会执行花括号中的操作。
- 判断条件为真,可以是一个 boolean值为 true 的变量,非 0 的数字,具体可以参考 第一点里面的第8条-数据类型转换-其他转 boolean。
2. 双分支
if(){}else{}
- 三目表达式:
条件 ? 条件为真时的操作 : 条件为假时的操作;
3. 多分支
(1)if()
if(){
}
else if{
}
else{
}
(2)switch(){}
//switch语句名
//()要判断的值
//{}要执行的内容
//default:不是必须的,常只有一个,放在最后,不需要加break;
switch(变量){case 变量值0:操作;break; case 变量值1:操作;break;default:当 case 条件不满足是执行的操作(默认操作);
}
- case 穿透: 当某一条 case 判断成功时,执行了当前 case 后的语句,后面所有的 case 都不再验证判断条件,直接执行后面所有的语句;
var n = 2; switch (n){case 1:console.log("星期一");case 2:console.log("星期二");case 3:console.log("星期三");case 4:console.log("星期四"); }//打印结果 星期二 星期三 星期四
- 阻止 case 穿透:
break
在 switch 中专门用来阻止 case 穿透。
2.2 循环结构
1. 循环三要素:
(1)声明计数器,比如:var i
;
(2)计数器的改变;
(3)停止条件;
2. 循环语句
(1)while(){}
//while语句名
//()执行条件、停止条件
//{}执行语句、循环体
var i = 0;
while(i<10){console.log(i);//0 1 2 3 4 5 6 7 8 9i++;//计数器常放最后console.log(i);//1 2 3 4 5 6 7 8 9 10
}
//当 i<10 时,执行循环体,否则不执行。执行次数 10
- 求100-999之间的水仙花数:
//水仙花数: a^3 + b^3 + c^3 = abc; var i = 100; while(i<1000){var a = i%10; //取个位数var b = parseInt(i%100/10); //取十位数var c = parseInt(i/100); //取百位数if(i == a*a*a + b*b*b + c*c*c){console.log(i)}i++; }
(2)do{}while(){}
特性:不管条件是真还是假,do 和 while 都会进行循环;
//do 语句名 {}do的执行语句、循环体
//while 语句名 ()停止条件 {}while的执行语句
//循环条件若放在 while 中,将会造成死循环
(3)for(){}
//for 语句名 ()条件组 {}循环体
for(var i=0;i<10;i++){console.log(i);//0 1 2 3 4 5 6 7 8 9
}
3. do-while 和 while的区别
do-while不管任何情况下都至少会执行一次。
var i = 0;
do{console.log("do执行:"+ i); //0 1 2 3 4 5 6 7 8 9i++;
} while ( i < 10){console.log("while执行:"+ i); //10
4. 死循环:
无法替自身控制结束的循环,称为死循环;
死循环的应用:每次判断一个条件,直到满足条件,利用 break
跳槽循环(常使用 while()
)。
- 篮球从5米高的地方掉下来,每次弹起的高度时原来的30%,经过几次弹起,篮球的高度是0.1?
var i = 0; var h = 5; while(true){h = h * 0.3;i++;if(h<0.1){break;} } console.log(i); //4
5. 循环中的关键字
(1)break
,跳出当前循环、结束当前循环,不再执行剩余代码。
(2)continue
,跳过本次循环,用于停止当次循环,回到循环起始处,进入下一次循环操作。。
var i = 0;
while(i<10){console.log(i);if(i==5){break;}i++;
}
// 打印结果:0 1 2 3 4 5var i = 0;
while(i<10){console.log(i);if(i==5){continue;}i++;
}
//造成死循环。当i=5时,跳过本次循环,即不执行i++,再次循环时i还是=5var i = 0;
while(i<10){i++;if(i==5){continue;}console.log(i);
}
//1 2 3 4 6 7 8 9 10
//当 i=5 时,跳过本次循环及后面的逻辑,再返回到花括号中第一句代码重新开始循环判断
三、函数
概念: 函数是由事件驱动的,或者当它被调用时可执行的可重复使用的代码块;
1. 函数的使用
(1)创建函数
- 声明式 创建函数:
function 函数名(){//...}
- 赋值式(也叫字面量式) 创建函数:
var 函数名 = function(){//...}
(2)函数调用方式:
- 自动执行:
函数名();
- 事件执行:
obtn.onclick = 函数名;
(3)函数执行时机:
- 任何情况下,只要
函数名()
,立即执行; - 当需要用事件触发函数时,不能加小括号,加了就会立即执行;
function bar () {console.log(1);
}
console.log(bar);
//打印结果:ƒ bar () {
// console.log(1);
//}console.log(bar()); //打印结果:1
(4)使用/执行: 函数名();
<button>按钮</button>
var obtn = document.querySelector("button");
obtn.onclick = say;function say(){console.log("hello");
}
(5)函数的好处:
① 重复使用;
② 选择执行;
③ 忽略细节(就想空调一样,不会组装,但是会使用);
2. 函数分类
(1)有名函数: function 函数名(){};
(2)无名函数: funtion(){};
(3)匿名函数: (function(){})()
- 区别:
① 无名函数: 不能直接写,必须通过事件调用,或者作为参数、返回值等使用;
② 匿名函数: 会立即执行,自动执行(即:不用主动调用,自己执行);
3. 函数的参数
形参: 形式的参数,函数定义时,小括号内的参数;
实参: 实际的参数,函数执行时,小括号内的参数;
function fn(n){ //n 形参console.log(n);
}
fn(12); // 12实参
(1)实参数量 = 形参数量,参数一一对应;
(2)实参数量 > 形参数量,多出来的实参到 argument
中;
(3)实参数量 < 形参数量,多出来的形参是 undefined
;
4. arguments
(1)伪数组:
- 理解:伪数组像数组一样有 length 属性,也有 0、1、2、3 等属性的对象,看起来就像数组一样,但不是数组。
- 特点:
① 具有 length 属性;
② 按索引方式存储数据;
③ 不具有数组的 push()、pop() 等方法; - 伪数组转换为真数组:
① 方法一:Array.prototype.slice.call(伪数组)
;
② 方法二:Array.prototype.slice.apply(伪数组)
;
③ 方法三:Array.prototype.slice.bind(伪数组)
;
④ 方法四:Array.from(伪数组)
,返回的就是真数组;
(2)arguments
是一个伪数组,接收所有实参。
function fn(a,b){console.log(a); //4console.log(b); //6console.log(arguments);//Arguments(4) [4,6,8,3]
}
fn(4,6,8,3)
- 示范将伪数组变成真数组:
function fn(a,b){console.log(arguments);//Arguments(4) [4,6,8,3]var arr = Array.prototype.slice.call(arguments);console.log(arr); //[4,6,8,3] } fn(4,6,8,3)
(3)arguments.callee
保存了自身所在的函数,“递归”中使用(但是后期递归舍弃了该方法);
function fn(a,b){console.log(arguments.callee);//[4,6,8,3]
}
fn(4,6,8,3)
(4)运用场景:无法确定实参的个数是多少时。
//求任意个数字的和
function fn(){var sum = 0;for(var i = 0; i<arguments.length; i++){sum += arguments[i];}
}
fn(4,6,8,3)
5. return
把函数里面的数据 返回到 函数外面,使我们可以在函数外面拿到函数里面的数据。
(1)作用:① 终止循环;② 返回值;
function fn(){return 123;
}
var a = fn();
console.log(a);//123
console.log(fn());//123
- 返回值: 返回值就是 将函数处理后的数据,返回到函数名上(利用这一点可以做一个简单的“闭包”,也可参考菜鸟教程), 便于其他程序或用户调用或做二次使用。
- 不一定所有函数都有返回值,只有需要返回数据的函数才加
retrun
。
- 闭包具体在后面提及,这里写一个示例。
function a(){var m = 0;return function(){m++;console.log(m)};} console.log(a()); //打印结果:返回的是return后面的函数,不包括 var m = 0;这一条语句。 //ƒ (){ // m++; // console.log(m) //} a()(); //11 这样相当于执行函数a的整个部分,所以多次调用,会一直先让m=0,再去执行return语句 a()(); //11
//从上面的打印结果,我们可以看到打印a()的结果是一个函数。 function a(){var m = 0;return function(){m++;console.log(m)};} var b = a(); //将这个函数结果定义一个新的变量保存起来 b(); //11 b(); //12 b(); //13//上面这种保存函数的方法,执行结果就相当于 //function a(){var m = 0;function b(){ //变量m和函数b组成了一个闭包return m++;} //} console.log(b()); //因为函数b的返回值是return,要想查看结果,就要console.log(b());而不能直接b() console.log(b()); //类似数组方法sort()的执行过程 console.log(b());
(2)return 的几个返回值:
① return true;
,返回正常的处理结果;终止处理。
② return false;
,返回错误的处理结果;终止处理;阻止提交表单;阻止执行默认的行为。
return false;
只在当前函数有效,不会影响其他外部函数的执行。
③ return;
,把控制权返回给页面。
(3)break
,continue
和 return
的用法及区别:
关键字 | break | continue | return |
---|---|---|---|
相同之处 | 三个都会将此时进行的语句停止。 | ||
不同之处 | break 是立即结束语句,并跳出语句,进行下个语句执行。 | continue 是停止当前语句,并从头执行该语句。 | return 停止函数。 |
使用环境 | break 和 continue 是用在循环或switch语句中,用来退出循环。 | return 是用在函数语句中。return 语句应用范围只能出现在函数体内,出现在代码中的其他任何地方都会造成语法错误! | |
举例 | for(i=0; i<5; i++){ if(i == 3){ break; } console.log(i); } | for(i=0; i<5; i++){ if(i == 3){ continue; } console.log(i); } | function format(d){ if(d<10){ return “0” + d; } else { return d; } |
返回值 | 0 1 2 | 0 1 2 4 | |
结果分析 | 当 i=3 时,直接退出for这个循环。这个循环将不再被执行!直接进入下一个语句。 | 当 i=3 时,直接跳出本次for循环。继续执行这个循环语句的后面的循环条件。 | 语句结束函数执行,返回调用函数,而且把表达式的值作为函数的结果。 |
6. 作用域
作用域分为:全局作用域、局部作用域、块级作用域,这里主要讲前两种。
- 作用域:作用域是可访问变量的集合。 —— 参考自菜鸟教程
- 在 JavaScript 中, 对象和函数同样也是变量。
- 函数作用域:作用域在函数内修改。即变量在函数内部作用的范围(区域),有函数的地方就有作用域。
(1)全局作用域: 变量在函数外定义,即为全局变量。全局变量有全局作用域:网页中所有脚本和函数均可使用。
var carName = " Volvo";// 此处可调用 carName 变量
function myFunction() {// 函数内可调用 carName 变量
}
- 如果变量在函数内没有声明(没有使用 var 关键字),该变量为全局变量,而全局变量一般存到
window
中,我们通常会省略window
。
以下实例中 carName 在函数内,但是为全局变量。myFunction(); // 此处可调用 carName 变量 function myFunction() {carName = "Volvo";// 此处可调用 carName 变量 } console.log(carName); //Volvo console.log(window.carName); //Volvo
(2)局部作用域: 变量在函数内声明,变量为局部作用域。每一个函数都有一个局部的作用域,局部作用域只能在函数内部访问。;
// 此处不能调用 carName 变量
function myFunction() {var carName = "Volvo";// 函数内可调用 carName 变量
}
① 因为 局部变量只作用于函数内,所以在不同的函数内部可以使用相同名称的变量。
② 局部变量在函数开始执行时创建,函数执行完后局部变量会自动销毁。
函数内改变的只是该函数内定义的局部变量,不影响函数外的同名全局变量的值,函数调用结束后,局部变量占据的内存存储空间被收回,而全局变量内存存储空间则被继续保留。
③ 当你在 全局 和 局部 同时用 var
命名同名变量时,那么同名局部变量会在其所在函数内屏蔽全局变量,而优先使用局部变量。
var a = "10";
function fn() { var a ="20";console.log(a)
}
fn(); //20
console.log(a); //10 这里的a为全局变量
(3)变量生命周期
① JavaScript 变量生命周期在它声明时初始化。
② 局部变量在函数执行完毕后销毁。
③ 全局变量的生命周期一直存在,在页面关闭后销毁。
- 将全局变量 变成 局部变量的方法:
将变量放到匿名函数中。
A、在匿名函数中,全局变量可以被匿名函数中的函数访问、使用;(function(){var a = 3; })()
B、没有真正声明全局变量时(比如变量在函数内没有用 var 声明就赋值),也不会污染整个全局变量空间、占用内存。
7. 变量提升
在 JavaScript 中,变量可以在使用后声明,也就是变量可以先使用再声明。
函数及变量的声明都将被提升到函数的最顶部。
(1)变量提升
所有用 var 声明的变量都存在提升,提升到作用一开始的位置,在写了 = 号的位置赋值(即提前声明,在原本赋值的地方赋值);
console.log(b);//b is not defined
console.log(a);//undefined
var a = 10;
console.log(a);//10
console.log(b);//b is not defined
- 相当于:
var a; console.log(b);//b is not defined console.log(a);//undefined a = 10; console.log(a);//10 console.log(b);//b is not defined
(2)函数提升
① 声明式: 整个函数提升(即提前声明,提前赋值),整个代码块提升到它所在的作用域的最开始执行;
fn();//hello
function fn(){console.log("hello");
}
- 相当于:
function fn(){console.log("hello"); } fn();//hello
② 赋值式(字面量式): 提升变量(即提前声明,但是没有提前赋值);
fn();//fn is not a funtion
var fn = function(){console.log("world");
}
- 相当于:
var fn; fn();//fn is not a funtion fn = function(){console.log("world"); }
这里有一道题,结合作用域以及变量提升的知识点,请你给出打印结果。
console.log(v1);
var v1 = 100;
function foo() {console.log(v1);var v1 = 200;console.log(v1);
}
foo();
console.log(v1);//打印结果:
//undefined
//undefined
//200
//100
- 解释一下,上面代码相当于下面这段代码的缩写:
var v1; //在全局上,我们可以看到 v1 已经被 var 声明定义了,所以要被提升。 console.log(v1); //undefined var v1 = 100; function foo() {var v1; //在局部作用域中,v1 被 var 声明,所以提前到作用一开始的地方。console.log(v1); //undefinedvar v1 = 200;console.log(v1); //200 } foo(); console.log(v1); //在全局获取不到局部作用域中的变量,这里的 v1 是全局变量
8. 递归
(1)递归就是在自身内部调用自己;
(2)缺点: 耗性能,逻辑稍微不太好理解;
-
递归计算阶乘
//3*2*1 function fn(n){if(n == 1){return 1;} else {return n*fn(n-1);} } fn(3);
-
斐波那契数列:1,1,2,3,5,8,13,21,34…
//写一个数字n,找到第 n 个斐波那契数列; function fn(n){ if( n == 1 || n== 2){return 1;} else {return fn(n-1) + fn(n-2); } fn(3);
下一篇—— js笔记(二)对象和构造函数、数组、this