JS基础知识梳理以及栈和堆知识点

1,119 阅读11分钟

**--- theme: channing-cyan

1.目标

  1. 形成高级编程人员的软件开发思维
  2. 掌握js语言的高级写法
  3. 了解一些不常用的语法

基础复习

  • 变量
    • 变量的作用,语法,命名规则
    • var:可以定义变量,但是会有变量名提升,没有作用域的概念
    • let:没有变量名提升,,要先定义后使用,有作用域的概念
    • const:定义常量(一旦定义赋值之后就不能再修改的变量:以后模块中的成员一般都是const)
  • 数据类型
    • 基本数据类型
      • 也叫值类型
      • Number String,Boolean,undefined,null
    • 复杂数据类型
      • 也叫引用类型,它的本质是一个地址空间
      • 函数,数组,对象
      • 引用类型的空,一般设置为nul
  • 类型转换
    • 转换成字符串
      • toString()
      • "" +
    • 转换成数字
      • parseInt():转换,直到碰到非数值,如果第一个就是非数值,就返回NaN
      • Number():只要有非数值字符串,就返回NaN
      • -0
    • 转换成布尔值
      • js中的false:0,'',"",null,undefined,NaN
      • Boolean(需要转换的变量)
  • 运算符
    • 算术运算符:+ - * / %
    • 赋值运算符:=
    • 比较运算符:
      • > < >= <= == != ===
      • ==:值要相等,它会将字符串转换为数值
      • ===:值和类型也要相等
    • 逻辑运算符:!&& ||
    • 自增自减运算符: ++ --
      • 写在前面:先执行,再使用
      • 写在后面:先使用再执行
    • 运算符的优先级:()
  • 流程控制语句
    • 顺序结构
    • 分支结构:if-else
    • 循环结构:for , forEach(function(value,index){})
  • 数组
    • 创建数组的两种方式
      • let arr = [ ] --语法糖
      • let arr = new Array(...)
    • 下标和长度
      • 只有一个属性:length
    • 取值和存值
      • 通过索引
      • 如果发现操作的数据类型是数组,那么找索引
    • api
      • 增加:push unshift
      • 删除:splice(index,length)
      • 修改:arr[i] = ''
      • 查询(获取): arr[i]
  • 函数
    • 声明和调用
    • 形参和实参
      • 数量对应
      • 顺序对应
      • 类型对应
    • 返回值
      • 如果方法没有返回值,默认会返回undefined
      • 通过return返回值
      • 一个函数中只能有一个return被执行
      • return只能返回一个变量,返回对象更常用
  • 对象
    • 创建对象的方式:{}
    • 属性和方法:特征和行为
    • 存值和取值
      • 通过key进行值的存储和获取
        • obj[key]:如果key是变量,则只能使用[]方式操作
        • obj.key
      • 赋值:
        • 如果key已经存在,就是修改
        • 如果key不存在则是添加--动态特征
    • 对象的遍历
      • for..in >> for(let key in obj) {}
  • 内置对象
    • Math
      • 静态类型
      • 通过Math构造函数直接调用里面的成员
      • 常见:floor ceil random abs
    • Array
    • Date
      • new Date():获取当前日期
      • Date.now():获取当前日期离默认日期的毫秒值
    • String

typeof关键字

typeof操作符返回一个字符串,返回的是操作数的类型

  • typeof 基本类型返回的是字符串值

    • 字符串 》》 string
    • bool 》》boolean
    • 数值 》》number
  • typeof 对象 返回的是object

  • typeof 函数 返回的是function

  • typeof null 返回的object

  • typeof undefined:返回undefined

  • typeof 数组:返回object

逻辑中断

&&:从左到右的顺序进行判断,如果发现某个操作数的逻辑判断是false,那么就不用继续判断了。

||:从左到右的顺序进行判断,如果发现某个操作数的逻辑是true,那么就不用继续判断了。

function fn (n1, n2) {
  n1 = n1 || 0
  n2 = n2 || 0
  console.log(n1 + n2)
}

switch-case分支结构

  • 1.语法
switch(表达式){	// 不是布尔类型:是一个确定的变量
    case1:	 // 值1,值2...都是字面量
        表达式的结果 === 值1,需要执行的代码
        break;
    case2:
        表达式的结果 === 值2,需要执行的代码
        break;
    case3:
        表达式的结果 === 值3,需要执行的代码
        break;
    .......
    default:
        表达式的结果和上面所有的case后面的值都不全等,则会执行这里的代码
        break;
}
  • 2.注意事项
    • 1.表达式的结果要和值一定是全等的关系===
    • 2.break作用:结束该switch语句,所以一般情况下要加上,如果不加上则会发生穿透
      • 穿透:从上一个case代码快执行到下一个case代码快
      • break关键字的作用就是防止穿透
    • 3.default语句可以写在任何地方,也可以省略,但是一般写在最后,这是一种代码规范
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>

<script>
    /**
     switch-case分支结构:常用于值匹配
        * 匹配:全等的关系

     * switch(条件值){
            case 值1:
                条件值 === 值1,需要执行的代码
                break;
           case 值2:
                条件值 === 值2,需要执行的代码
                break;
           case 值3:
                条件值 === 值3,需要执行的代码
                break;
            .......
           default:
                条件值和上面所有的case后面的值都不全等,则会执行这里的代码
                break;
        }

    /**switch语句注意事项
     * 1.表达式的结果要和值一定是全等的关系===
     * 2.break作用:结束该switch语句,所以一般情况下要加上,如果不加上则会发生穿透
     *      * 穿透:从上一个case代码快执行到下一个case代码快
     *      * break关键字的作用就是防止穿透
     *  3.default语句可以写在任何地方,也可以省略,但是一般写在最后,这是一种代码规范
     */

    //示例:用户输入黑马学科编号,告诉用户学习什么学科  1-前端  2-PHP  3-java  4-UI
    let subject= +prompt("请输入您要报名的学科编号,1-前端  2-PHP  3-java  4-UI");

    switch (subject){
        case  1:
            alert("恭喜你选择了2020年最有钱途的学科!");
            break;
        case  2:
            alert("选择了PHP,臭流氓!");
            break;
        case  3:
            alert("选择了Java,请问植发多少钱一根?");
            break;
        case  4:
            alert("未来的UI视觉交互设计师");
            break;
        default :
            alert("脑子有包");
            break;
    }
</script>
</body>
</html>

switch-case穿透用法

  • 合理穿透:多种值需要执行相同代码
<script>
    /**合理穿透:当存在多种值需要执行相同代码时使用穿透可以节省代码
     * 用户输入某一个月份,告诉用户这个月份属于什么季节
     * 12,1,2 冬季
     * 3,4,5 春季
     * 6,7,8 夏季
     * 9,10,11 秋季
     */
    let month = +prompt("请输入月份");
    switch (month){
        case 12:
        case 1:
        case 2:
            alert("冬季");
            break;
        case 3:
        case 4:
        case 5:
            alert("春季");
            break;
        case 6:
        case 7:
        case 8:
            alert("夏季");
            break;
        case 9:
        case 10:
        case 11:
            alert("秋季");
            break;
        default:
            alert("你来自火星吧?");
            break;
    }

</script>

三元表达式

  • 1.运算符根据参与运算的值数量分为一元、二元、三元运算符

    • 一元运算符:只能操作一个值 ++ -- !
    • 二元运算符:操作两个值 1 + 1 1 > 0
    • 三元运算符:操作三个值
  • 2.三元运算符语法

    • 三元运算符: ?:

    • 三元表达式:

      bool表达式?代码1:代码2
      
      • 1.如果表达式成立则执行代码1,否则执行代码2
  • 2.如果代码1或者代码2有运算结果则三元运算式的结果就是他们其中的一个

    • 三元运算符做的事和if-else类似,只是代码更简洁

    • 三元表达式中:表达式部分永远是条件,最终代表整个结果的不是代码1 就是 代码2

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>

</body>

<script>
    /*
    一元运算符:由一个值参与的运算符  :  a++    a--  !a
     二元运算符:  由两个值参与的运算符 :  a + b   a > b
     三元(三目)运算符:由三个值参与的运算符
    */

    /*
     * 三元运算符:  ?:
     * 三元表达式:   表达式?代码1:代码2
     *      * 1.如果表达式成立则执行代码1,否则执行代码2
     *      * 2.如果代码1或者代码2有运算结果则三元运算式的结果就是他们其中的一个
     *
     * 三元运算符做的事和if-else类似,只是代码更简洁
     */

    //案例1:
    let num1 = 10;
    num1 > 0 ? console.log('哈哈') : console.log('呵呵');

    //上面这个三元表达式等价于下面的if - else语句
    // if(num1 > 0){
    //     console.log ( "哈哈" );
    // }else{
    //     console.log ( "呵呵" );
    // }

    //案例2:三元表达式一般应用是用来赋值
    let num2 = 20;
    let res2 = num2 > 0 ? num2 + 1 : num2 - 1;
    console.log ( res2 );//21

    //上面这个三元表达式等价于下面的if - else语句
    // if(num2 > 0){
    //     res2 = num2 + 1;
    // }else{
    //     res2 = num2 - 1;
    // }

    //练习:输出性别  (实际开发中,性别通常会使用一个布尔类型来存储,这样存储效率更高)
    let name = "马云";
    let age = 38;
    let gender = true;      //true男 1         false女 0
    console.log("我的名字是"+name+",我的年龄是"+age+",我是一个"+(gender == true ? "男":"女")+"生");


</script>
</html>

三种分支结构语法总结

  • 1.原则上,三种分支结构语句之间可以互转,只不过每一种分支结构语句适用场景不一样
  • 2.if分支结构:适合条件判断
    • 最常用:if-else 两种互斥条件判断
  • 3.switch-case 适合做固定值匹配
  • 4.三元表达式: 比if-else代码更简洁,但是代码量较多时易读性变差。以后它有一个最大的优点:可以在模板中添加
    • 反引号
    • 模板引擎
    • 插值表达式

while循环结构

  • 循环三要素
    • 起始值
    • 条件
    • 变量的变化
  • 1.语法:
while(条件 true/false){
    循环体/需要重复执行的代码;
}
  • 执行步骤:
    • 1.判断条件是否成立
      • 1.1 如果成立,执行循环体代码,然后重复步骤1
      • 1.2 如果不成立,结束循环,执行大括号后面的代码
  • 3.注意点
    • (1)小括号中的语句,无论结果是什么都会转换成布尔类型来判断是否成立
    • (2)避免写一个死循环
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>

<script>

    //需求:打印 3 次 '我爱大前端'

    //复制粘贴弊端:(1)代码冗余  (2)不便于维护
    // console.log ( "我爱大前端" );
    // console.log ( "我爱大前端" );
    // console.log ( "我爱大前端" );

    
    /*
    1.循环结构 : 代码重复执行

    2. 语法
        while(条件 true/false){
            循环体 :需要重复执行的代码
        };

    执行步骤
        1. 判断条件是否成立
            2.1 成立:执行循环体代码。 重复步骤1
            2.2 不成立,循环语句结束,执行大括号后面的代码

    */
    let i = 1;//循环变量,记录循环次数
    while(i<=3){
        console.log ( "我爱大前端" );
       i++;////循环变量自增  自增的目的是为了控制循环的次数,否则这是一个死循环
    }
    console.log('111');//大括号外的代码与循环结构没有关系,还是顺序执行


   //循环语句注意点:
    //循环语句注意点:
    //(1)小括号中的语句,无论结果是什么都会转换成布尔类型来判断是否成立
    //(2)避免写一个死循环
    //let num = 1;
    // while(num < 10){
    //     console.log ( num );
    //     num++;//改变循环变量的值,可以避免死循环
    // }

</script>
</body>
</html>

do-while循环结构

  • 1.语法:
do{
    循环体;
}while( 条件 );
  • 2.执行过程
    • 1.先执行循环体代码
    • 2.执行条件语句
      • 如果结果为true,执行循环体代码
      • 如果为false,循环结束
    • 3.重复步骤2
  • 3.do-while和while实现的循环其实是一样的,只有一个不同点:do-while循环不管怎样先执行一次循环体代码,然后再判断条件
    • while循环:先奏后斩(先判断条件再执行循环体)
    • do-while循环:先斩后奏(不管怎样先执行一次循环体代码,然后再判断条件)
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>



<script>

    /* 1. 学习目标 : do-while循环 
       
       2. 学习路线
            (1)复习while语法特点
            (2)学习do-while语法
            (3)介绍do-while语法应用场景
    
    */


    //1.while循环:
    //let i = 1;
    // while(i > 5){
    //     //循环条件不成立,循环体一次都不执行
    //     console.log ( "哈哈哈哈" );
    //     i++
    // }

    //2.do-while循环
    /**
     do-while语法:(用的很少)

     do{
            循环体;
        }while( 条件 );

     特点:无论如何先执行一次循环体,然后再去判断条件
     */
    let i = 1;
        do{
            console.log ( "呵呵呵呵呵" );
            i++;
        }while (i > 5);



    //while循环:先奏后斩(先判断条件再执行循环体)
    //do-while循环:先斩后奏(不管怎样先执行一次循环体代码,然后再判断条件)


    //3.do-while循环与while循环应用场景
    //无论如何需要先执行一次循环体,使用do-while代码更简洁

    //例子:让用户输入账号和密码,如果输入正确就登陆成功,如果输入错误就让他一直输入

    //while循环实现
    // let username = prompt('请输入账号');
    // let password = prompt('请输入密码');
    //
    // while(username != 'admin' || password != '123456'){
    //     username = prompt('请输入账号');
    //     password = prompt('请输入密码');
    // }

    //do-while实现
    do{
        let username = prompt('请输入账号');
        let password = prompt('请输入密码');
    }while(username != 'admin' || password != '123456')

</script>
</body>
</html>

三种循环结构总结

  • 1.原则上,三种循环结构语句之间可以互转,只不过每一种语句的适用场景不一样
  • 2.最常用:for循环:适合循环次数固定
  • 3.while循环:适合循环次数不固定
  • 4.do-while循环:适合循环次数不固定,但是循环体代码至少要执行一次

2-数组排序-冒泡算法

  • 算法algorithm,是一种解决问题的方法
  • 算法的目标:使用最少的内存,最短的时间,解决最多的问题
  • 冒泡算法:
    • 重复地走访过要排序的元素列,依次比较两个相邻的元素
      • 顺序正确:代表位置正确,不需要交换
      • 顺序错误:交换两个元素,让顺序正确
<script>
	/*
		冒泡算法(顺序:从小到大)
		1.从第一个元素开始,比较下一个元素
			* 如果前面一个大于后面的元素:交换
			* 如果前面一个小于或者等于后面的元素:不用动
		2.循环比较数组中的每一个元素:直到最大的那个元素到达数组最后
		
		3.一次循环,只能得出最大的数据排到最后(最前),因此需要根据数组元素的长度来进行循环嵌套
			* 一次只能让当前最大的到最后(如果原来最大的就在最后,那么就是次大的)
			* 根据数组长度实现:每次都能得出一个最大,直到全部都排好序
	*/
    // 定义一个无序数组
    let arr = [3,5,1,8,6,2];
    
    // 外部循环:决定里面循环的次数
    for(let i = 0;i < arr.length;i++){
        // 内部循环:决定当前最大的元素跑到正确的位置去
        for(let j = 0;j < arr.length - 1;j++){
            // j < arr.length - 1 是因为需要进行向后一个进行元素匹配
            
            // 判定当前元素与后一个元素的关系:前面大于后面:交换(其他情况不用变)
            if(arr[j] > arr[j+1]){
                // 交换两个元素的值:采用第三个变量
                let temp = arr[j];
                arr[j] = arr[j+1];
                arr[j+1] = temp;
            }
        }
    }
    
    console.log(arr);		// [1,2,3,5,6,8]
</script>

3.面向对象编程

基本概念

所谓面向对象就是指:你想做一件事情,你自己做不到,那么就去找到能够实现这个功能的对象,调用它的方法(传递参数,遵守方法的规则)

什么是对象?

Everything is object (万物皆对象)

对象到底是什么,我们可以从两次层次来理解。

(1) 对象是具体事物的抽象。

一本书、一辆汽车、一个人都可以是对象,当实物被抽象成对象,实物之间的关系就变成了对象之间的关系,从而就可以模拟现实情况,针对对象进行编程。

问: 书是对象吗

(2)对象是无序键值对的集合,其属性可以包含基本值、对象或者函数

每个对象都是基于一个引用类型创建的,这些类型可以是系统内置的原生类型,也可以是开发人员自定义的类型。

什么是面向对象?

面向对象编程 —— Object Oriented Programming,简称 OOP ,是一种编程开发思想。

在面向对象程序开发思想中,每一个对象都是功能中心,具有明确分工,可以完成接受信息、处理数据、发出信息等任务。 因此,面向对象编程具有灵活、代码可复用、高度模块化等特点,容易维护和开发,比起由一系列函数或指令组成的传统的过程式编程(procedural programming),更适合多人合作的大型软件项目。

面向对象与面向过程:

  • 面向过程就是亲历亲为,事无巨细,有条不紊,面向过程是解决问题的一种思维方式,(执行者的角度)
    • 关注点在于解决问题的过程(先xx,然后xx,在xx);
  • 面向对象就是找一个对象,让她去做这件事情(指挥者的角度)
    • 关注点在找到能解决问题的对象上。
  • 面向对象不是面向过程的替代,而是面向过程的封装
  • 例如洗衣服(面向过程和面向对象的区别)

面向对象的特性:

  • 封装性
    • 将功能的具体实现,全部封装到对象的内部,外界使用对象时,只需要关注对象提供的方法如何使用,而不需要关心对象的内部具体实现,这就是封装。
  • 继承性
    • 在js中,继承的概念很简单,一个对象没有的一些属性和方法,另外一个对象有,拿过来用,就实现了继承。
    • 注意:在其他语言里面,继承是类与类之间的关系,在js中,是对象与对象之间的关系。
  • [多态性]
    • 多态是在强类型的语言中才有的。js是弱类型语言,所以JS不支持多态

4.栈和堆

(stack)中主要存放一些基本类型的变量和对象的引用其优势是存取速度比堆要快,但缺点是存在栈中的数据大小与生存期必须是确定的,缺乏灵活性,

(heap 多)用于复杂数据类型(引用类型)分配空间,例如数组对象、object对象;它是运行时动态分配内存的,因此存取速度较慢。

栈和堆的图例

image.png

  • 值类型:简单类型,变量在存储的时候,存储的是值本身。如果做为参数传递,仅仅是将栈空间中存储的内容复制一份进行赋值,修改其中一个变量另外不会变化

  • 引用类型:复杂类型,变量在存储的时候,存储的是对象的地址,如果做为参数传递,是将栈空间中存储的引用地址复制一份进行赋值,造成实参和形参指向同一个空间,修改其中一个另外一个也会变化

5.创建对象的几种方式

内置构造函数创建

我们可以直接通过 new Object() 创建:

//在js中,对象有动态特性,可以随时的给一个对象增加属性或者删除属性。
var person = new Object()
person.name = 'Jack'
person.age = 18

person.sayName = function () {
  console.log(this.name)
}

缺点:麻烦,每个属性都需要添加。

对象字面量创建

var person = {
  name: 'Jack',
  age: 18,
  sayName: function () {
    console.log(this.name)
  }
}

缺点:如果要批量生成多个对象,应该怎么办?代码很冗余

简单改进:工厂函数

我们可以写一个函数,解决代码重复问题:

function createPerson (name, age) {
  return {
    name: name,
    age: age,
    sayName: function () {
      console.log(this.name)
    }
  }
}

然后生成实例对象:

var p1 = createPerson('Jack', 18)
var p2 = createPerson('Mike', 18)

缺点:但却没有解决对象识别的问题,创建出来的对象都是Object类型的。

继续改进:构造函数

构造函数是一个函数,用于实例化对象,需要配合new操作符使用。

function Person (name, age) {
  this.name = name
  this.age = age
  this.sayName = function () {
    console.log(this.name)
  }
}

var p1 = new Person('Jack', 18)
p1.sayName() // => Jack

var p2 = new Person('Mike', 23)
p2.sayName() // => Mike

而要创建 Person 实例,则必须使用 new 操作符。 以这种方式调用构造函数会经历以下 4 个步骤:

  1. 创建一个新对象
  2. 将构造函数的作用域赋给新对象(因此 this 就指向了这个新对象)
  3. 执行构造函数中的代码
  4. 返回新对象

构造函数需要配合new操作符使用才有意义,构造函数首字母一般为大写

构造函数的缺点

使用构造函数带来的最大的好处就是创建对象更方便了,但是其本身也存在一个浪费内存的问题:

function Person (name, age) {
  this.name = name
  this.age = age
  this.type = 'human'
  this.say = function () {
    console.log('hello ' + this.name)
  }
}

var person1 = new Person('lpz', 18)
var person2 = new Person('Jack', 16)
console.log(person1.say === person2.say) // => false

解决方案:

图示

image.png

解决:提取同一个 say 方法

  1. 解决了浪费内存的弊端
  2. 但是造成了 污染全局变量 的问题
    // 提前将say 声明好
    function say() {
      console.log(this.name);
    }
    function createStudent(name, age) {
      this.name = name;
      this.age = age;
      this.say = say
    }

    const obj = new createStudent("悟能", 83);
    const obj1 = new createStudent("悟能1", 84);

    console.log(obj.say === obj1.say); // true 

图示

image.png

6.定义函数的三种方式

  1. 函数声明
  2. 函数表达式
  3. 构造函数Function

函数声明

常规的方式

fn();//函数声明可以先调用,在声明
function fn(参数..){
  console.log("这是函数声明")
  return 返回值
}

函数表达式

const fn = function() {
  console.log("这是函数表达式");  
}
fn();//函数表达式必须先声明,再调用

构造函数Function

所有函数都可以通过Function构造函数来创建

函数也可以看成对象

new Function([前面是参数列表,]最后一个参数是函数体)
var fn1 = new Function("a1", "a2", "alert(a1+a2)");
fn1(1,2);

**