目錄
- JS進階
- 1、資料類型
-
- 1.1、分類
- 1.2、判斷
- 1.3、類型對象和執行個體對象的差別
- 1.4、`undefined` 和 `null` 的差別
- 2、記憶體
-
- 2.1、資料
- 2.2、什麼是記憶體
- 2.3、練習
- 3、函數
-
- 3.1、函數調用
- 3.2、回調函數
- 3.3、IIFE
- 4、this
- 5、分号問題
- 6、原型 `prototype`
-
- 6.1、什麼是原型對象
- 6.2、顯式原型屬性和隐式原型屬性
- 6.3、原型鍊
- 6.4、構造函數/原型/實體對象的關系
- 6.5、結論
- 6.6、原型鍊屬性問題
- 6.7.instanceof
- 6.8、面試題
- 7、變量提升與函數提升
- 8、執行上文
-
- 8.1、代碼分類
- 8.2、 全局執行上下文
- 8.3、函數執行上下文
- 8.4、執行上下文棧
- 8.5、面試題
- 9、作用域和作用域鍊
-
- 9.1、分類
- 9.2、作用
- 9.3、作用域與執行上下文的差別
- 9.4、面試題
- 10、循環周遊加監聽
- 11、閉包
-
- 11.1、如何産生閉包
- 11.2、常見閉包
- 11.3、作用
- 11.4、生命周期
- 11.4、應用
- 11.5、閉包的缺點
- 11.6、面試題
- 12、對象建立模式
- 13、原型鍊繼承
JS進階
1、資料類型
1.1、分類
- 基本類型
-
String
-
Number
-
boolean
-
undefined
-
null
-
- 對象類型
-
Object
-
Function
-
Array
-
1.2、判斷
-
typeof
-
->null
object
-
->Array
object
- 其餘都可以判斷
-
-
判斷對象的具體類型instanceof
-
===
-
==
var a;
console.log(a, typeof(a)); //undefined, 'undefined'
a = 'aitguigu';
console.log(typeof(a));// 'string'
a = null
console.log(typeof(a));// 'objcet'
1.3、類型對象和執行個體對象的差別
function Person(name, age) { // 構造函數 類型對象
this.name = name;
this.age = age;
}
var p = new Person(); // 根據類型創見的執行個體對象
1.4、 undefined
和 null
的差別
undefined
null
-
定義未指派undefined
-
指派并定義且值為null
null
2、記憶體
2.1、資料
- 特點:可傳遞、可運算
2.2、什麼是記憶體
- 記憶體中存儲兩種資料:位址值、内部存儲的資料
- 記憶體分類
- 堆:對象
- 棧:全局變量、局部變量、函數名
2.3、練習
var obj1 = {name: 'Tom'};
var obj2 = obj1;
obj2.age = 12;
console.log(obj1.age);// 12
function fn(obj) {
obj.name = 'A';
}
fn(obj1);
console.log(obj2.name); // 'A'
var a = {age: 12};
var b = a;
a = {name: 'BOB', age: 13};
b.age = 14;
console.log(b.age, a.name, a.age);// 14, 'BOB', 13;
function fn2(obj) {
obj = {age: 15};
}
fn2(a);
console.log(a.age); // 13
3、函數
3.1、函數調用
- 直接調用
- 通過對象調用
- new 調用
- call() 和 apply() 可以讓任意函數成為指定對象的方法
var obj = {};
function test() {
this.name = 'atguigu';
}
test.call(obj);
console.log(obj.name);// atguigu
3.2、回調函數
- 定義且沒有調用并最終執行的函數
-
事件回調函數DOM
- 定時器回調函數
-
請求回調函數AJAX
- 生命周期回調函數
3.3、IIFE
- 隐藏實作
- 不會污染外部命名空間
- 可以編寫JS子產品
(function() {// 匿名函數自調用,向外暴露一個全局函數
var a = 1;
function test() {
console.log(a);
}
window.$ = function() {
return {
test: test
}
}
})();
$().test();
4、this
- 任何函數本質上都是通過某個對象來調用
- 所有函數内部都有一個變量
,值是調用函數的對象,如果沒有指定,就是this
window
-
test()
window
-
p.test()
p
-
新建立的對象new test()
-
p.call(obj)
obj
function Person() {
getThis = function() {
console.log(this);
}
}
var p = new Person();
p.getThis(); // this 是p
var b = p.getThis;
b(); // this 是 window
5、分号問題
-
前要加分号()
-
前要加分号[]
- JS檔案頭部沒有加分号,合并會導緻問題
6、原型 prototype
prototype
6.1、什麼是原型對象
- 每個函數都有一個
屬性,它預設指向一個prototype
空對象,即原型對象Object
-
中有一個屬性prototype
,指向函數對象constructor
- 空對象是指的沒有自定義屬性
-
console.log(Date.prototype, typeof Date.prototype)// Object
function fun(){}
fun.prototype.test = function(){}
console.log(fun.prototype) // Object
console.log(Date.prototype.constructor == Date) // true
6.2、顯式原型屬性和隐式原型屬性
- 每個函數都有一個顯式原型屬性
prototype
- 每個執行個體對象都有一個隐式原型屬性
proto
- 對象的隐式原型的值為其對應構造函數的顯式原型的值
function Fn() { // 内部語句 Fn.prototype = {}
}
var fn = new Fn();// 内部語句 fn.__proto___ = Fn.prototype
console.log(fn.__proto__===Fn.prototype); // true
6.3、原型鍊
- 别名:隐式原型鍊
- 通路一個屬性時
- 先從自身屬性中查找,找到并傳回
- 如果沒有再沿着
這條鍊向上查找,找到并傳回__proto__
- 如果最終沒找到,傳回
undefined
6.4、構造函數/原型/實體對象的關系
-
是一個函數對象,是以擁有Foo
屬性,預設指向prototype
函數對象的原型對象Foo
-
同時也是一個執行個體對象,Foo
是以擁有var Foo = new Function();
屬性,指向__proto__
函數對象的原型對象Function
-
是一個函數對象,是以擁有Function
屬性與prototype
的Foo
屬性都指向__proto__
函數對象的原型對象Function
-
同時也是一個執行個體對象,Function
是以擁有var Function = new Function();
屬性,指向__proto__
函數的原型對象Function
-
是一個函數對象,是以擁有Object
屬性,預設指向prototype
函數對象的原型對象Object
-
同時也是一個執行個體對象,Object
是以擁有var Object = new Function();
屬性,指向__proto__
函數對象的原型對象Function
-
的原型對象是Function
的原型對象的執行個體Object
6.5、結論
- 結論1:函數的顯式原型指向的對象預設是空
執行個體對象(Object
除外)Object
- 結論2:所有函數都是
的執行個體Function
- 結論3:
的原型對象是原型鍊的盡頭Object
// 驗證
console.log(Object.prototype instanceof Object) //false
console.log(Function.prototype instanceof Object) // true
console.log(Function.prototype === Function.__proto__) //true
console.log(Object.prototype.__proto__) // null
6.6、原型鍊屬性問題
- 讀取對象屬性時會自動在原型鍊中查找
- 設定對象屬性值時,不會自動查找原型鍊,如果目前對象中沒有此屬性,直接添加
- 方法一般定義在原型中,屬性一般通過構造函數定義在對象本身上
// 讀取對象屬性時會自動在原型鍊中查找
function Fn() {}
Fn.prototype.a = 'xxx'
var fn = new Fn();
console.log(fn.a) // xxx
// 設定對象屬性值時,不會自動查找原型鍊,如果目前對象中沒有此屬性,直接添加
var fn2 = new Fn()
fn2.a = 'yyy'
console.log(fn2.a, fn.a) // yyy, xxx
// 方法一般定義在原型中,屬性一般通過構造函數定義在對象本身
function Person(name, age) {
this.name = name;
this.age = age;
}
Person.prototype.setAge = function (age) {
this.age = age; // this這個值仍然是指向它原本屬于的對象,而不是從原型鍊上找到它時,它所屬于的對象。
}
var person = new Person('xs', 21);
person.setAge(22);
console.log(person);
var person2 = new Person('ws', 31);
person2.setAge(33);
console.log(person2);
console.log(person2.__proto__ === person.__proto__);// true
6.7.instanceof
- 表達式
A instanceof B
-
執行個體對象A
-
構造函數B
-
- 如果
函數的顯式原型對象在B
對象的原型鍊上,傳回A
,否則傳回true
false
function Foo(){}
var f1 = new Foo()
console.log(f1 instanceof Foo)
console.log(f1 instanceof Object)
console.log(Object instanceof Function) // true
console.log(Object instanceof Object); // true
console.log(Function instanceof Function) // true
console.log(Function instanceof Object) // true
console.log(Object instanceof Foo) // false
6.8、面試題
// 1
function A () {}
A.prototype.n = 1
var b = new A()
A.prototype = {
n: 2,
m: 3
}
var c = new A()
console.log(b.n, b.m, c.n, c.m) // 1, undefined, 2, 3
// 2
var F = function F() {}
Object.prototype.a = function() {
console.log('a')
}
Function.prototype.b = function() {
console.log('b')
}
var f = new F()
f.a() // a
f.b() // 報錯
F.a() // a
F.b() // b
7、變量提升與函數提升
- 通過
定義(聲明)的變量,在定義語句之前就可以通路的到,值為var
undefined
- 通過
聲明的函數,在之前就可以調用,值為函數定義(對象)function
var a = 3
function fn() {
console.log(a)
var a = 4;
}
fn() // undefined
8、執行上文
8.1、代碼分類
- 全局代碼
- 函數代碼(局部代碼)
8.2、 全局執行上下文
- 在執行全局代碼前,将
确定為全局執行上下文window
- 對全局資料進行預處理
-
定義的全局變量指派為var
,添加為undefined
屬性window
-
聲明的全局函數指派并添加為function
方法window
-
指派為this
window
-
- 開始執行全局代碼
8.3、函數執行上下文
- 在調用函數時,準備執行函數體之前,建立對應的函數執行上下文對象
- 對局部資料進行預處理
- 形參變量進行實參指派添加為執行上下文的屬性
-
實參清單指派添加為執行上下文的屬性arguments
-
定義的局部變量指派為var
添加為上下文屬性undefined
-
聲明的函數指派并添加為上下文屬性function
-
指派為調用函數對象this
- 開始執行函數體代碼
8.4、執行上下文棧
- 在全局代碼執行前,JS引擎會建立一個棧來存儲管理所有的執行上下文對象
- 在全局執行上下文
确定後,将其添加到棧中(壓棧)window
- 在函數執行上下文建立後,将其添加到棧中(壓棧)
- 在目前函數執行完成後,将棧頂的對象移除(出棧)
- 當所有代碼執行完後,棧中隻剩下
window
var a = 10
var bar = function (x) {
var b = 5
foo(x + b)
}
var foo = function (y) {
var c = 5
console.log(a + c + y)
}
bar(10)
8.5、面試題
// 輸出結果是什麼 一共幾個執行上下文對象 5個
console.log("global begin: " + i) // undefined
var i = 1
foo(1)
function foo(i) {
if (i == 4) return
console.log("foo begin: " + i) // 1 2 3
foo(i + 1)
console.log("foo end: " + i); // 3 2 1
}
console.log("global end: " + i); // 1
// 2
// 先執行變量提升,再執行函數提升
function a() {}
var a;
console.log(typeof a); // function
// 3
if (!(b in window)) {
var b = 1;
}
console.log(b) // undefined
// 4
var c = 1
function c(c) {
console.log(c)
}
c(2) // c is not a function
9、作用域和作用域鍊
- 靜态的(相對于上下文對象),編寫代碼時候已經确定
9.1、分類
- 全局作用域
- 函數作用域
- 沒有塊作用域(ES6有了)
9.2、作用
- 隔離變量,不同作用域下同名變量不會有沖突
9.3、作用域與執行上下文的差別
- 建立
- 全局作用域之外,每個函數都會建立自己的作用域,作用域在函數定義時就已經确定,而不是在函數調用時
- 全局上下文是在全局作用域确定後,JS代碼馬上執行前建立
- 函數上下文是在調用函數時,函數體代碼執行前建立
- 性質
- 作用域是靜态的,隻要函數定義好就不會變化
- 執行上下文是動态的,調用函數時建立,函數結束後銷毀
- 聯系
- 執行上下文(對象)從屬于所在作用域
- 全局上下文對應全局作用域,函數上下文對應函數作用域
9.4、面試題
// 1
var x = 10;
function fn() {
console.log(x);
}
function show(f) {
var x = 20;
f()
}
show(fn) // 10
// 2
var fn = function () {
console.log(fn)
}
fn() //函數
//3
var obj = {
fn2 : function () {
console.log(fn2)
}
}
obj.fn2() // Uncaught ReferenceError: fn2 is not defined
10、循環周遊加監聽
var btns = document.getElementsByTagName('button');
// 寫法1 錯誤
for(var i = 0, length = btns.length; i < length; i++) {
var btn = btns[i]
btn.onclick = function() {
alert(i + 1)
}
}
// i 是全局作用域
console.log(i)
// 寫法2
for(var i = 0, length = btns.length; i < length; i++) {
var btn = btns[i]
btn.index = i
btn.onclick = function() {
alert(this.index + 1)
}
}
console.log(i)
// 寫法3 利用了閉包
for(var i = 0, length = btns.length; i < length; i++) {
(function(i) {
var btn = btns[i]
btn.onclick = function() {
alert(i + 1)
}
})(i)
}
console.log(i)
<button>按鈕1</button>
<button>按鈕2</button>
<button>按鈕3</button>
11、閉包
11.1、如何産生閉包
- 當一個嵌套的内部(子)函數引用了嵌套的外部(父)函數的變量(函數)時,就産生了閉包
- 閉包是嵌套的内部函數
- 包含被引用變量(函數)的對象
- 注意:閉包存在于嵌套的内部函數
- 産生閉包的條件
- 函數嵌套
- 内部函數引用了外部函數的資料(變量/函數)
- 注意:
- 建立閉包是再調用外部函數時建立
- 參數和變量不會被垃圾回收機制回收
- 垃圾回收機制,函數沒有被引用執行完以後這個函數的作用域就會被銷毀,如果一個函數被其他變量引用,這個函數的作用域将不會被銷毀(簡單來說就是函數裡面的變量會被儲存下來,可以了解成全局變量)
function fn1() {
var a = 2
function fn2() {
console.log(a);
}
}
fn1()
11.2、常見閉包
- 将函數作為另外一個函數的傳回值
- 将函數作為實參傳遞給另一個函數
function fn1() {
var a = 2
function fn2() {
a++;
console.log(a);
}
return fn2
}
var f = fn1();
f() // 3
f() // 4
function showDelay(msg, time) {
setTimeout(function () {
alert(msg)
}, time)
}
showDelay(1, 1000)
function fn1() {
var a = 2
function fn2() {
a++;
console.log(a);
}
function fn3() {
a--;
console.log(a);
}
return fn3
}
var f = fn1();
f() // 1
f() // 0
11.3、作用
- 延長了局部變量的生命周期,使用函數内部的變量在函數執行完後,仍存活在記憶體中
- 讓函數外部可以操作(讀寫)到函數内部的資料(變量/函數)
11.4、生命周期
- 産生:在嵌套内部函數定義執行時就産生了,不是在調用時
- 銷毀:在嵌套的内部函數成為垃圾對象時
function fn1() {
var a = 2
function fn2() {
a++;
console.log(a);
}
return fn2
}
var f = fn1();
f = null
11.4、應用
- 定義JS子產品
- 具有特定功能的JS檔案
- 将所有的資料和功能都封裝在一個函數中
- 隻向外暴露一個包含n個方法的對象或函數
- 子產品的使用者,隻需要通過子產品暴露的對象調用方法來實作對應功能
function myModel() {
var msg = 'aaa'
function doSomething() {
console.log('doSomething()' + msg.toUpperCase())
}
function doOtherthing() {
console.log('doOtherthing()')
}
return {
doSomething : doSomething,
doOtherthing : doOtherthing
}
}
<script type="text/javascript" src="modelDemo.js"></script>
<script>
var fn = myModel();
console.log(fn.doSomething()); // doSomething() AAA
console.log(fn.doOtherthing()); // undefined
</script>
- 方法2
(function (w) {
var msg = 'aaa'
function doSomething() {
console.log('doSomething()' + msg.toUpperCase())
}
function doOtherthing() {
console.log('doOtherthing()')
}
w.myModel = {
doSomething : doSomething,
doOtherthing : doOtherthing
}
})(window)
<script type="text/javascript" src="modelDemo2.js"></script>
<script>
var fn = window.myModel;
console.log(fn.doSomething());
console.log(fn.doOtherthing());
</script>
11.5、閉包的缺點
- 函數執行完後,函數的局部變量沒有釋放,占用記憶體時間會邊長
- 容易造成記憶體洩露
- 意外的全局變量
// 1
function fn() {
a = 3
console.log(a)
}
fn()
// 2
var a = setInterval(function() {
console.log('----')
}, 1000)
// clearInterval(a)
// 閉包
- 解決:
- 盡量避免閉包
- 及時釋放
11.6、面試題
// 面試題 1
var name = 'The Window'
var object = {
name: 'myObject',
getNameFunc: function () {
return function () {
return this.name
}
}
}
alert(object.getNameFunc()()); // The Window
// 面試題 2
var name2 = 'The Window'
var object2 = {
name2: 'myObject',
getNameFunc: function() {
var that = this;
return function() {
return that.name2; // 閉包
}
}
}
alert(object2.getNameFunc()())// myObject
// 面試題 3
function fun(n, o) {
console.log(o);
return {
fun: function (m) {
return fun(m,n)
}
}
}
var a = fun(0);// undefined
a.fun(1); // 0
a.fun(2); // 0
a.fun(3); // 0
var b = fun(0).fun(1).fun(2).fun(3);//undefined,1,2,3
var c = fun(0).fun(1);// undefined, 1
c.fun(2); // 1
c.fun(3); // 1
12、對象建立模式
- 構造函數式建立
- 場景:初始時不确定對象内部資料
- 缺點:語句太多
- 字面量式建立
- 場景:初始時對象内部資料确定
- 缺點:如果建立多個對象,有重複代碼
- 工廠模式建立
- 場景:需要建立多個對象
- 缺點:對象沒有一個具體的類型,都是Object類型
- 自定義構造函數模式
- 場景:需要建立多個類型确定的對象
- 缺點:資料相同,浪費記憶體
- 構造函數 + 原型 的組合模式
- 場景:屬性在函數中初始化,方法添加到原型上
- 缺點:需要建立多個類型确定的對象
// 1
var obj = new Object();
obj.name = 'Joke';
obj.age = '18';
// 2
var obj2 = {name: 'Tom', age: 12}
function createPerson(name, age) {
var obj = {
name: name,
age: age,
}
return obj;
}
// 3
var obj3 = createPerson('HHH', 22);
var obj4 = createPerson('GGG', 23);
// 4
function Person(name, age) {
this.name = name;
this.age = age;
}
// 5
Person.prototype.setName = function (name) {
this.name = name;
}
var person = new Person('AAA', 45);
console.log(obj);
console.log(obj2);
console.log(obj3);
console.log(obj4);
person.setName('張三');
console.log(person)
13、原型鍊繼承
- 子類型的原型為父類型的執行個體
- 借用構造函數
- 在子類型構造函數中通用
調用父類型構造函數call()
- 在子類型構造函數中通用
- 原型鍊 + 借用構造函數的組合繼承
- 利用原型鍊實作對父類型對象的方法繼承
- 利用
借用父類型建構函數初始化相同屬性super()
// 方式一
// 父類型
function Supper() {
this.supProp = 'supper'
}
Supper.prototype.showSupperProp = function () {
console.log("s >>> " + this.supProp);
}
// 子類型
function Sub() {
this.subProp = 'sub'
}
// Sub.prototype = new Supper()
Sub.prototype.showSubProp = function () {
console.log(this.subProp);
}
var sub = new Sub();
sub.showSupperProp() // Uncaught TypeError: sub.showSupperProp is not a function
// 方式二
function Person(name, age) {
this.name = name;
this.age = age;
}
function Student(name, age, price) {
Person.call(this, name, age)
this.price = price;
}
var student = new Student('zs', 23, 299); // Student {name: 'zs', age: 23, price: 299}
// 方法三
function Person(name, age) {
this.name = name;
this.age = age;
}
Person.prototype.setName = function (name) {
this.name = name
}
function Student(name, age, price) {
Person.call(this, name, age)
this.price = price;
}
Student.prototype = new Person();
Student.constructor = Student;
Student.prototype.setPrice = function (price) {
this.price = price;
}
var student = new Student('zs', 29, 30);
student.setName('sss');
console.log(student); // Student {name: 'sss', age: 29, price: 30}