對JSON不甚瞭解的可以先看下這個:《JSON三分鐘課程》、《JSON入門課程》
對JSON的操作主要是解析JSON字串為一個對象和將一個對象轉換成JSON字串。
網上搜一下JSON解析,就會發現一堆文章和方法,當然這篇文章主要不是討論JSON的解析,不過也可以大致回顧一下:
第一、eval。eval('(' + jsonStr + ')');加個括弧是為了強製表達式運算,不然直接eval('{}'),具體原因可以去翻看一下《JS語言精髓與編程實踐》
第二、Function
第三、另外有一個JSON解析的庫,這個可以自己去下載
第四、除了IE6,7之外,現代瀏覽器基本都內建了JSON解析器
【說明,prototype本身也實現了一個evalJSON方法,不過也脫離不了上面幾種情況】
執行個體如下(沒有用JSON解析庫):
(function(){
var obj = "{\"name\":\"xesam\"}";
console.log('eval:',eval('(' + obj + ')'));
console.log('Function',(new Function('return' + obj))());
console.log('JSON:',JSON.parse(obj));
})();
//結果:{name:'xesam'}
現在將上面的過程反過來,將一個對象編碼為一個JSON字串。同樣,如果瀏覽器帶有原生的JSON方法,有一個與parse對應的方法stringify
(function(){
var obj = {name:'xesam'};
console.log('obj:',JSON.stringify(obj));//'{"name":"xesam"}'
})();
這種操作就不像解析那樣有那麼多方法,因此需要自己來寫。
寫之前,先來探討一下目標對象可能出現的情況,然後把它轉換成字串的形式。
var object_1 = null;
var object_2 = undefined;
var object_3 = 12;
var object_4 = '12';
var object_5 = true;
var object_6 = [1,2,3];
var object_7 = {name :'xesam'};
var object_8 = {name : function(){}};
先硬性規定一下,null、undefined、true、false返回對應的'null'、'undefined'、'true'、'false'。數字直接量和字串直接量返回對應的數值。
下面主要是區分出Array類型和Function,主要操作就是遍曆每一項,碰到數組或者一般對象就依次展開,所以可以得到一個初始的版本:
(function(){
function object2Str(object){
if(arguments.length != 0 && typeof object == 'undefined'){
return 'undefined';
}
switch (object) {
case null : return 'null';
case true : return 'true';
case false: return 'false';
}
var type = Object.prototype.toString.call(object);
switch(type){
case '[object Number]' : return String(object);
case '[object String]' : return String(object);
case '[object Array]' : {
var ret = [];
for(var i = 0,len = object.length; i < len; i++){
ret.push(object2Str(object[i]));
}
return '[' + ret.join(',') + ']';
}
case '[object Object]' :{
var ret = [];
for(var key in object){
if(object.hasOwnProperty(key)){
if(typeof object2Str(object[key]) != 'undefined'){
ret.push(String(key) + ':' + object2Str(object[key]));
}
}
}
return '{' + ret.join(',') + '}';
}
}
}
console.log(object2Str(null));
console.log(object2Str(undefined));
console.log(object2Str(true));
console.log(object2Str(12));
console.log(object2Str('34'));
console.log(object2Str({name : function(){}}));
var object = [1,2,3,4,[5,6,7,8,9],6];
console.log(object2Str(object));
var object = {
name:'xesam',
age : 24,
books:{
key_1 : 'book_1',
key_2 : 'book_2'
}
};
console.log(object2Str(object));
})();
看這個實現,{name:function(){}}這個並沒有顯式的提到,因為在預設情況下if(typeof object2Str(object[key]) != 'undefined')這一步就被處理了。
基本的架構得到了,我們的實現中還包括了undefiend的處理,Prototype的實現中並沒有這一步,需要注意。
下面要做的就是一些修繕工作,繼續補充:
在上面的例子中,我們忽略了var number_1 = 12與var number_2 = new Number(12)的區別,我們先填補這兩者之間的區別。
於是在處理每一個值之前先直接調用valueOf獲得變數的原始值。於是上面的例子變為:
(function(){
function object2Str(object){
if(arguments.length != 0 && typeof object == 'undefined'){
return 'undefined';
}
var type = Object.prototype.toString.call(object);
switch (type) {
case '[object Number]' :
case '[object String]' :
case '[object Boolean]':
object = object.valueOf();
}
switch (object) {
case null : return 'null';
case true : return 'true';
case false: return 'false';
}
type = typeof object;
switch(type){
case 'number' : return String(object);
case 'string' : return String(object);
case 'object' : {
var ret = [];
if(Object.prototype.toString.call(object) == '[object Array]'){
for(var i = 0,len = object.length; i < len; i++){
ret.push(object2Str(object[i]));
}
return '[' + ret.join(',') + ']';
}else if(Object.prototype.toString.call(object) == '[object Function]'){
return '{}';
}else{
for(var key in object){
if(object.hasOwnProperty(key)){
if(typeof object2Str(object[key]) != 'undefined'){
ret.push(String(key) + ':' + object2Str(object[key]));
}
}
}
return '{' + ret.join(',') + '}';
}
}
}
}
console.log(object2Str(null));
console.log(object2Str(undefined));
console.log(object2Str(true));
console.log(object2Str(12));
console.log(object2Str('34'));
console.log(object2Str({name : function(){}}));
var object = [1,2,3,4,[5,6,7,8,9],6];
console.log(object2Str(object));
var object = {
name:'xesam',
age : 24,
books:{
key_1 : 'book_1',
key_2 : 'book_2'
}
};
console.log(object2Str(object));
})();
【說明:上面的實現中並未轉義",/,\和還有一些控制符在Prototype中,調用的是string.inspect這個方法來實現的,不過這一部分在後面的String部分,所以暫且這麼實現,瞭解即可】
現在回到Prototype中,在Prototype裡面,相似的方法叫做Str【這個函數是大寫開頭,說明是一個局部函數,另一個大寫開頭的是Type,這兩個方法並未對外公開】
對比object2Str和Str,除了上面說的特殊字元轉義之外,Str的實現還多了兩個參數就是key和stack。
這個stack的作用是什麼呢?為了避免迴圈使用導致棧溢出。
比如我有一個對象:
var obj = {name : this}。
去調用Prototype裡面的Str方法,就會報錯(typeError),如果直接調用object2Str就會報棧溢出。所以這是一個必要的檢測。
所以我們仿寫最終的版本是:
(function(){
function object2Str(object,stack){
stack = stack || [];
if(arguments.length != 0 && typeof object == 'undefined'){
return 'undefined';
}
var type = Object.prototype.toString.call(object);
switch (type) {
case '[object Number]' :
case '[object String]' :
case '[object Boolean]':
object = object.valueOf();
}
switch (object) {
case null : return 'null';
case true : return 'true';
case false: return 'false';
}
type = typeof object;
switch(type){
case 'number' : return String(object);
case 'string' : return String(object);
case 'object' : {
for (var i = 0, length = stack.length; i < length; i++) {
if (stack[i] === object){
console.log('error');
throw new TypeError();
}
}
stack.push(object);
var ret = [];
if(Object.prototype.toString.call(object) == '[object Array]'){
for(var i = 0,len = object.length; i < len; i++){
ret.push(object2Str(object[i]));
}
ret = '[' + ret.join(',') + ']';
}else{
for(var key in object){
if(object.hasOwnProperty(key)){
if(typeof object2Str(object[key],stack) != 'undefined'){
ret.push(String(key) + ':' + object2Str(object[key],stack));
}
}
}
ret ='{' + ret.join(',') + '}';
}
stack.pop();
return ret;
}
}
}
console.log(object2Str(null));
console.log(object2Str(undefined));
console.log(object2Str(true));
console.log(object2Str(12));
console.log(object2Str('34'));
console.log(object2Str({name : function(){}}));
var object = [1,2,3,4,[5,6,7,8,9],6];
console.log(object2Str(object));
var object = {
name:'xesam',
age : 24,
books:{
key_1 : 'book_1',
key_2 : 'book_2'
}
};
console.log(object2Str(object));
var object = {
name : this
}
console.log(object2Str(object));
})();
另外,在Object中,Str是為toJSON來服務的,因此這個調用方法也是特別處理了的,其參數形式是Str(key, holder, stack)
由於在Prototype的Object中是實現了一個keys方法和一個values方法,所以在類似下面的代碼中
for(var key in object){
if(object.hasOwnProperty(key)){
if(typeof object2Str(object[key]) != 'undefined'){
ret.push(String(key) + ':' + object2Str(object[key]));
}
}
}
直接使用了keys函數,Prototype中的 具體實現是:
var keys = Object.keys(value);
for (var i = 0, length = keys.length; i < length; i++) {
var key = keys[i], str = Str(key, value, stack);
if (typeof str !== "undefined") {
partial.push(key.inspect(true)+ ':' + str);
}
}
所以這麼一來,toJSON的調用形式也變成了:
function toJSON(value) {
return Str('', { '': value }, []);
}
如果按照我們在例子中的寫法,可以直接是
function toJSON(value) {
return object2Str(value, []);
}
說完Str,另一個用的多的就是inspect方法,顧名思義啊·檢查作用,可以用來調試。
一個最簡單的實現:
(function(){
function inspect(object){
return object.toString();
}
})();
null和undefined沒有toString方法,這裡順便說明一下,typeof null == 'object',但是null又沒有toString方法,因此不能人為null是一個簡單的object
所以這個討論在前面已經討論過了,一次跳過。
其他就是一些檢測,最佳化的機制。就不算重點了。
至於keys(values)兩個方法是很簡單的,就是抄襲python,遍曆一個對象的key(value),然後裝到一個數組裡返回。
【toHTML,isHash,toQueryString】的具體實現跟後面關聯比較緊,因此在這裡就暫時不說。
轉載請註明來自小西山子【http://www.cnblogs.com/xesam/】
本文地址:http://www.cnblogs.com/xesam/archive/2011/12/22/2297781.html