首頁  >  文章  >  web前端  >  一些常用的JavaScript 知識點整理

一些常用的JavaScript 知識點整理

怪我咯
怪我咯原創
2017-04-05 14:50:441326瀏覽

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 以及其他基於它的複雜資料型別。

✦ 基本型別:在記憶體中佔據實際大小的空間,賦值的時候,會在記憶體中建立一份新的副本。保存在 棧記憶體 中。

✦ 引用類型:指向物件的指標而不是物件本身,賦值的時候,只是創建了一個新的指標指向物件。儲存在 堆記憶體 中。

一些常用的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引擎具有自動垃圾回收機制,不需要太關注記憶體分配和垃圾回收問題。這兒就不展開了!

引用型別

前面提過,Object是唯一的複雜資料型別,引用型別都是從Object型別繼承而來。

Array:陣列類型

Date:日期類型

✦ RegExp:正規表示式類型,這個多學有好處!

✦ 等等...

那问题来了,我们用的最多的函数是什么数据类型呢?答案是Function类型!

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

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

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

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

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

面向对象编程

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

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

函数表达式

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。

✦ 递归调用。

按这个模式,找几个经典的递归练练手,就熟悉了。

闭包

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

那么闭包是什么呢?如果一个函数可以访问另一个函数作用域中的变量,那么前者就是闭包。自然,创建闭包的常用方式就是在一个函数内部创建另一个函数!

并没有什么神奇的,这就是闭包,闭包就是一个理所当然的现象,也就是在父函数中定义子函数,然后子函数可以访问父函数的作用域。

我们通常是因为被闭包坑了,才会被闭包吓到,尤其是面试题里一堆闭包。

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

/* 我们通过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主要的内容就这些了,所以不要被自己吓到了。


以上是一些常用的JavaScript 知識點整理的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn