从零开始的JavaScript学习之旅——Week 2

368 阅读11分钟

数据类型转换

显式转换

  • 转字符串:
  1. xx.toString();,xx不能是undefinednull,会报错,两者不能使用.去操作
  2. String(xx);,万能的,但是没用——完全等效于隐式转换,还不如xx+"" 注意:一般来说两个都不用,因为页面上获取到的数据都是字符串
  • 转数字:
  1. 字符串 to 数字:parseInt/Float(str);,原理:从左向右,依次读取每个字符,碰到非数字字符就停止转换,如果一开始就不认识,则为NaN 注意:Int不认识小数点,Float认识第一个小数点

  2. Number(xx);,万能的,但是没用:完全等效于隐式转换,还不如-0 *1 /1 %1

  • 转布尔: Boolean(xx);,万能的,但是没用:还不如!!x 注意:
  1. 只有6个为false:0""undefinednullNaNfalse,其余都为true
  2. 一定不会手动使用此方法,但是在某些地方会自动带有此方法:
    • 分支条件
    • 循环条件 不管在分支条件和循环条件之中写的是什么,只需要看为true还是false

隐式转换

一般都出现在运算符和表达式中


运算符和表达式

算术运算符

算术运算符 + - * / %

隐式转换: 默认转为数字,再运算

注意:

  1. +运算,只要碰上一个字符串,两边都会转为字符串(调用了一个String()方法),再拼接
  2. - * / %,只要是纯数字组成字符串,也可以转为数字(悄悄调用了一个方法,我们程序员看不见Number())

比较/关系运算符

比较/关系运算符:> < >= <= == != === !==

返回的结果: 布尔值:truefalse

隐式转换: 默认一切都转为数字,再比较大小

注意:

  1. 如果参与比较两个都是字符串,按位比较每个字符的十六进制unicode号(十进制ASCII码)。规律:0-9<A-Z<a-z<汉字(汉字第一个字:一:4e00(ascii码:19968);最后一个字:龥:9fa5(ascii码:40869))

  2. NaN,参与任何比较运算结果都为false。判断值是否为NaN!isNaN(x),如果x是一个数字,结果true;x是一个NaN,结果就为false

  3. undefined==null;

    • 问题:null==undefined结果为true,区分不开undefinednull
    • 解决:全等===数值相同并且数据类型也要相同(不再带有隐式转换)
    • !==:不带隐式转换的不等比较
String()的执行原理:任何东西都可以转为字符串
    function String(x){
        if(x===null){
            console.log("null");
        }else if(x===undefined){
            console.log("undefined");
        }else{
            console.log(x.toString());
        }
    }

逻辑运算符:

&&:全部条件都为true,结果才为true,只要有一个为false,结果就为false

||:全部条件都为false,结果才为false,只要有一个为true,结果就为true

!:颠倒bool值

注意:

短路逻辑: 只要前一个条件已经可以得出最终结论,则后续条件不会再执行

&&:如果前一个条件满足,则后一个操作才执行,如果前一个条件不满足,则后一个操作不执行
    实现了一个【简单】的分支:目的:简化if(){},操作只能用一句话
        语法:条件&&(操作);
        举例:if(total>=500){total*=0.8}
              total>=500&&(total*=0.8)

||:两个值中二选一,应用于老IE(6/7/8)兼容

位运算:

左移:m<<n,读作m左移了n位,m*2的n次方 - 缺点:底数只能是2
右移:m>>n,读作m右移了n位,m/2的n次方 - 缺点:底数只能是2

赋值运算:+= -= *= /= %= ++ --

赋值运算:+= -= *= /= %= ++ --

一句话完成了两个操作,先运算再赋值回去,比如:i++ => i+=1 => i=i+1;

递增: i++,每次只能加1

累加: i+=n,每次加n(n可为任意有效数字)

笔试题:++i 和 i++ 的区别?
    1、单独使用时,放前放后无所谓都一样
    2、如果参与了别的表达式,变量中的值都会+1
            前++,返回的是加了之后的新值
            后++,返回的是加了之前的旧值
举例:
demo01:
    var me=2;
    var lp=me++ + ++me + me++ + ++me + me++;
    //     2      4      4      6      6
    console.log(lp);//22
    console.log(me);//7
demo02:
    var me=2;
    var lp=++me + ++me + me++ + me++ + ++me + me++ + ++me;
    //     3      4      4      5      7      7      9
    console.log(lp);//39
    console.log(me);//9

三目运算

三目运算: 目的:简化if...elseif...else if...else

语法:
1. 条件?操作1:默认操作;
2. 条件1?操作1:条件2?操作2:条件3?操作3:默认操作;

注意:

  1. 只能完成简单的分支,即操作只能有一句话
  2. 默认操作不能省略否则会报错

扩展

  1. 计算器很笨,牛逼在他的计算速度快和记忆力强——硬盘(固态硬盘(不开机、只能保存10年数据)和机械硬盘)
  2. 计算时会带有摄入误差:解决:var str=num.toFixed(保留小数位数);,按小数位四舍五入,但是返回的会是一个字符串
  3. 获取第一个字的ascii码:var ascii=str.charCodeAt(0);

函数

自定义函数

自定义函数: 也称方法,一段提前被定义好的,可以反复使用代码段

  1. 如何使用:
  • 创建/定义/声明函数&返回结果:
    1. 声明方式:function关键字进行声明。并不是人人都有声明方式,只有个别人才有,而且声明方式一定是最简单的
      • 只有3个人具有声明方式:变量常量函数,其余人最简单的方式也是直接量
    2. 直接量方式:
语法:
声明方式:
    function 函数名(形参,...){
        函数体
        return 返回值;
    }
直接量方式:
    var 函数名=function(形参,...){
        函数体
        return 返回值;
    }	
注意:函数名其实也是一个变量名	
  • 调用函数&接住结果
语法:
var result=函数名(实参,...);

解释:return:本意退出函数,但是如果后面跟着一个数据,则可以将数据返回到全局作用域中,但是仅负责返回,不负责保存,所以我们需要自己创建一个变量接住函数调用的结果

return只能写一次,而且最好写在函数体的后面

使用场景: 并不是任何时候都需要加return

  1. 全局想要使用局部的
  2. 调用完函数还希望拿到函数的结果在后续还要做操作时
  3. 如果没有return,其实也有默认返回值undefined

作用域

  • 全局作用域: 全局变量全局函数,在任何位置都可以访问/使用

  • 函数/局部作用域: 局部变量局部函数,只能在当前函数调用时内部可用

    有了作用域才有变量的使用规则:优先使用自己的,自己没有找全局,全局都没有报错

注意:千万不要对未声明的变量直接赋值,导致全局污染;所有的变量在使用之前都一定要先var,不能对着没有var变量直接赋值

  • 哪些属于局部变量:
    1. 直接在函数作用域中创建的变量
    2. 形参
问题:全局无法使用局部的?
解决:使用return

声明提前

定义: 在程序执行之前,将var声明的变量和function【声明】的函数集中提前到当前作用域的顶部,但是赋值留在原地,变量比函数更轻

注意:

  1. 写代码的时候尽量要先创建后使用
  2. 写代码的时候尽量不要出现重复的名字
笔试技巧:如果看到先使用后创建,多半都是考声明提前

按值传递

按值传递: 两个变量之间进行赋值

  1. 如果传递的是原始类型的值:两个变量之间赋值,做操作,互不影响的,其实是复制了一个副本给对方
  2. 如果传递的是引用类型的对象:ArrayFunction,两个变量之间赋值,做操作,是会相互影响的,因为两个用的是同一个地址值

预定义的全局函数

预定义的全局函数: 前辈们(实现浏览器的程序员们)提前定义好,我们可以直接在任何位置使用的函数

  1. 编码和解码:

    问题: url网址中不允许出现多字节字符,如果出现会导致乱码(utf-8编码格式下,一个汉字占3字节)

    解决: 前端工程师需要将用户输入的网址中的中文编码为单字节字符,后端工程师接住前端传来的东西解码为原文

    编码:var code=encodeURIComponent("str");

    解码:var 原文=decodeURIComponent(code); 注意:随着浏览器的不断升级,浏览器现在自带此功能

  2. isFinite(num): 判断num是不是在有效范围之内,三种情况会为false:分母为0,NaN,Infinity

  3. parseInt/Float()eval()isNaN()

分支结构

分支结构: 根据条件的不同,执行不同的操作,if...else结构,switch...case结构

语法:
    switch(变量/表达式){
        case1:
        操作1;
        break;
        case2:
        操作2;
        break;
        default:
        默认操作;
    }

注意:

  1. 默认只要满足一条路,会把后面所有的操作全都做完,解决:break,退出循环,一般放在操作的后面,但是:最后default不需要加break,如果连续的多个操作是一样的效果,也可以省略中间部分
  2. 不带有隐式转换
  3. default可以省略不写
if vs switchswitch:优点:效率相对较高,因为不需要做任何范围判断
            缺点:不能实现范围判断,必须要知道用户有可能输入的结果是什么才能使用
    if    :优点:实现范围判断
            缺点:效率相对较低
建议:js优化,尽量的将if 优化为:三目、短路、switch

扩展

  • JS动画: 跟JS没关系,有JS来触发,JS设置CSS是瞬间的操作,只需要在CSS中加一个过渡就可以具有动画了

循环结构

循环结构: 反复执行 相同相似的操作

while循环:

语法:
    var 循环变量=几;
    while(循环条件){
        循环体;
    }

do...while循环:

语法:
    var 循环变量=几;
    do{
        循环体;
        变量变化;
    }while(循环条件)
面试题:whiledo...while的区别?
    只看第一次,如果第一次大家都满足条件,两者没有区别,无非do...while更麻烦
    如果第一次大家都不满足条件,while一次都不会执行,而do...while至少会执行一次

for循环:

语法:
for(var 循环变量=几;循环条件;变量变化){
    循环体;
}

循环终止语句:

break - 退出整个循环
continue - 退出本次循环

数组

数组的基础

数组的基础:一个变量可以保存多个数据

创建数组

  1. 直接量:var arr=[值1,....];
  2. 构造函数:var arr=new Array(值1,....);
缺陷:面试中:new Array(3);设置一个长度为3的空数组

访问数组:

获取: arr[i];

注意:下标越界:得到undefined

添加/替换: arr[i]=新值;

注意:下标越界:得到稀疏数组

数组的3大不限制:

不限制长度、类型、下标越界(不推荐)

length的三个固定套路:

获取倒数第n个元素:arr[arr.length-n];

向末尾添加元素:arr[arr.length]=新值

缩容:arr.length-=n;

遍历数组:

语法:
    for(var i=0;i<arr.length;i++){
        arr[i];//当前次元素
    }

补充:如何释放一个引用类型:

要看清楚这个引用类型对象有几个变量名引用着,每个变量都要释放才能真的释放干净

关联(hash)数组:

索引数组: 下标都是数字组成的数组(默认)

关联(hash)数组: 下标是可以自定义的数组

自定义下标的原因: 索引数组的下标无具体意义,不便于我们查找

创建:

  1. 创建一个空数组:var arr=[];
  2. 添加自定义下标并且赋值:arr["自定义"]=值

访问:arr["自定义下标"];

遍历: 把所有的元素都取出来执行相同或者相似的操作

  1. 问题:不能使用for循环,因为length失效了,关联数组的length永远为0,而且下标也不再是一个数字

  2. 解决:for in循环

语法:
    for(var i in 数组名){
        数组名[i]
    }
使用范围:既可以遍历关联数组,也可以遍历索引数组
使用建议:索引数组依然使用for循环,关联数组只能使用for in循环

补充:

JS中除了undefined和null不是一个对象,万物皆对象,而且一切对象的底层都是hash数组

hash数组的原理:

  1. hash算法:将字符串交给hash算法,会得到一个尽量不重复的数字,但是字符串内容相同的,那么得到的数字一定也是相同的
  2. 添加元素:将自定义的下标交给hash算法,得到一个数字(地址值),把要保存的数据放到那个地址之中
  3. 获取元素:将指定的下标交给hash算法,得到一个和添加时完全相同的数字(地址值),得到这个地址值之中保存这的数据了

数组的API

数组的API:函数:前辈们预定义的好的,但是只有数组可以使用的方法

数组转为字符串:

var str=arr.join("自定义连接符");

注意:

  1. 如果没有传入实参,则和toString/String,完全一样,默认由,分割
  2. 固定套路:
案例1:
1、笔试题:提供一个数组给你,无缝拼接数组里面的内容变为一个字符串
			var arr=["h","e","l","l","o"," ","w","o","r","l","d"];
			var str=arr.join("")
			console.log(str);//"hello world";

2、开发中:将数组中的数据拼接为页面上的元素:初级版数据渲染
			var cities=["北京","南京","西京","东京","重庆"];
			var str="<option>"+cities.join("</option><option>")+"</option>";
			sel.innerHTML=str;//innerHTML可以识别标签

案例2:
实现下拉菜单二级联动:
    1、必须使用二维数组,而且二维数组的数据顺序一定要和一级的对应上
    2、select专属事件:select.onchange:状态改变事件:选中项发生改变才会触发
    3、select具有一个属性:this.selectedIndex;获取选中项的下标 - 只有select不需要自定义下标
    4、其实绑定事件的部分就是函数名,也可以拿来调用
demo:
    html:
        <select name="" id="sel1"></select>
        <select name="" id="sel2"></select>
    js:
        function select_skills() {
            var arr1 = ["---请选择角色---", "坤坤", "卢本伟", "大司马"];
            var arr2 = [
                ["---请选择技能---"],
                ["唱", "跳", "rap", "篮球"],
                ["盖亚变身", "吃屏幕", "倒卡布奇诺", "番茄连招"],
                ["芜湖起飞", "鬼刀一开", "肉蛋葱鸡", "肌肉金轮"]
            ]

            sel1.innerHTML = "<option>" + arr1.join("</option><option>") + "</option>";
            sel1.onchange = function () {
                var i = this.selectedIndex;
                sel2.innerHTML = "<option>" + arr2[i].join("</option><option>") + "</option>";
            }
            sel1.onchange();
        }
        select_skills();

拼接数组:(添加元素到的末尾的新方式)

语法:var newArr=arr.concat(值1,arr1...);

注意:

  1. 此方法不修改原数组,只会返回一个新数组
  2. 此方法传参支持数组参数,并且会悄悄的打散这个数组,单独传入

截取子数组:

截取子数组: 从starti位置截取到endi+1位置的元素,组成一个新数组

语法:var subArr=arr.slice(starti,endi+1) 注意:

  1. 此方法不修改原数组,只会返回一个新数组
  2. 会包含下标为starti的元素,但是不包含下标为endi+1的元素(含头不含尾)
  3. 第二实参可以省略:从starti截到末尾;第一实参也可以省略:从头截到尾
  4. 支持负数参数,-1代表倒数第一个

深拷贝与浅拷贝:

浅拷贝只复制一层对象的属性(如按值传递);深拷贝则递归复制了所有层级(复制了一个副本给对方)。

增删改(splice):

语法:
	删除:var dels=arr.splice(starti,n);//从starti开始删除n个
	  特殊:此方法其实也有返回值,所有删除的元素组成的一个新数组

	插入:arr.splice(starti,0,值1,...);//从starti开始删除0个,插入了新元素
	  特殊:原来starti位置的元素以及后续元素都会被向后移动

	替换:var dels=arr.splice(starti,n,值1,...);
	  特殊:插入的个数和删除的个数可以随意

翻转数组:

语法:arr.reverse();

排序:

冒泡排序:

案例:
    笔试题:冒泡排序:把数组中的每一个数字取出来,前一个和后一个进行比较,如果前一个>后一个,两者就要交换位置:
        公式:
            var arr=[13,25,4,3675,12,23,3,215,2,1,42,4,65,473,2431,123];
            for(var j=0;j<arr.length-1;j++){
                for(var i=0;i<arr.length-(j+1);i++){
                    if(arr[i]>arr[i+1]){
                        var m=arr[i];
                        arr[i]=arr[i+1];
                        arr[i+1]=m;
                    }
                }
             }	
            console.log(arr);

数组API提供的排序:

数组排序:arr.sort();

注意:

  1. 默认按照字符串按位PK每个字符的unicode号排序
  2. 按照数字排序:
语法:
    arr.sort(function(a,b){//回调函数:不需要手动调用的函数:带有循环,提供了两个形参:a是后一个数,b是前一个数
        return a-b;
    })

    //return a-b:如果a>b,返回是一个正数
    //    	  如果a<b,返回是一个负数
    //	          如果a==b,返回是一个0,sort根据你反复的结果,来判断两者要不要交换位置
  1. 降序排列:
语法:
    arr.sort(function(a,b){//回调函数:不需要手动调用的函数:带有循环,提供了两个形参:a是后一个数,b是前一个数
        return b-a;
    })
  1. 强调:JS中只有数组可以排序,网页上任何具有排序功能的案例,底层一定都是一个数组

栈和队列

栈和队列::4个API:添加元素和删除元素的新方式

  • 栈:一端封闭,只能从另一端进出的操作

    • 开头进:arr.unshift(值1,...);
    • 开头出:var first=arr.shift();,一次只能删掉一个,并且会返回删除的元素 缺点:每一次进出都会修改其他人的下标
    • 结尾进:arr.push(值1,...);
    • 结尾出:var last=arr.pop();,一次只能删掉一个,并且会返回删除的元素 优点:不会影响到其他元素的下标
  • 队列:只能从一端进入,另一端出:

    • 开头进:arr.unshift(值1,...);

    • 结尾出:var last=arr.pop();,一次只能删掉一个,并且会返回删除的元素

    • 结尾进:arr.push(值1,...);

    • 开头出:var first=arr.shift();,一次只能删掉一个,并且会返回删除的元素

二维数组

二维数组:数组中的值再次引用了一个数组

使用场景:在一个数组内在此细分内容

创建:

var arr=[["张三丰",128,3500],["张翠山",30,4500],["张无忌",18,5500]];

访问:arr[r][c];

注意

  1. 列下标越界:返回undefined
  2. 行下标越界:报错:undefined不能使用[]

遍历二维数组

遍历二维数组:必然需要两个循环嵌套:外层循环控制行,内层循环控制列

语法:
    for(var r=0;r<arr.length;r++){
        for(var c=0;c<arr[r].length;c++){
            console.log(arr[r][c]);
        }
    }

String

string定义:字符串:多个字符组成的只读字符数组

为什么字符串也可以叫数组呢?

  • 和数组有共同点
  1. 支持下标——获取某个字符
  2. 支持length——字符的长度
  3. 遍历
  4. 数组不修改原数组的API,字符串也可以使用(concat、slice)
  • 不同点:数组修改原数组的API,字符串都不可以使用,但是字符串也有很多属于自己的API

只读

只读:字符串中的所有的API都不会修改原字符串,只会返回新字符串

引用类型的对象:11个

String(字符串) Number(数字) Boolean(布尔)——具有包装类型

Array(数组) Function(函数) Math(数学) Date(日期) RegExp(正则) Error(错误) Object(面向对象)

Global - 全局对象:在浏览器端被window对象给代替了:window对象可以省略不写出来

包装类型:将原始类型的值变为一个引用类型的对象

为什么:前辈们发现字符串/数字/布尔经常都会被拿来使用,所以提前提供了包装类型封装为一个引用类型的对象,提供我们一些属性和方法(便于程序员操作)

何时使用:只要在你试图用原始类型的值去调用属性或者方法时,会自动套上包装类型

何时释放:属性或方法调用完毕后,包装类型自动释放

注意:为什么undefinednull不能使用.,不具有包装类型,没有任何的属性和方法

String API:

只读字符数组:字符串的任何API都不会修改原字符串,保存新字符串

转字符串

作用:3个

  1. 字符串中如果出现了和字符串冲突的符号,可用\将其转义为原文。举例:\" \'

  2. 特殊功能:换行:\n、制表符:\t

  3. 可以书写unicode号 表示一个字\uXXXX(汉字的第一个字:4e00,汉字的最后一个字:9fa5)

转换大小写

转换大小写:统一的转为大写或小写,再比较,忽略大小写(验证码)

  • 大写:var newStr=str.toUpperCase();
  • 小写:var newStr=str.toLowerCase();

获取字符串中指定位置的字符的ascii码

语法var ascii=str.charCodeAt(i);

通过ascii码转回原文

语法var 原文=String.fromCharCode(ascii);

检索字符串

检索字符串:检查索引/下标:从starti位置开始找右侧的第一个关键字的下标

作用:判断有没有

语法var i=str/arr.indexOf("关键字",starti);

注意

  1. starti可以省略,如果省略则从0开始
  2. 返回值:找到了,返回第一个字符的下标
  3. 没找到,返回-1
  4. 数组也可以使用此方法
举例:
鄙视题:找到所有关键字的位置
    var str="no zuo no die no can no bibi";
    var i=-1;
    while((i=str.indexOf("no",i+1))!=-1){
        console.log("找到了:"+i);
    }

拼接字符串

拼接字符串var newStr=str.concat(str1,str2...) (还不如+运算)

截取字符串:3个

  1. var subStr=str.slice(starti,endi+1);
  2. var subStr=str.substring(starti,endi+1);,不支持负数参数
  3. var subStr=str.substr(starti,n);,截取的个数,不必考虑含头不含尾

替换字符串

替换字符串:搭配正则表达式功能更强大

语法var newStr=str.replace("关键字"/正则表达式,"新内容");

切割/分割字符串

切割/分割字符串:作用:str <=> arr

语法var arr=str.split("自定义切割符");

注意

  1. 切割符可以自定义,切割过后返回一个数组,数组中不再包含切割符
  2. 如果传入的切割符是一个"",每一个字符都会被切开

去掉字符串的开头结尾的空白字符

语法str.trim();


扩展:创建元素并且渲染页面

  1. 创建空标签 语法var elem=document.createElement("标签名");

  2. 设置必要的属性或事件

    • elem.属性名="属性值";
    • elem.on事件名=function(){函数体} - 事件都可以在创建时提前绑定上
  3. 创建好的元素渲染到DOM树上

    • 父元素.appendChild(elem);