1. 程式人生 > 實用技巧 >javascript資料型別、建立物件及面向物件

javascript資料型別、建立物件及面向物件

1.資料型別

javascript包含兩種不同的資料型別的值:基本型別和引用資料型別。

1.1 基本型別

String、Number、Boolean、null、undefined、Symbol(es6)

1.2 引用型別

Object、data、Array、Set(es6)、Map(es6)等

1.3 基本型別和引用型別的區別

javascript的變數儲存方式 棧記憶體(stack)和堆記憶體(heap)

棧:自動分配記憶體,系統自動釋放。裡面存放的是基本型別的值和引用型別的地址。

堆:動態分配記憶體,大小不定,也不會自動釋放,裡面存的是引用型別的值。

基本資料型別是按值訪問的,因為可以操作儲存在變數中的實際的值。
引用型別的值是儲存在記憶體中的物件。javascript不允許直接訪問記憶體中的位置,也就是說不能直接
操作物件的記憶體空間。在操作物件時:實際上是在操作物件的引用而不是實際的物件。

傳值與傳址

基本型別和引用型別最大的區別實際是 傳值與傳址的區別

值傳遞:基本型別採用的是值傳遞。

地址傳遞:引用型別則是地址傳遞,將存放在棧記憶體中的地址賦值給接受的變數。

在我們進行賦值操作的時候,基本資料型別的賦值(=)是在記憶體中開闢一段棧記憶體,然後再將值賦值到新的棧中

	var a = 10;
	var b = a;
	a++;
	console.log(a); //11
	console.log(b); //10
	//基本型別的賦值 兩個變數是兩個獨立互不影響的變數

但是引用型別的賦值是傳址。只是改變了指標的指向。
也就是說引用型別的賦值是物件儲存在棧中的地址的賦值,這樣的話兩個變數就指向同一個物件,因此兩者之間操作互相有影響。

	var a = {};
	var b = a;
	a.name = "jane";
	console.log(a.name,b.name) //jane,jane
	b.age =18;
	console.log(a.age,b.age)//18,18
	console.log(a ===b);//true

佔記憶體中的變數一般都是已經大小或者有範圍上限的,算作一種簡單儲存。
而堆記憶體儲存的物件型別資料對於大小這方面,一般都是未知的。
個人認為,這也就是為什麼null作為一個object型別的變數卻儲存在棧記憶體中的原因了。

因此當我沒定義了一個const物件的時候,我們說的常量其實是指標,就是說const物件對應的堆記憶體指向是不變的,但是堆記憶體中的資料本身的大小或者屬性是可變的。
對於const定義的基礎變數而言,這個值就相當於const物件的指標,是不可變的。

說到這裡,有一個十分容易忽略的點,使用new關鍵字初始化之後是不存在棧記憶體中的。根據建構函式生成新例項,這個時間生成的是物件,而不是基本型別

	var a = new String("123");
	var b = String("123");
	var c = "123";
	console.log(a==b,a==c,b==c); //true,true,true
	console.log(a===b,a===c,b===c); //false,false,true
	console.log(typeof a ) //"object"

可以看出new一個String,出來的是物件,而直接字面量賦值和工廠模式出來的都是字串。但是根據我們上面的分析大小相對固定可預期的幾遍是物件也可以儲存在棧記憶體的。比如null,為啥這個不是呢

	var a = new String("123");
	var b = new String("123");
	console.log(a==b,a===b); //false false

很明顯,如果a,b是儲存在棧記憶體的話,兩者明顯應該是相等的,就像null === null是true一樣,
但結果兩者並不相等,說明兩者都是儲存在堆記憶體中的,指標指向不一致。

我們常說的值型別和引用型別其實說的就是棧記憶體和堆記憶體變數,再想想值傳遞和引用傳遞、深拷貝和淺拷貝,都是圍繞堆疊記憶體展開的,一個是處理值,一個是處理指標。

主要區別

1.值是否可變

基本型別的值是不可變的,除非重新給他賦值,引用型別的值是可變的

	var name = 'jozo';
	name.toUpperCase(); //'JOZO'
	name[0] = "mm";
	console.log(name); //'jozo'
	
	name = "song";
	console.log(name); //"song"
	var person = {};
	person.name = "jozo";
	console.log(person); //{name:"jozo"};

2.比較,是否2個數據型別相等

基本型別的比較是 值的比較

	var a = 1;
	var b = true;
	console.log(a == b);// true

引用型別的比較是 引用的比較

	var person1 = {};
	var person2 = {};
	console.log(person1 == person2); //false

3.值存放的位置

基本型別的變數是存放在棧記憶體的。

引用型別的值是儲存在棧記憶體和堆記憶體中的物件。

複製的情況

複製時,基本型別是直接複製了一個新的變數,新舊兩個變數之間沒有關係
引用型別複製了新的變數,但這個變數是一個指標,新舊兩個指標指向同一個物件

	var a = 10;
	var b = a;
	a++;
	console.log(a); //11
	console.log(b); //10
	//基本型別的賦值 兩個變數是兩個獨立互不影響的變數
	var a = {};
	var b = a;
	a.name = "jane";
	console.log(a.name,b.name) //jane,jane
	b.age =18;
	console.log(a.age,b.age)//18,18
	console.log(a ===b);//true

記憶體分配和垃圾回收

一般來說棧記憶體線性有序儲存,容量小,系統分配效率高。而堆記憶體首先要在堆記憶體新分配儲存區域,之後又要把指向儲存到棧記憶體中,效率相對就要低一些了。
垃圾回收方面,棧記憶體變數基本上用完就回收了,而堆記憶體中的變數因為存在很多不確定的引用,只有當所有呼叫的變數全部銷燬之後才能回收。

1.4 宣告變數

宣告新變數的時,可以使用關鍵詞 new 來宣告起型別

	var a = new String;
	var b = new Number;
	var c = new Boolean;
	var d = new Object;
	var e = new Array;

如何判斷資料型別呢?

基本資料型別的檢測: 大多使用typeof

	console.log(typeof 2); //number
	console.log(typeof "2"); //string
	console.log(typeof null); //object
	console.log(typeof undefined); //undefined
	console.log(typeof function(){}); //function
	console.log(typeof true); //boolean

引用資料型別檢測 instanceof Object.prototype.toString.call();

instanceof並不能完全準確檢查資料型別
A instanceof B 只是判斷A的原型是不是B

	let arr = [];
	console.log(arr instanceof Array);//true
	console.log(arr instanceof Object); //true

深入瞭解instanceof

Object.prototype.toSting.call能更精準的區分資料型別

	console.log(Object.prototype.toString.call("1")); //"[object String]"
	console.log(Object.prototype.toString.call(1)); //"[object Number]"
	console.log(Object.prototype.toString.call(true)); //"[object Boolean]"
	console.log(Object.prototype.toString.call(undefined)); //"[object undefined]"
	console.log(Object.prototype.toString.call(null)); //"[object Null]"
	console.log(Object.prototype.toString.call({})); //"[object Object]"
	console.log(Object.prototype.toString.call([])); //"[object Array]"

物件的建立

2.1 Object 型別建構函式

	var obj = new Object();
	var arr = new Array();
	//新增方法
	obj.name = "小明";
	//新增屬性
	obj.sleep = function(){
		console.log(this.name+"在睡覺")
	}

2.2 字面量定義(巢狀字面量)

	var obj1 = {};
	obj1.name = "小紅";
	var obj2 = {
		name:"小蘭",
		say:function(){
			console.log(this.name+"說大家好");
		}
	}

2.3工廠定義方式

	function createObj(name,age){
		var obj = new Object();
		obj.name = name;
		obj.age = age;
		obj.say = function(){
			console.log(this.name+"說你好");
		}
		return obj;
	}
	var p1 = createObj("小明",20);
	var p2 = createObj("小紅",18);

2.4建構函式方式

建構函式方法 首字母大寫

	function Person(name,age){
		this.name = name;
		this.age = age;
		this.say = function(){
			console.log(this.name+"說你好");
		}
	}
	var p1 = new Person("小明",20);
	var p2 = new Person("小紅",18);

2.5 Object.create

	var p = {name:"小明"};
	var obj = Object.create(p)

3.過程式開發與面向物件開發

面向過程 --站在一個執行者的角度去做事情;

面向物件 --站在指揮者的角度,是一種開發思想;

洗衣服作為例子:

面向過程:

1. 找個盆子
2. 收集要洗的衣服
3. 放水放洗衣液
4. 開始洗,洗完了晒

面向物件:

1. 找個物件
2. 讓他去洗
3. 檢查洗好了沒,然後晾晒

面向物件的三大特徵:

封裝:

  • 隱藏物件的屬性和實現細節,僅對外提供公共的訪問方式,將變化隔離,便於使用,提高複用性和安全性。

繼承

  • 提高程式碼複用性;繼承是多型的前提。

多型

  • 多型行是指相同的操作或函式、過程可作用於多種型別的物件上並獲得不同的結果。不同的物件,收到同一訊息可以產生不同的結果,這種現象稱為多型性。

五大基本原則

單一職責原則

  • 類的功能要單一,不能包羅永珍。

開放封閉原則

  • 一個模組對於拓展是開放的,對於修改是封閉的,想要增加功能熱烈歡迎,想要修改,no!

介面分離模式

  • 設計時採用多個與特定客戶有關的介面比採用一個通用的介面要好。

最後

  1. 抽象會使複雜的問題更加簡單化。
  2. 從以前面向過程的執行者,變成了指揮者。
  3. 面向物件更符合人類思維。面向過程則是機器的思想。