JavaScript 学习笔记

作者: 残夜孤鸥 | 来源:发表于2016-10-31 14:56 被阅读135次

    五种基本数据类型和一种复杂数据类型

    基本数据类型5种
     undefined 
     null 
     boolean 
     number    不区分浮点数和整数, 一律使用number来表示
     string
    
    复杂数据类型1种
    Object类型, 也是所有其他对象的基类
    

    最基础的语法:

    编程语言基础语法差异不大, 无非是数据类型, 操作符, 控制语句, 函数等
    

    基本操作符:

    常用的操作股包括:算术操作符 , 关系操作符, 布尔操作符, 赋值操作符
    

    控制语句

    if语句
    switch语句
    for语句
    while语句
    for-in语句
    

    函数

    函数是一小段逻辑的封装, 理论上逻辑越独立越好.
    JavaScript函数相对其他语言来说有很大不同. JavaScript函数既可以作为参数, 也可以作为返回值.
    此外JavaScript函数可以接受任意数量的参数, 并且可以通过arguments对象访问这些参数.
    任何一门语言的基础语法都是相通的, 除开一些细节差异, 大致就是上面这些了: 数据类型,操作符, 控制语句, 函数, 模块等等
    

    复杂概念,进阶
    变量,作用域,内存问题
    1.变量

    JavaScript变量分为两种: 基本类型和引用类型.其中基本类型就是前面的五种基本数据类型 引用类型就是前面的Object以及基于它的复杂数据类型.
    

    基本数据类型

    在内存中占据实际的大小控件, 赋值的时候, 会在内存中穿件一份新的副本.保存在"栈内存"中
    

    引用类型:

    只想对象的指针而不是对象本身, 赋值的时候 只是其创建了一个新的指针指向对象.保存在"堆内存"中.
    

    简单来说就是:基本类型在内存中是实际的值;而引用类型在内存汇总就是一个指针,指向一个对象, 多个引用类型可能同时指向同一个对象.

    确定一个变量是哪种基本类型用typeof操作符.
    确定一个变量是哪种引用类型用instanceof操作符.
    

    作用域

    1.变量是在某个特定的作用域中生命的,  作用域决定了这些变量的生命周期,以及哪些代码可以访问其中的变量.
    2.JavaScript作用域只包括全局作用域和函数作用域, 并不包含块级作用域!
    3.作用域是可以嵌套的, 从而形成作用链.由于作用域链的存在, 可以让变量的查找向上追溯, 既子函数可以访问父函数的作用域=>祖先函数的作用域=>直到全局作用域, 这种函数我们成为闭包.
    

    例:

    var color = "blue";
    function changeColor() {
         var anotherColor = ""red;
               function swapColors() {
                       var temCorlor = anothrtColor;
                       color = temCorlor;
             //这里可以访问color, anotherColor, tempColor
           }
    }
    // 这里只能访问color, changeColor();
    

    作用域的概念看着简单, 实际应用会有不少问题, 遇到问题细心分析.

    内存问题:

    JavaScript引擎具有自动垃圾回收机制, 不需要太关注内存分配和垃圾回收问题.
    

    引用类型:
    Object是唯一的复杂数据类型, 应用类型都是从Object类型上继承而来.

    * Array : 数组类型
    * Date :日期类型
    * RegExp : 正则表达式类型
    * 等等...
    

    但我们用的最多的函数是Function类型:

    由于Function是引用类型, 而JavaScript又可以往引用类型上加属性和方法.那么. 函数也可以!这也是javaScript函数强大和复杂的地方.也就是说: 函数也可以拥有自定义方法和属性!
    JavaScript对前面提到的5种基本类型的其中三种也做了引用类型封装, 分别是Boolean, Number, String, 但使用不多, 只做了解.
    

    注: 在所有代码执行之前, 作用域就内置了两个对象, 分别是Global和Math, 其中浏览器的Global就是window.

    到此为止,JavaScript中基础的概念都差不多介绍了, 其中函数和作用域相对来说复杂一些, 其他的都很浅显.

    面向对象编程:

    JavaScript本身并没有类和接口的概念, 面向对象都是基于原型实现的.
    

    定义一个类:
    (我们使用构造函数+原型的方式来定义一个类)

    使用构造函数创建自定义类型, 然后使用new操作符来创建类的示例, 但是构造函数上的方法和属性在每个示例上都存在, 不能共享, 于是我们引入原型来实现方法和属性的共享
    

    例子:

    //构造函数
    function Person(name, age, job) {
            this.name = name;
            this.age = age;
            this.job = job;
            this.friends = [@"Shelby", "Court"];
    }
    // 原型
    Person.prototype = {
           constructor : Person,
           sayName: function() {
                 return this.name;
           }
    }
    // 实例化
    var person1 = new Person("Nicholas", 29, "Software Engineer");
    var person2 = new Person("Greg", 27, "Doctor");
    
    person1.friends.push("Van");
    alert(person1.friends); // 输出"Shelby, Count, Van"
    alert(person2.friends);// 输出"Shelby, Count"
    alert(person1.friends === person2.friends);//输出"false"
    alert(person1.sayName === person2.sayName);//输出true
    

    实现继承:
    如何让子类继承父类呢?JavaScript通过原型链来实现继承!
    如何构建原型链呢?将子类实例赋值给父类构造函数的原型即可.
    构建原型链之后, 子类就可以访问父类的所有属性和方法!

    // 父类
    function SuperType() {
          this.property = true;
    }
    SuperType.prototype.getSuperValue = function() {
             return this.property;
    }
    
    //子类
    function SubType() {
          this.subproperty = false;
    }
    
    //子类继承父类
    SubType.orototype = new SuperType();
    //给子类添加新方法
    SubType.prototype.getSubvalue = function() {
    return this.subproperty;
    }
    // 重写父类的方法
    SubType.prototype.getSuperValue = function() {
           return false;
    }
    // 实例化
    var instance = new SubType();
    console.log(instance.getSuperValue());//输出false
    

    函数表达式:
    JavaScript中有两种定义函数的方式: 函数声明和函数表达式

    使用函数表达式无需对函数命名, 从而实现动态 编程, 也既匿名函数. 有了匿名函数,JavaScript函数有了更强大的用处.
    

    递归:

    递归是一种很常见的算法, 景点例子就是斐波拉契数列. 
    // 最佳实践, 函数表达式
    var factorial = (function f(num)) {
           if (num <= 1) {
               return 1;
           } else {
            return num * f(num -1);    
           }
    }
    
    // 缺点1:
    // factorial 存在被修改的可能
    // 导致 return num *factorial (num - 1) 报错
    function facorial(num) {
          if (num <= 1) {
             return 1;
          } else {
               return num * factorial(num - 1);
          }
    }
    //缺点2:
    // argumentscallee, 规范已经不推荐使用
    function factorial(num) {
          if (num <= 1) {
              return 1;
          } else {
               return num * argument.callee(num - 1);
          }
    }
    

    注: 递归就是这样,arguments.callee已经是过去式, 改回函数表达式才是最常用的,实践出真知.要注意两点1.边界条件, 通常是if-else. 2.递归调用

    闭包:

    如果一个函数可以访问另一个函数作用域中的变量, 那么牵着就是闭包.犹豫JavaScript函数可以返回函数, 自然, 创建闭包的常用方式就是在一个函数里创建另一个函数.
    这很简单, 例如在父函数中定义子函数就可以创建闭包, 而子函数可以访问父函数的作用域.
    

    如何解决闭包的缺陷:

    0.我们通过subFuncs返回函数数组, 然后分别调用执行
    1. 返回函数的数组subFuncs, 而这些函数对superFunc的变量有引用
    2.当我们回头执行subFuncs中的函数的时候, 我们得到的i其实一直都是10,
    3.原因是当我们返回subFuncs之后, superFunc中的i=10
    4, 所以当执行subFuncs中的函数的时候, 输出i都为10.
    以上就是闭包最大的坑, 一句话的理解就是: 子函数对父函数变量的引用, 是父函数运行结束之后的变量的状态
    
    function superFunc() {
    var subFuncs = new Array();
    for (var i = 0; i<10; i++) {
         subFuncs[i] = function() {
              return i; 
              }
         }
          return i;
    }
    

    那么, 如何解决上诉闭包的坑呢?
    其实原理很简单, 既然闭包坑的本质是:子函数对父函数变量的引用, 是父函数运行结束之后的变量的状态,那么我们解决这个问题的方式就是: 子函数对父函数变量的引用, 使用运行时的状态, 就是在函数表达式的基础上, 加上自执行就好.

    function superFunc() {
           var subFuncs = new Array();
           for ( var i = 0: i <10; i++) {
                 subFuncs[i] = function(num) {
                       return function() {
                                   return num;
                       }
                 }(i)
           }
          return subFuncs;
    }
    

    综上, 闭包本身不是什么复杂的机制,就是子函数可以访问父函数的作用域.
    而由于JavaScript函数的特殊性, 我们可以返回函数, 如果我们将作为闭包的函数返回, 那么该函数引用的父函数变量是父函数运行之后的状态, 而不是运行时的状态, 这便是闭包最大的坑, 而为了解决这个坑, 我们常用的方式就是让函数表达式自执行. 此外, 由于闭包引用了祖先函数的作用域, 所以滥用闭包会有内存问题.

    闭包的主要用处:封装

    闭包可以封装私有变量或者封装块级作用域.
    -> 封装块级作用域
    JavaScript并没有块级作用域的概念, 只有全局作用域和函数作用域, 那么如果想要创建块级作用域的话, 我们可以通过闭包来模拟.
    创建并立即调用一个函数, 就可以封装一个块级作用域. 该函数可以立即执行其中的代码, 内部变量执行结束就会被立即销毁.
    
    function outputNumber(count) {
    // 在函数作用域下, 利用闭包封装块级作用域
    // 这样的话, i在外部不可用, 便有了类似块级的作用域
    (function() {
           for (var i = 0; i < count; i++) {
                alert(i);
          }
    })();
        alert(i); // 导致一个错误
    }
    
    //在全局作用域下, 利用闭包封装块级作用域
    //这样的话, 代码块不会对全局作用域造成污染
    (function()  {
          var now = new Date();
          if (now.getMonth() == 0 && now.getDate() == 1) {
              alert("Happy new year!");
    
          }
    })();
    // 是的, 封装块级作用域的核心就是这个: 函数表达式 + 自执行!
    (function() {
         // 这里是块级作用域
    })();
    

    -->封装私有变量
    JavaScript也没有私有变量的概念, 我们也可以使用闭包来实现公有方法, 通过隐藏变量暴露方法的方式来实现封装私有变量.

    (function() {
    //私有变量和私有函数
    var privateVariable = 10;
    function privateFunction(){
         return false;
    }
    // 构造函数
    MyObjc = function(){};
    // 公有/特权方法
    Myobject.prototype.pulicmethod = function() {
              privateVariable++;
              return privateFunction;
        };
    })();
    

    总结:

    * JavaScript的基础主要包括 : 5中基本数据类型, 1种复杂数据类型, 操作符, 控制              语句,函数等.
    * 了解基本的语法后, 要学习JavaScript的变量, 作用域, 作用域链
    * 常见的引用类型可以边查边用, 注重正则的学习
    * 面向对象编程的部分外面有很多种方式, 你只需记住使用构造函数+原型去定义一个类, 使用原型链去实现继承即可. 
    * 函数表达时间引出了几个比较好玩的东西: 递归, 闭包, 封装. 记住递归的最佳实践,闭包的定义及缺陷,闭包的适用场景.
    

    JavaScript作为一门动态语言, 和其他语言有较大的差异.

    相关文章

      网友评论

        本文标题:JavaScript 学习笔记

        本文链接:https://www.haomeiwen.com/subject/exzuuttx.html