前端基礎知識1
阿新 • • 發佈:2018-11-26
今天不知道什麼原因心血來潮,說一說JS中最最最最最基本的概念吧,但是很多人可能不知道喔。
自己有個小計劃就是希望五年內寫一門自己的語言,雖然已經有JS了。此處吹個牛逼,莫怪,莫怪。廢話不多說,還是寫心得吧。
基礎知識
(1)資料型別
- 分類
- 基本(值)型別
- String: 任意字串
- Number: 任意的數字
- boolean: true/false
- undefined: undefined
- null: null
- 物件(引用)型別
- Object: 任意物件
- Function: 一種特別的物件(可以執行)
- Array: 一種特別的物件(數值下標, 內部資料是有序的)
- 判斷
- typeof:
- 可以判斷: undefined/ 數值 / 字串 / 布林值 / function
- 不能判斷: null與object object與array
- instanceof:
- 判斷物件的具體型別
- ===
- 可以判斷: undefined, null
(2)資料 變數 記憶體
- 什麼是資料?
- 儲存在記憶體中代表特定資訊的’東東’, 本質上是0101…
- 資料的特點: 可傳遞, 可運算
- 一切皆資料
- 記憶體中所有操作的目標: 資料
- 算術運算
- 邏輯運算
- 賦值
- 執行函式
- 什麼是記憶體?
- 記憶體條通電後產生的可儲存資料的空間(臨時的)
- 記憶體產生和死亡: 記憶體條(電路版)>通電>產生記憶體空間==>儲存資料==>處理資料==>斷電==>記憶體空間和資料都消失
- 一塊小記憶體的2個數據
- 內部儲存的資料
- 地址值
- 記憶體分類
- 棧: 全域性變數/區域性變數
- 堆: 物件
- 什麼是變數?
- 可變化的量, 由變數名和變數值組成
- 每個變數都對應的一塊小記憶體, 變數名用來查詢對應的記憶體, 變數值就是記憶體中儲存的資料
- 記憶體,資料, 變數三者之間的關係
- 記憶體用來儲存資料的空間
- 變數是記憶體的標識
問題: 在js呼叫函式時傳遞變數引數時, 是值傳遞還是引用傳遞
- 理解1: 都是值(基本/地址值)傳遞
- 理解2: 可能是值傳遞, 也可能是引用傳遞(地址值)
<script type="text/javascript">
var a = 3
function fn (a) {
a = a +1
}
fn(a)
console.log(a)
function fn2 (obj) {
console.log(obj.name)
}
var obj = {name: 'Tom'}
fn2(obj)
</script>
問題: JS引擎如何管理記憶體?
- 記憶體生命週期
- 分配小記憶體空間, 得到它的使用權
- 儲存資料, 可以反覆進行操作
- 釋放小記憶體空間
- 釋放記憶體
- 區域性變數: 函式執行完自動釋放
- 物件: 成為垃圾物件==>垃圾回收器回收
<script type="text/javascript">
var a = 3
var obj = {}
obj = undefined
function fn () {
var b = {}
}
fn() // b是自動釋放, b所指向的物件是在後面的某個時刻由垃圾回收器回收
</script>
關於物件
- 什麼是物件?
- 多個數據的封裝體
- 用來儲存多個數據的容器
- 一個物件代表現實中的一個事物
- 為什麼要用物件?
- 統一管理多個數據
- 物件的組成
- 屬性: 屬性名(字串)和屬性值(任意)組成
- 方法: 一種特別的屬性(屬性值是函式)
- 如何訪問物件內部資料?
- .屬性名: 編碼簡單, 有時不能用
- [‘屬性名’]: 編碼麻煩, 能通用
問題: 什麼時候必須使用[‘屬性名’]的方式?
- 屬性名包含特殊字元: - 空格
- 屬性名不確定
<script type="text/javascript">
var p = {}
//1. 給p物件新增一個屬性: content type: text/json
// p.content-type = 'text/json' //不能用
p['content-type'] = 'text/json'
console.log(p['content-type'])
//2. 屬性名不確定
var propName = 'myAge'
var value = 18
// p.propName = value //不能用
p[propName] = value
console.log(p[propName])
</script>
函式
- 什麼是函式?
- 實現特定功能的n條語句的封裝體
- 只有函式是可以執行的, 其它型別的資料不能執行
- 為什麼要用函式?
- 提高程式碼複用
- 便於閱讀交流
- 如何定義函式?
- 函式宣告
- 表示式
- 如何呼叫(執行)函式?
- test(): 直接呼叫
- obj.test(): 通過物件呼叫
- new test(): new呼叫
- test.call/apply(obj): 臨時讓test成為obj的方法進行呼叫
回撥函式
- 什麼函式才是回撥函式?
1). 你定義的
2). 你沒有調
3). 但最終它執行了(在某個時刻或某個條件下) - 常見的回撥函式?
- dom事件回撥函式 ==>發生事件的dom元素
- 定時器回撥函式 ===>window
- ajax請求回撥函式
- 生命週期回撥函式
<script type="text/javascript">
document.getElementById('btn').onclick = function () { // dom事件回撥函式
alert(this.innerHTML)
}
//定時器
// 超時定時器
// 迴圈定時器
setTimeout(function () { // 定時器回撥函式
alert('到點了'+this)
}, 2000)
</script>
IIFE
- 理解
- 全稱: Immediately-Invoked Function Expression
- 中文名:立即執行函式表示式
- 作用
- 隱藏實現
- 不會汙染外部(全域性)名稱空間
- 用它來編碼js模組
<script type="text/javascript">
(function () { //匿名函式自呼叫
var a = 3
console.log(a + 3)
})()
var a = 4
console.log(a)
;(function () {
var a = 1
function test () {
console.log(++a)
}
window.$ = function () { // 向外暴露一個全域性函式
return {
test: test
}
}
})()
$().test() // 1. $是一個函式 2. $執行後返回的是一個物件
</script>
this
- this是什麼?
- 任何函式本質上都是通過某個物件來呼叫的, 如果沒有直接指定就是window.
- 所有函式內部都有一個變數this
- 它的值是呼叫函式的當前物件
- 如何確定this的值?
- test(): window
- p.test(): p
- new test(): 新建立的物件
- p.call(obj): obj
舉例說明吧:
<script type="text/javascript">
function Person(color) {
console.log(this)
this.color = color;
this.getColor = function () {
console.log(this)
return this.color;
};
this.setColor = function (color) {
console.log(this)
this.color = color;
};
}
Person("red"); //this是誰? window
var p = new Person("yello"); //this是誰? p
p.getColor(); //this是誰? p
var obj = {};
p.setColor.call(obj, "black"); //this是誰? obj
var test = p.setColor;
test(); //this是誰? window
function fun1() {
function fun2() {
console.log(this);
}
fun2(); //this是誰? window
}
fun1();
</script>
函式高階
原型
- 函式的prototype屬性(圖)
- 每個函式都有一個prototype屬性, 它預設指向一個Object空物件(即稱為: 原型物件)
- 原型物件中有一個屬性constructor, 它指向函式物件
- 給原型物件新增屬性(一般都是方法)
- 作用: 函式的所有例項物件自動擁有原型中的屬性(方法)
<script type="text/javascript">
// 每個函式都有一個prototype屬性, 它預設指向一個Object空物件(即稱為: 原型物件)
console.log(Date.prototype, typeof Date.prototype)
function Fun () {//alt + shift +r(重新命名rename)
}
console.log(Fun.prototype) // 預設指向一個Object空物件(沒有我們的屬性)
// 原型物件中有一個屬性constructor, 它指向函式物件
console.log(Date.prototype.constructor===Date)
console.log(Fun.prototype.constructor===Fun)
//給原型物件新增屬性(一般是方法) ===>例項物件可以訪問
Fun.prototype.test = function () {
console.log('test()')
}
var fun = new Fun()
fun.test()
</script>
顯式原型 和 隱式原型
- 每個函式function都有一個prototype,即顯式原型(屬性)
- 每個例項物件都有一個__proto__,可稱為隱式原型(屬性)
- 物件的隱式原型的值為其對應建構函式的顯式原型的值
- 記憶體結構(圖)
- 總結:
- 函式的prototype屬性: 在定義函式時自動新增的, 預設值是一個空Object物件
- 物件的__proto__屬性: 建立物件時自動新增的, 預設值為建構函式的prototype屬性值
- 程式設計師能直接操作顯式原型, 但不能直接操作隱式原型(ES6之前)
<script type="text/javascript">
// 每個函式都有一個prototype屬性, 它預設指向一個Object空物件(即稱為: 原型物件)
console.log(Date.prototype, typeof Date.prototype)
function Fun () {//alt + shift +r(重新命名rename)
}
console.log(Fun.prototype) // 預設指向一個Object空物件(沒有我們的屬性)
// 原型物件中有一個屬性constructor, 它指向函式物件
console.log(Date.prototype.constructor === Date)
console.log(Fun.prototype.constructor === Fun)
//給原型物件新增屬性(一般是方法) ===>例項物件可以訪問
Fun.prototype.test = function () {
console.log('test()')
}
var fun = new Fun()
fun.test()
</script>
原型鏈
1. 原型鏈(圖解)
- 訪問一個物件的屬性時,
- 先在自身屬性中查詢,找到返回
- 如果沒有, 再沿著__proto__這條鏈向上查詢, 找到返回
- 如果最終沒找到, 返回undefined
- 別名: 隱式原型鏈
- 作用: 查詢物件的屬性(方法)
2. 建構函式/原型/實體物件的關係(圖解)
3. 建構函式/原型/實體物件的關係2(圖解)
<script type="text/javascript">
// console.log(Object)
//console.log(Object.prototype)
console.log(Object.prototype.__proto__)
function Fn() {
this.test1 = function () {
console.log('test1()')
}
}
console.log(Fn.prototype)
Fn.prototype.test2 = function () {
console.log('test2()')
}
var fn = new Fn()
fn.test1()
fn.test2()
console.log(fn.toString())
console.log(fn.test3)
// fn.test3()
/*
1. 函式的顯示原型指向的物件預設是空Object例項物件(但Object不滿足)
*/
console.log(Fn.prototype instanceof Object) // true
console.log(Object.prototype instanceof Object) // false
console.log(Function.prototype instanceof Object) // true
/*
2. 所有函式都是Function的例項(包含Function)
*/
console.log(Function.__proto__===Function.prototype)
/*
3. Object的原型物件是原型鏈盡頭
*/
console.log(Object.prototype.__proto__) // null
</script>
原型鏈_屬性問題
-
讀取物件的屬性值時: 會自動到原型鏈中查詢
-
設定物件的屬性值時: 不會查詢原型鏈, 如果當前物件中沒有此屬性, 直接新增此屬性並設定其值
-
方法一般定義在原型中, 屬性一般通過建構函式定義在物件本身上
<script type="text/javascript">
function Fn() {
}
Fn.prototype.a = 'xxx'
var fn1 = new Fn()
console.log(fn1.a, fn1)
var fn2 = new Fn()
fn2.a = 'yyy'
console.log(fn1.a, fn2.a, fn2)
function Person(name, age) {
this.name = name
this.age = age
}
Person.prototype.setName = function (name) {
this.name = name
}
var p1 = new Person('Tom', 12)
p1.setName('Bob')
console.log(p1)
var p2 = new Person('Jack', 12)
p2.setName('Cat')
console.log(p2)
console.log(p1.__proto__===p2.__proto__) // true
</script>
變數提升和函式提升
1. 變數宣告提升
- 通過var定義(宣告)的變數, 在定義語句之前就可以訪問到
- 值: undefined
2. 函式宣告提升
- 通過function宣告的函式, 在之前就可以直接呼叫
- 值: 函式定義(物件)
3. 問題: 變數提升和函式提升是如何產生的?
自己想吧,哈哈哈哈!
<script type="text/javascript">
console.log('-----')
/*
面試題 : 輸出 undefined
*/
var a = 3
function fn () {
console.log(a)
var a = 4
}
fn()
console.log(b) //undefined 變數提升
fn2() //可呼叫 函式提升
// fn3() //不能 變數提升
var b = 3
function fn2() {
console.log('fn2()')
}
var fn3 = function () {
console.log('fn3()')
}
</script>
函式執行上下文
1. 程式碼分類(位置)
- 全域性程式碼
- 函式(區域性)程式碼
2. 全域性執行上下文
- 在執行全域性程式碼前將window確定為全域性執行上下文
- 對全域性資料進行預處理
- var定義的全域性變數==>undefined, 新增為window的屬性
- function宣告的全域性函式==>賦值(fun), 新增為window的方法
- this==>賦值(window)
- 開始執行全域性程式碼
3. 函式執行上下文
- 在呼叫函式, 準備執行函式體之前, 建立對應的函式執行上下文物件(虛擬的, 存在於棧中)
- 對區域性資料進行預處理
- 形參變數==>賦值(實參)==>新增為執行上下文的屬性
- arguments==>賦值(實參列表), 新增為執行上下文的屬性
- var定義的區域性變數==>undefined, 新增為執行上下文的屬性
- function宣告的函式 ==>賦值(fun), 新增為執行上下文的方法
- this==>賦值(呼叫函式的物件)
- 開始執行函式體程式碼
<script type="text/javascript">
function fn(a1){
console.log(a1); //2
console.log(a2); //undefine
a3(); //a3
console.log(this); //window
console.log(arguments); // [2,3,...........]為陣列
var a2 = 3;
function a3(){
console.log("a3");
}
}
fn(2,3);
console.log(a1, window.a1)
window.a2()
console.log(this)
var a1 = 3
function a2() {
console.log('a2()')
}
console.log(a1)
</script>
執行上下文棧
- 在全域性程式碼執行前, JS引擎就會建立一個棧來儲存管理所有的執行上下文物件
- 在全域性執行上下文(window)確定後, 將其新增到棧中(壓棧)
- 在函式執行上下文建立後, 將其新增到棧中(壓棧)
- 在當前函式執行完後,將棧頂的物件移除(出棧)
- 當所有的程式碼執行完後, 棧中只剩下window
舉個例子:
<script type="text/javascript">
console.log('gb: '+ i)
var i = 1
foo(1)
function foo(i) {
if (i == 4) {
return
}
console.log('fb:' + i)
foo(i + 1) //遞迴呼叫: 在函式內部呼叫自己
console.log('fe:' + i)
}
console.log('ge: ' + i)
</script>
1. 依次輸出什麼?
gb: undefined
fb: 1
fb: 2
fb: 3
fe: 3
fe: 2
fe: 1
ge: 1
2. 整個過程中產生了幾個執行上下文?
5
執行上下文棧中遇到的坑
<script type="text/javascript">
/*
測試題1: 先執行變數提升, 再執行函式提升
*/
function a() {}
var a
console.log(typeof a) // 'function'
/*
測試題2:
*/
if (!(b in window)) {
var b = 1
}
console.log(b) // undefined
/*
測試題3:
*/
var c = 1
function c(c) {
console.log(c)
}
c(2) // 報錯
</script>
作用域
1. 理解
- 就是一塊"地盤", 一個程式碼段所在的區域
- 它是靜態的(相對於上下文物件), 在編寫程式碼時就確定了
2. 分類
- 全域性作用域
- 函式作用域
- 塊作用域(ES6有了)
4. 作用
- 隔離變數,不同作用域下同名變數不會有衝突
<script type="text/javascript">
/* //沒塊作用域
if(true) {
var c = 3
}
console.log(c)*/
var a = 10,
b = 20
function fn(x) {
var a = 100,
c = 300;
console.log('fn()', a, b, c, x)
function bar(x) {
var a = 1000,
d = 400
console.log('bar()', a, b, c, d, x)
}
bar(100)
bar(200)
}
fn(10)
</script>
作用域與執行上下文
- 區別1
- 全域性作用域之外,每個函式都會建立自己的作用域,作用域在函式定義時就已經確定了。而不是在函式呼叫時
- 全域性執行上下文環境是在全域性作用域確定之後, js程式碼馬上執行之前建立
- 函式執行上下文是在呼叫函式時, 函式體程式碼執行之前建立
- 區別2
- 作用域是靜態的, 只要函式定義好了就一直存在, 且不會再變化
- 執行上下文是動態的, 呼叫函式時建立, 函式呼叫結束時就會自動釋放
- 聯絡
- 執行上下文(物件)是從屬於所在的作用域
- 全域性上下文環境==>全域性作用域
- 函式上下文環境==>對應的函式使用域
<script type="text/javascript">
var a = 10,
b = 20
function fn(x) {
var a = 100,
c = 300;
console.log('fn()', a, b, c, x)
function bar(x) {
var a = 1000,
d = 400
console.log('bar()', a, b, c, d, x)
}
bar(100)
bar(200)
}
fn(10)
</script>
作用域鏈
- 理解
- 多個上下級關係的作用域形成的鏈, 它的方向是從下向上的(從內到外)
- 查詢變數時就是沿著作用域鏈來查詢的
- 查詢一個變數的查詢規則
- 在當前作用域下的執行上下文中查詢對應的屬性, 如果有直接返回, 否則進入2
- 在上一級作用域的執行上下文中查詢對應的屬性, 如果有直接返回, 否則進入3
- 再次執行2的相同操作, 直到全域性作用域, 如果還找不到就丟擲找不到的異常
<script type="text/javascript">
var a = 1
function fn1() {
var b = 2
function fn2() {
var c = 3
console.log(c)
console.log(b)
console.log(a)
console.log(d)
}
fn2()
}
fn1()
</script>