[前端学习]js语法部分学习笔记,第三天

JS循环

for循环

  • 格式:
    for( var i=0; i<5 ; i++){
    循环代码;
    }
  • i=0,是初始化条件,即循环从几开始,在整个循环过程中,它只会在最开始的时候执行一次;
  • i<5,是退出条件,规定了循环多少次结束;
  • i++增量参数
  • 整个流程就是,先执行初始化条件,然后判断条件,满足循环一次,然后执行增量,再判断条件,再循环一次,之后就是反复的增量-判断-循环,直到条件不满足退出循环。

多重for循环

  • 当多个for循环嵌套时,可以把里层的for循环看作外层for循环的代码
  • 外层循环一次,就执行内层的代码,执行内层for时再开始循环,当内层for循环次数完毕后,再回到外层,外层继续循环在执行内层代码
  • 所以通俗来说就是每外层执行一次循环,内层for就要循环完所有次数,再返回外层循环。比如:外层循环5次,内层循环10次,那么内层一共需要循环50次。
  • 如果不能理解,可用断点调试来看执行顺序。

双重for循环小案例解析

  • 倒直角三角形,代码:
    for(var i=1; i<10; i++) {
    for(var j=i; j<10; j++) {
    document.write("*");
    }
    document.write("<br />");
    }
  • 这个案例的重点在于,内层循环出来的*号要越来越少,才能实现倒三角行。而满足这个条件很简单,让内层循环j=i就能实现,因为外层i循环自增会越来越大,如果内层j=i,那么在规定了j<10的情况下,就会让内层循环次数越来越少。这样就能出现倒直角三角形的效果。
  • 九九乘法表,代码:
    <style>
    table, td {
    border: 1px solid #aaa;
    }
    table {
    border-collapse: collapse;
    border-spacing: 0;
    }
    </style>
    <script>
    document.write("<table>");
    for(var i=1; i<=9; i++) {
    document.write("<tr>");
    for(var j=1; j<=i; j++) {
    document.write("<td>" + j + "×" + i + "=" + j*i + "</td>");
    }
    document.write("</tr>");
    }
    document.write("</table>");
    </script>
  • 要实现九九乘法表要满足两个条件,一:表是9行;二:第几行对应的就有几列。
  • 那么开始分析思路:既然要9行,就让外层的i循环来控制行数,i<=9就可以循环出9行内。
  • 重点在于怎么让每行显示对应的列数,这里可以用内层循环j<=i来实现。每次循环次数由i的值来控制,从而达到目的。
  • 又因为九九乘法表每行都是从1开始乘并递增,所以让j=1再输出jxi的字符串即可。

斐波那契数列循环案例

  • 代码如下:
    var num1 = 1;
    var num2 = 1;
    var temp = 0;
    for(var i = 1; i<= x; i++){
    temp = num2;
    num2 = num2 + num1;
    num1 = temp;
    }
  • 斐波那契数列循环有一个很重要的规律的就是:第三个值等于前两个值的和。根据兔子计算的题来看,一般是求到x月时有多少只兔子。
  • 这个可以理解为是一种递增的模式,我们需要使用循环来让num1和num2通过上面的规律来增加,相当于往后走了一级。
  • 这样就很好实现了,创建一个临时变量来中转,把num2的值给临时变量,让num2 = num1 + num2,再把临时变量中原num2的值赋给num1,这样就相当于num1的值变成了num2的值,num2的值变成了后一个数字的值。不就是相当于往后走了一层吗。
  • 然后就可以根据需求来循环,求第几个月后,就循环几次,而num2的值因为是和所以就是最终结果。
    • 总结:
    • 并不是要记住这两个案例怎么写,两个案例都是循环值交叉作用的体现。要透彻的理解案例的原理,并举一反三学会在实际工作中具体的应用。

for in遍历

  • 使用for in不仅可以遍历数组,还可以遍历非数字索引的对象。key是一个存储键的变量,in后面跟上要遍历的数据名称。
    for (var key in obj) {
    console.log(obj[key]);
    }

while循环

  • 格式:
    var i = 0;
    while(条件表达式) {
    内容代码
    i++;
    }
  • 可以看到while和for虽然写法上略有不同,但是循环规律都是一样的,都是通过初始值、循环条件、自增量来组成。基本上for能做的while也能做,但是如果做数字方面的循环,for会看得更清晰一些。
  • 利用循环求1加到100值时,除了初始值外还需要声明一个存放结果的变量来配合。

do while循环

  • 格式:
    var i = 0;
    do {
    内容代码;
    i++;
    } while(条件表达式)
  • do while是while循环的一种拓展方法,从书写顺序就能知道,do while是先执行再判断。
  • 那么就可以得出do while和while的区别了:while是先判断,如果不满足则不执行;do while是先执行一次,再进行判断,不满足就不执行。所以如果使用do while循环,就算条件不满足也会执行一次。

continue和break的区别

  • continue跳过本次循环,略过下面的代码,直接进入下一次循环,可用“计算1-100偶数和案例”来深入理解;
  • break跳出所有循环,直接结束。可用“循环出1-100之间第一个能被3整除的数案例”来深入理解。如果要使用break跳出多层循环,需要使用标记来结束。例如:tag{ for(){ for(){ break tag}}},在循环外部做一个标记,然后在内层使用break时跟上标记,就能结束多层循环

JS数组

  • 数组的三种声明赋值方式
  • 第一种使用构造函数创建:
    var arr = new Array("值1","值2".....);
    注意这里如果构造函数参数写多个值就是正常的数组赋值,如果只写一个是设置数值的长度。
  • 第二种通过字面量直接创建:
    var arr = ["值1","值2"....];
  • 第三种:(第三种稍微特殊一点,先声明空的数组,然后再根据数字索引来写入值,数组的数字索引默认是从0开始)
    var arr = [];
    arr[0] = "值1";
    arr[1] = "值2";
  • 因为JS弱类型的特性,可以数组内可以放任意类型的值,但是实际工作中基本不会这样做。

数组的访问

  • 普通访问:
    数组名[索引值];
  • 获取数组的长度:
    数组名.length;
    • 数组length属性
      • 数组length属性是非常强大的,因为它的会随着数组个数变化而动态改变,所以能应用到的地方很多。比如:
      • 通过length来个数组追加值。数组[数组.length] = 值,通过规律可以发现,空数组是length为0,那么第一次赋值就是给0索引号赋值。第二次length变成1了,那么再次赋值就给1索引号赋值。以此类推,你会发现不不用写索引号,直接放入length属性就可以保证从0开始连续追加值。
      • 另外给length赋值可以直接影响数组内容的个数。
  • for循环遍历数组:
    for( var i = 0; i < 数组名.length; i++) {
    console.log(arr[i]);
    }
  • for循环直接让循环值小于数组长度即可(因为索引值是从0开始,所以必须小于才不会多循环一次)
  • for反向遍历数组
    for( var i = 数组名.length-1 ; i >= 0 ; i--) {
    console.log(arr[i]);
    }
  • 反向遍历,就让循环初始值等于该数组的最大索引号,然后用递减的方式倒序来输出即可。
  • 使用for循环来给数组赋值时,可以配合length属性来用。一个空数组,它的length就是0,那么就可以把length当作是它的初始索引号,然后循环过程中,每附一个值length会自动增加1。但是这个方法仅限初始数组是空数组。

如何找出数组中最大最小值及其索引

  • 首先要分析需求,目的是要找出四个值。那么肯定需要四个变量来装这四个值。
  • 接下来用假设值的方法来遍历数组并获取结果
  • 代码如下:
    var arr = [99,22,38,45,23,84,48];
    //假设最大值最小值和索引,然后通过循环判断来改变
    var max = arr[0];
    var maxIndex = 0;
    var min = arr[0];
    var minIndex = 0;
    //这时就可以通过for循环来把数组中所有值找出来,并依次判断
    for(var i=0 ; i< arr.length; i++) {
    //因为假设的max是最大的值,如果它小于当前循环出来的值,那么证明当前值更大,就把当前值赋给max,同时索引号也要变
    if(max < arr[i]) {
    max = arr[i];
    maxIndex = i;
    }
    //因为假设的min是最小的值,如果它大于当前循环出来的值,那么证明当前值更小,就把当前值赋给min,同时索引号也要变
    if(min > arr[i]) {
    min = arr[i];
    minIndex = i;
    }
    }
  • 记住,找出最大或者最小值,必须要先设置一个假设值,再遍历比较。

翻转数组

  • 翻转数组实际上是利用for循环,把一个数组的所有值以反向的顺序赋给另一个数组
  • 那么根据这个逻辑,就可以分析出一个规律:翻转数组[索引号] = 原数组[数组长度-1-当前循环索引号],这样就实现了数组的翻转务必亲手写几次,理解原理)。
  • 代码如下:
    var arr = ["值1", "值2", "值3", "值4", "值5"];
    var arr1 = []; 要声明一个空数组来装翻转的数据
    for(var i=0; i<arr.length; i++) {
    arr1[i] = arr[arr.length-1-i]; 这样arr1[0]的值就是arr最后一个索引的值,然后循环i的值变化,arr1的索引值越来越大,arr的索引值越来越小,这样就实现了一个反顺序的数组值赋值。
    }
  • 也可以通过倒序遍历来把值正序赋给一个新的变量,也能实现翻转。
    var arr= [];
    var newArr = [];
    for (var i=arr.length-1; i>=0; i--) {
    newArr[newArr.length] = arr[i];
    }

数组冒泡排序案例

  • 需求是把数组中所有的值按从小到大或者从大到小的顺序进行排列
  • 实现代码(以从小到大为例,比较符反过来就是从大到小了)如下:
    var arr = [19,20,85,24,28,94,30,1,22];
    //外层循环控制一共循环多少次才能把所有值都排序好,根据排序规律可以得出,需要数组值总数-1次循环才能完成排序,又因为是从0开始,所以得小于数组值总数-1次
    for (var i=0; i<arr.length-1; i++){
    //声明一个外层循环的值
    var flag = true;
    //内层循环控制数组每个值要比较多少次才能到位置,正常情况也是需要数组值总数-1次循环,但是又因为每次循环会固定好一个值的位置,因此循环次数是有规律的递减。
    for( var j=0; j<arr.length-1-i; j++) {
    //开始前后两个值两两比较,如果前面的大就放到后面去
    if(arr[j] > arr[j+1]){
    flag = false; //只要重新赋值就说明顺序不对,在交换,如果没有重新赋值就是没有进来,说明所有顺序是对的
    var temp = arr[j];
    arr[j] = arr[j+1];
    arr[j+1] = temp;
    }
    }
    //如果经过循环flag的值没有改变,则表示已经是正确顺序,直接跳出不再执行
    if(flag) {
    break;
    }
    }
  • 大致原理代码已经说清楚了,多敲几遍理解就好。只要明白冒泡排序需要双层for来控制比较次数和所有值排序次数,以及循环次数的规律、前后值交换需要一个临时变量即可。

JS函数

  • 函数是可以重复执行的代码块
  • 格式:
    function fun(形参1,形参2...) {
    函数内部代码
    返回值
    }
    fun(实参1,实参2) 在调用函数时,传入参数实现不同结果
  • 函数是可以多次重复调用的代码块,有助于节省代码量易于维护。有点类似于css的类选择器。
  • 参数可以写也可以不写。实参可以是任意形式的值。如果实参比形参多,多出来的实数会浪费掉;如果实参比形参少,多出来的形参会默认为undefined。另外传入参数时,只是把实参的值取出来复制了一份给形参,并不是直接把实参传入了。
  • return就是返回值,在函数中return有结束函数运行的作用,执行到它函数就结束了,并通过return把执行结果返回到函数调用的地方。
  • js中没有重载,所以函数名不能相同,否则会覆盖。
  • 函数因为有预解析,执行顺序会先执行调用代码,再进入函数内部执行,不受书写顺序的影响,在浏览器调试工具中,有对应进入函数内调试的按钮。

函数声明提升

  • 在JS中,会默认把函数声明提升到最顶端,就是说最优先加载。变量声明也会声明提升,但是变量只提升的声明却不提升赋值。所以用表达式形式声明函数,并在函数前调用会出错。

全局变量和局部变量

  • JS中在外部声明的变量就叫做全局变量,它的特点是在任何地方都能正常使用
  • 局部变量是指在函数内部声明的变量,局部变量只能在声明的函数内使用,在外部调用会报错。

函数的递归

  • 通俗来说,递归就函数在内部自己调用自己。递归一定要有跳出条件。这里只是做一个基础的了解,后面会深入了解。
  • 一个递归累加的案例,稍作了解:
  • 分析累加的规律,1的累加是1,2的累加是2+1,3的累加是3+2+1,4的累加是4+3+2+1。可以看到实际上累加是当前值加上前一个数的累加,那么得出累加公式n = n + (n-1的累加),那么通过递归实现就是:
    function num(n) {
    //必须要作一个判断当做结束条件,不然递归会无限循环
    if( n == 1) {
    return 1;
    }
    var num = n + num(n-1);
    return num;
    }
    num(n);
  • 实际是就是通过递归函数,来反复调用自己来计算前一个数的累加值,一直到1就结束递归,并返回给第一次递归调用时。

回调函数

  • 因为函数实际上也是一种数据,它既可以调用,也可以当作值来赋给其他类型。那么把函数当作参数来传给其他函数,也是可以实现的。
  • 这种把一个函数当作参数传给另一个函数,就叫做回调函数。注意传入回调函数时,只传入函数名,不需要带上括号,等于是把函数内部的代码传进去,并不是立刻执行

函数求三个数中的最大值

  • 代码如下:
    function getMax(a, b, c) {
    renturn (a>b ? a : b) > c ? (a>b? a : b) : c;
    }
  • 比较三个值就用三元先比较前两个,得到大的,再和第三个用三元比,再返回比较出来大的那个。

JS复杂数据类型:object对象

  • js的数据类型分为基本数据类型(数值型、字符型、布尔型、空型、未定义型),复杂数据类型(对象)
  • 对象实际上是对存在事物的抽象化表示,所以也有种说法叫万物皆为对象。
  • 对象和基本数据类型的最大的区别就是,对象有属性和方法。
  • 属性代表这个对象的名词(年龄、性别、姓名等),方法代表这个对象的动词(说话、走路、唱歌、跳舞等)
  • 注意对象和它的属性与方法是通过.号或者[]来连接。
    • 对象的声明和赋值的两种方法:
    • 构造函数创建:
      var obj = new Object(); 声明一个叫obj的对象
      obj.name = xxx; 声明obj对象的姓名属性
      obj.sex = "男"; 声明obj对象的性别属性
      obj.run = function(形参) { 声明obj对象的走路方法
      return "我在走路"
      };
      obj.talk = function(形参) { 声明obj对象的说话方法
      return "我在说话"
      };
    • 字面量创建
      var obj = {
      name: xxx;
      sex: "男";
      run: function() {
      return "走路";
      }
      }
    • 因为方法是动作,所以需要用函数来承载,也可以说函数在对象中叫做方法。以上声明的属性和方法都是属于obj这个对象的。
  • 当然因为对象也是无序的键值对组成,所以也可以使用类似数组的方法来赋值和访问。这种方式更加灵活,可以再中括号内使用字符串拼接或者变量等。
    var obj = {};
    obj['name'] = xxx;
    obj['run'] = function(){};
  • 对象属性可以存放任意类型的数据。
    • 对象的访问:
      console.log(obj); 打印出这个对象所有的属性和方法
      console.log(obj.name); 访问属性值需要将值打印出来
      obj.run(实参); 方法调用与函数类似,直接写对象名.方法名()就可以
    • 总结,任何对象的属性和方法,它们的区别都是方法有小括号(),属性没有。

键值对的概念

  • 实际上我们已经见过很多键值对了,比如数组、css、字典等,都是键值对。
  • 键值对,就是一个键对应一个值,当我们想使用或查找某个值时,直接调用对应的键就可以了,这样可用大大调高效率。而对象也是键值对,属性名或者方法名就是键,存储的内容就是值。调用名字就能获取值或者执行代码。

this伪变量

  • 三种情况:
    • 1.当this在全局上下文时(即函数外部),它对应的是window
    • 2.当this在函数内部时,它对应的是当前函数所属的对象
    • 3.在构造函数中时,会改变this的指向,直接指向对象不再根据所属,并返回这个对象

批量创建对象

  • 工厂函数模式:方法很简单,就是把创建一个对象的流程代码放进去函数封装起来,并给函数写上所有对象属性对应的形参(方法一般是在内部写好,很少传进去),在函数最后返回创建好的对象。当外部调用这个函数时就创建了一个对象,而且根据实参的不同,每次创建的对象值也不同。就像是其他编程语言中的类。
  • 构造函数模式:为了解决工厂函数模式的一些缺点(不能查看对象类别),构造函数就诞生了。大致的写法相似,但有一些变化。构造函数模式中,不需要手动创建对象,所有的属性和方法赋值时都用this.名称的方式来写,并且因为this的特性不用再写返回值。创建对象时,使用new关键字。
  • 一个手机构造函数案例:
    //一般构造函数使用名词命名,且首字母大写
    function Phone(brand, color, price) {
    this.brand = brand;
    this.color = color;
    this.price = price;
    this.call = function() {
    console.log("打电话");
    }
    this.message = function() {
    console.log("发短信");
    }
    }
    var huaweiPhone = new Phone("华为","黑色",5000);
    console.log(huaweiPhone.color); //因为huaweiPhone是通过Phone创建的实例对象,所以创建后直接用实例名调用属性和方法就可以了
    huaweiPhone.call();
  • 可以通过(A instanceof B)来比较A是不是通过B创建出来的实例对象。是返回true,否则返回false

数据存放的位置

  • 在JS中基本数据类型的值存放在栈内层中,复杂数据类型的值存放在堆内存中。
  • 因为基本数据类型存储数据比较少,所以直接把值放在栈内层中访问速度很快。把一个基本数据赋值给另一个基本数据,它们虽然值相同,但实际因为直接把值放过去所以是两个引用地址。它们完全独立,所以任意一基本数据再重新赋值后另一个不受影响 。
  • 复杂数据类型因为占用空间较大,所以是把值放到堆内存中,而栈内层开辟的空间只是存放了一个指向堆内存引用地址。这是如果把一个复杂数据赋值给另一个复杂数据,只是在栈内存中把引用地址赋给了它,而实际上还是共用的存放在堆内存中的值,所以当修改了堆内存中数据时,因为引用地址相同所以值会一起变。
  • 把基本数据类型和复制数据类型作为实参,传入函数中,再做修改,结果和上面说一样。结论就是:在栈内存中存放的值,那么赋值过去,怎么修改都不会影响。但是如果存放的是引用地址,那么赋值过去,修改会一起变(有一种特殊情况,就是传入之后,有又创建了一个对象赋值给形参,那么形参的引用地址就变了)。
  • 可以找具体的图来看,更容易理解。

内置数学对象

  • 因为内置的数学对象太多,这里不做记录,去课件里看。

JS基础测试题总结

  • 未定义的变量,使用.toString()转换字符串函数,会直接报错。
  • continue只能在循环中使用。
  • 'var a= + 任意类型',JS加法运算中,加号左边不写或者null或者undefined+任意类型,都会转换成数值型。无效数字返回NaN
  • JS是面向对象的语言,不是面向过程
  • ++在前先自增再执行,++在后先执行后自增

推荐阅读更多精彩内容