JavaScript 知识点整理

作者: 齐修_qixiuss | 来源:发表于2016-10-31 07:58 被阅读15301次

    JavaScript是按照ECMAScript标准设计和实现的,后文说的JavaScript语法其实是ES5的标准的实现。
    先说说有哪些基础语法?

    最基础语法有哪些?

    基础语法几乎所有的语言差异不大,无非数据类型、操作符、控制语句、函数等,简单列举下。

    5种基本数据类型 & 1种复杂的数据类型

    JavaScript包含5种基本数据类型,分别是undefined / null / boolean / number / string,基本数据类型就这五种,没有其他的!
    JavaScript包含1种复杂的数据类型,就是Object类型,Object类型是所有其他对象的基类。
    注意:JavaScript并不区分浮点数和整数,都是用number来表示。

    前面提到的5种基本数据类型,以及这儿的1种复杂数据类型,这就是数据类型的全部了!

    基本操作符

    这个是常识,知道怎么回事就好。
    常用的操作符包括:算术操作符、关系操作符、布尔操作符、赋值操作符等。

    控制语句

    这就是我们常说的if-else之类的控制语句。
    常用的并不多:if语句、switch语句、for语句、while语句、for-in语句。

    函数

    函数就是一小段逻辑的封装,理论上逻辑越独立越好。
    JavaScript函数相对其他语言来说有很大不同。JavaScript函数既可以作为参数,也可以作为返回值。
    此外JavaScript函数可以接受任意数量的参数,并且可以通过arguments对象来访问这些参数。

    任何一门语言的基础语法都是相通的,除开一些细节差异,大致就是上面这些了:数据类型、操作符、控制语句、函数、模块等等。

    接下来介绍稍微复杂的一些概念。

    变量、作用域、内存问题

    变量

    JavaScript变量分为两种:基本类型和引用类型。其中基本类型就是前面提到的5种基本数据类型,引用类型就是前面提到的Object以及基于它的其他复杂数据类型。
    ✦ 基本类型:在内存中占据实际大小的空间,赋值的时候,会在内存中创建一份新的副本。
    ✦ 引用类型:指向对象的指针而不是对象本身,赋值的时候,只是创建了一个新的指针指向对象。

    变量内存分配

    一句话就是,基本类型在内存中是实际的值;而引用类型在内存中就是一个指针,指向一个对象,多个引用类型可能同时指向同一个对象。

    那么,如何确定某个变量是哪种数据类型呢?
    确定一个变量是哪种基本类型用typeof操作符。
    确定一个变量是哪种引用类型用instanceof操作符。
    这个别忘了!

    作用域

    变量是在某个特定的作用域中声明的,作用域决定了这些变量的生命周期,以及哪些代码可以访问其中的变量。
    JavaScript作用域只包括全局作用域和函数作用域,并不包含块级作用域!

    作用域是可以嵌套的,从而形成作用域链。由于作用域链的存在,可以让变量的查找向上追溯,即子函数可以访问父函数的作用域=>祖先函数的作用域=>直到全局作用域,这种函数我们也称为闭包,后文会介绍。

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

    如下图所示,每个作用域能够访问到的变量以及嵌套的作用域可向上追溯。


    作用域链

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

    内存问题

    JavaScript引擎具有自动垃圾回收机制,不需要太关注内存分配和垃圾回收问题。这儿就不展开了!

    引用类型

    前面提过,Object是唯一的复杂数据类型,引用类型都是从Object类型上继承而来。
    ✦ Array:数组类型
    ✦ Date:日期类型
    ✦ RegExp:正则表达式类型,这个多学学有好处!
    ✦ 等等...
    那问题来了,我们用的最多的函数是什么数据类型呢?答案是Function类型!
    诶,好像发现了点什么东西?由于Function是引用类型,而JavaScript又可以往引用类型上加属性和方法。那么,函数也可以!这也是JavaScript函数强大和复杂的地方。也就是说:函数也可以拥有自定义方法和属性!

    此外,JavaScript对前面提到的5种基本类型的其中3种也做了引用类型封装,分别是Boolean、Number、String,但其实使用不多,了解就行。

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

    到此为止,JavaScript中基础的概念都差不多介绍了,其中函数和作用域相对来说复杂一些,其他的都比较浅显。
    接下来,我会介绍介绍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.prototype = 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);
        }
    });
    
    // 缺点:
    // factorial存在被修改的可能
    // 导致 return num * factorial(num - 1) 报错
    function factorial(num) {
        if (num <= 1) {
            return 1;
        } else {
            return num * factorial(num - 1);
        }
    }
    
    // 缺点:
    // arguments.callee,规范已经不推荐使用
    function factorial(num) {
        if (num <= 1) {
            return 1;
        } else {
            return num * arguments.callee(num - 1);
        }
    }
    

    递归就是这样,好多人还在使用arguments.callee的方式,改回函数表达式的方式吧,这才是最佳实践。

    啰嗦一句,好多人觉得递归难写,其实你将其分为两个步骤就会清晰很多了。
    ✦ 边界条件,通常是if-else。
    ✦ 递归调用。
    按这个模式,找几个经典的递归练练手,就熟悉了。

    闭包

    很多人经常觉得闭包很复杂,很容易掉到坑里,其实不然。

    那么闭包是什么呢?如果一个函数可以访问另一个函数作用域中的变量,那么前者就是闭包。由于JavaScript函数可以返回函数,自然,创建闭包的常用方式就是在一个函数内部创建另一个函数!
    这并没有什么神奇的,在父函数中定义子函数就可以创建闭包,而子函数可以访问父函数的作用域。
    我们通常是因为被闭包坑了,才会被闭包吓到,尤其是面试题里一堆闭包。

    闭包的定义前面提了,如何创建闭包也说了,那么我们说说闭包的缺陷以及如何解决?

    /* 我们通过subFuncs返回函数数组,然后分别调用执行 */
    
    // 返回函数的数组subFuncs,而这些函数对superFunc的变量有引用
    // 这就是一个典型的闭包
    // 那么有什么问题呢?
    // 当我们回头执行subFuncs中的函数的时候,我们得到的i其实一直都是10,为什么?
    // 因为当我们返回subFuncs之后,superFunc中的i=10
    // 所以当执行subFuncs中的函数的时候,输出i都为10。
    // 
    // 以上,就是闭包最大的坑,一句话理解就是:
    // 子函数对父函数变量的引用,是父函数运行结束之后的状态
    function superFunc() {
        var subFuncs = new Array();
        for (var i = 0; i < 10; i++) {
            subFuncs[i] = function() {
                return i;
            };
        }
    
        return subFuncs;
    }
    
    // 那么,如何解决上诉的闭包坑呢?
    // 其实原理很简单,既然闭包坑的本质是:子函数对父函数变量的引用,是父函数运行结束之后的状态
    // 那么我们解决这个问题的方式就是:子函数对父函数变量的引用,使用运行时的状态
    // 如何做呢?
    // 在函数表达式的基础上,加上自执行即可。
    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 outputNumbers(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;
        }
    
        //构造函数
        MyObject = function() {};
        //公有/特权方法
        MyObject.prototype.publicMethod = function() {
            privateVariable++;
            
            return privateFunction();
        };
    })();
    

    总结说点啥?

    这差不多就是JavaScript的一些基础语法和稍微高级一些的用法,其实所谓的高级,都是JavaScript“不太成熟”的表现,尤其是面向对象,出于工程化的需要但是JavaScript本身并不完美支持。好在ES6最新标准解决了很多问题,结合Babel用起来也不用太考虑兼容性问题,如果你是新手的话,建议你直接去撸ES6+Babel吧。

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

    JavaScript作为一门动态语言,和其他语言有较大的差异,这也造成很多人学习JavaScript时会觉得难学。但你现在看看前文,虽然是一个简略的总结,但JavaScript主要的内容就这些了,所以不要被自己吓到了。
    再补一句,如果你是新手的话,建议你直接去撸ES6+Babel吧。

    相关文章

      网友评论

      • 六把刀:最后一题有点绕,你得再写点比如 暴露给了Function的原型链上
      • 六把刀:最后的封装私有变量代码是不是有误
        并没有暴露的对象给谁
        齐修_qixiuss:第一:私有变量和是否暴露对象无关,是面向对象的一个概念,更多是为了不污染全局。
        第二:此外,MyObject是全局变量,对外暴露了公有方法。
      • xjccc:新手。。能否推荐一下应该从哪里看起。现在很多东西有点看不懂-。-
        xjccc:@齐修_qixiuss :confounded: 好的吧~
        齐修_qixiuss:@xJ丶王小京 找一本书,然后按照这篇博客的知识点详细看看...
      • f59b4193e7cb:继承的时候应该是把父类实例赋值给子类的构造函数的原型吧。
        你写的是:将子类实例赋值给父类构造函数的原型即可。
        //子类继承父类
        SubType.prototype = new SuperType();
        f59b4193e7cb:@齐修_qixiuss 太客气啦!😄
        齐修_qixiuss:@jimmyisjimmy 多谢指出,已修改
      • 奇奇怪怪癖:幸亏还能看懂
      • WangXingtong:技术贴,顶!d=====( ̄▽ ̄*)b
      • Mango97:js初学者
      • 我就是大表哥:这本书我看过。。这些东西在第六章。。面向对象编程。。。代码你就把那些参数改一下嘛
      • 小月对饮:高一党表示,虽然看不懂但好厉害的样子
        acaa303f30dd:@importantage 厉害了 高一
      • 平凡而简单:为什么建议新手直接学习ES6+Babel了!
        是不是说ES6基本颠覆了js,还是因为RN?
      • 94de8b6897a4:技术贴很喜欢
      • 波波一起飞:看了一遍犀牛书,锋利的jqury,JavaScript的设计模式;回头看看你写的总结,简单多了;感觉大脑有了js相关知识的思维导图
      • 54f1e909cb46:数据类型不提一下 ES6 提出的 Symbol 类型?
        齐修_qixiuss:@mecweb 本文基于ES5整理,虽然推荐ES6,由于历史原因,还没有完全拥抱ES6,所以不对ES6评论太多,只是推荐!
      • ghwaphon:看得出作者很用心在记录,给你点个赞
      • 異議:很好,精简
        齐修_qixiuss:@異議 多谢,这篇文章本意就是做减法,不做加法!初学者可以在这个基础上做加法...
      • 98720347038f:加油!!我是简书 官方专题 【大学生活】的主编,欢迎加我微信【sukie428】,来勾搭,我们还有个全国大学生群欢迎来玩。
      • Silence_wh:<javascript高级程序设计> :+1:
        齐修_qixiuss:@Silence_wh :pray: 在这儿也推荐下这本书
      • 杨斌1994:js分原始类型和集合类型,集合类型包括原生对象和数组,其中js默认实现了对象的扩展包括数组对象,正则对象,异常对象等。
        闭包的本质是函数外部有指向其内部地址的引用,作用域链也好变量也罢。
        闭包也可以方便的实现函数式编程范式。
        我和楼主一样非常喜欢js语言,虽然“出生寒门”,怎奈何天生丽质难自弃啊d=(´▽`)=b
      • SeaseeYoul:赞,温故而知新。
        希望作者有时间的话能讲讲webpack和gulp的常用配置,以及手写配置~
        :grin:
        齐修_qixiuss:@SeaseeYoul 哈哈哈,挖的坑有人开始催了,性能优化的坑,webpack&gulp整理开源的坑...只能说,我在抽时间整理...欲哭无泪 :sweat:
      • Eugene1024:才学会点基础的js
        齐修_qixiuss:@z不羁的风 那就以这个为重点线,多翻翻书。这是一篇做减法的博客,不是做加法的,希望对新人老人都有一些帮助
      • 彳余三岁:🤔总觉得递归代码第八行多半个括号)不知道是不是错觉
        齐修_qixiuss:@帅气的昵称oO 哈哈,并没有多 :wink:

      本文标题:JavaScript 知识点整理

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