ホームページ  >  記事  >  ウェブフロントエンド  >  JavaScriptの知識まとめ 基本構文から高度な使い方まで

JavaScriptの知識まとめ 基本構文から高度な使い方まで

高洛峰
高洛峰オリジナル
2017-02-04 16:53:091305ブラウズ

JavaScript は ECMAScript 標準に従って設計および実装されています。後述する JavaScript 構文は、実際には ES5 標準の実装です。

まず、基本的な文法とは何かについて話しましょう。


01-

最も基本的な文法は何ですか?

ほぼすべての言語の基本構文にはほとんど違いはなく、データ型、演算子、制御ステートメント、関数など以外は何もありません。ここでは簡単なリストを示します。

5 つの基本データ型と 1 つの複雑なデータ型

JavaScript には 5 つの基本データ型、つまり、未定義 / null / ブール値 / 数値 / 文字列が含まれており、これらが 5 つの基本データ型であり、他にはありません。

JavaScript には 1 つの複合データ型が含まれています。これは、オブジェクト型です。オブジェクト型は、他のすべてのオブジェクトの基本クラスです。 (注: JavaScript は浮動小数点数と整数を区別しません。それらはすべて数値で表されます。)

上記の 5 つの基本データ型と、ここでの 1 つの複雑なデータ型、これがすべてのデータ型です。


基本演算子

これは常識です。何が起こっているのかを知っておいてください。

一般的に使用される演算子には、算術演算子、関係演算子、ブール演算子、代入演算子などが含まれます。


制御ステートメント

これは、if-else などの制御ステートメントとよく呼ばれるものです。

一般的に使用されるものはそれほど多くありません: if ステートメント、switch ステートメント、for ステートメント、while ステートメント、for-in ステートメント。


関数

関数は、ロジックの小さな部分をカプセル化したものです。理論的には、ロジックの独立性が高いほど優れています。

JavaScript 関数は他の言語とは大きく異なります。 JavaScript 関数はパラメータと戻り値の両方を受け取ることができます。

さらに、JavaScript 関数は任意の数のパラメーターを受け入れることができ、これらのパラメーターには引数オブジェクトを通じてアクセスできます。

どの言語でも基本的な構文は同じですが、いくつかの細かい違いはありますが、データ型、演算子、制御ステートメント、関数、モジュールなどはおおよそ上記のとおりです。

次に、もう少し複雑な概念をいくつか紹介します。


02-

変数、スコープ、メモリの問題

変数

JavaScript 変数は、基本型と参照型の 2 つの型に分けられます。基本型は前述の 5 つの基本データ型であり、参照型は前述の Object とそれに基づくその他の複雑なデータ型です。

基本タイプ: メモリ内の実際のサイズのスペースを占有し、値を割り当てると、メモリ内に新しいコピーが作成されます。スタックメモリに保存されます。

参照型: オブジェクト自体ではなく、オブジェクトへのポインター。値を割り当てると、オブジェクトを指す新しいポインターが作成されます。ヒープメモリに保存されます。

JavaScript 知识点梳理 | 从基础语法到高级用法

一言で言えば、基本型はメモリ内の実際の値ですが、参照型はオブジェクトを指すメモリ内のポインタであり、複数の参照型が同じものを指す場合があります。同時に対象となります。

それでは、特定の変数がどのデータ型であるかを判断するにはどうすればよいでしょうか?

変数がどの基本型であるかを判断するには、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 知识点梳理 | 从基础语法到高级用法スコープチェーン

スコープの概念は単純に見えますが、実際に使用すると多くの問題が発生します。問題が発生した場合は、注意深く分析する必要があります。

メモリの問題

JavaScript エンジンには自動ガベージ コレクション メカニズムがあるため、メモリ割り当てやガベージ コレクションの問題にあまり注意を払う必要はありません。ここでは詳しく説明しません!


03-

参照型

前述したように、複合データ型は Object のみであり、参照型は Object 型から継承されます。

Array: 配列型

Date: 日付型

RegExp: 正規表現型、これについて詳しく学ぶとメリットがあります。

ちょっと待って…

それでは、最も一般的に使用される関数はどのデータ型なのかという質問です。答えは「関数型」です!

诶,好像发现了点什么东西?由于Function是引用类型,而JavaScript又可以往引用类型上加属性和方法。那么,函数也可以!这也是JavaScript函数强大和复杂的地方。也就是说:函数也可以拥有自定义方法和属性!

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

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

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

接下来,我会介绍介绍JavaScript中一些稍微复杂一些的概念:面向对象。


03- 

面向对象编程

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

为了简单,我们只分析面向对象的两个问题:

如何定义一个类?

如何实现类的继承

定义一个类

不扯其他的,直接告诉你。我们使用构造函数+原型的方式来定义一个类。

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

JavaScript 知识点梳理 | 从基础语法到高级用法

最后,我们将需要共享的方法和属性定义在原型上,把专属于实例的方法和属性放到构造函数中。到这儿,我们就通过构造函数+原型的方式定义了一个类。

// 构造函数
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通过原型链来实现继承!

如何构建原型链呢?将父类实例赋值给子类构造函数的原型即可。好绕,但是千万得记住了!

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

面向对象的知识可以用一本书来写,这儿只是简单的介绍下最基础最常用的概念。


03- 

函数表达式

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();
    };
})();

03- 

总结说点啥?

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

JavaScript的基础主要包括:5中基本数据类型、1种复杂的数据类型、操作符、控制语句、函数等。

了解基本的语法后,你还需要学习学习JavaScript的变量、作用域、作用域链。

常见的引用类型可以边查边用。作为过来人,建议多学学正则,对你的代码功底会有较大的提升。

面向对象编程的部分外面有很多种方式,你只需要记住使用构造函数+原型去定义一个类,使用原型链去实现继承即可。更多的扩展,去翻翻书吧。

函数表达式引出了几个比较好玩的东西:递归、闭包、封装。记住递归的最佳实践、闭包的定义及缺陷、闭包的适用场景。

JavaScript作为一门动态语言,和其他语言有较大的差异,这也造成很多人学习JavaScript时会觉得难学。但你现在看看前文,虽然是一个简略的总结,但JavaScript主要的内容就这些了,所以不要被自己吓到了。

再补一句,如果你是新手的话,建议你直接去撸ES6+Babel吧。

更多JavaScript 知识点梳理 | 从基础语法到高级用法相关文章请关注PHP中文网!

声明:
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。