一、構造函數和new命令
1、構造函數
- JavaScript語言的對象體系,不是基於“類”的,而是基於構造函數(constructor)和原型鏈(prototype)
- 爲了與普通函數區別,構造函數名字的第一個字母通常大寫,比如: var Person = function(){ this.name = '王大錘'; }
- 構造函數的特點:
a、函數體內部使用了
this
關鍵字,代表了所要生成的對象實例; b、生成對象的時候,必需用new
命令調用此構造函數
2、new
作用:就是執行構造函數,返回一個實例對象
var Person = function(name, age){ this.name = name; this.age = age; this.email = '[email protected]'; this.eat = function(){ console.log(this.name + ' is eating noodles'); } } var per = new Person('王大錘', 18); console.log(per.name + ', ' + per.age + ', ' + per.email); //王大錘, 18, [email protected] per.eat(); //王大錘 is eating noodles
執行new命令時的原理步驟:
- 創建一個空對象,作爲將要返回的對象實例
- 將這個空對象的原型,指向構造函數的
prototype
屬性 - 將這個空對象賦值給函數內部的
this
關鍵字 - 開始執行構造函數內部的代碼
注意點:當構造函數裏面有return關鍵字時,如果返回的是非對象,new命令會忽略返回的信息,最後返回時構造之後的this對象; 如果return返回的是與this無關的新對象,則最後new命令會返回新對象,而不是this對象。示例代碼:
console.log('---- 返回字符串 start ----'); var Person = function(){ this.name = '王大錘'; return '羅小虎'; } var per = new Person(); for (var item in per){ console.log( item + ': ' + per[item] ); } //---- 返回字符串 start ---- //name: 王大錘 console.log('----- 返回對象 start ----'); var PersonTwo = function(){ this.name = '倚天劍'; return {nickname: '屠龍刀', price: 9999 }; } var per2 = new PersonTwo(); for (var item in per2){ console.log(item + ': ' + per2[item]); } //----- 返回對象 start ---- //nickname: 屠龍刀 //price: 9999
new命令執行的內部過程,可以用下面的代碼模擬:
/* 第一個參數:constructor 表示構造函數名 後面的params指該構造函數需要傳遞的參數,可以多個參數 */ function testNew(constructor, params){ //1、將當前函數testNew的所有參數arguments轉成數組 var args = [].slice.call(arguments); //2、取出數組args中的第一個元素,也就是構造函數名constructor var constructor = args.shift(); //3、創建一個空對象,繼承構造函數的prototype屬性 var context = Object.create(constructor.prototype); //4、傳入構造函數參數,執行構造函數 constructor.apply(context, args); //5、返回實例化的對象 return context; } //測試 var Person = function(name, age){ this.name = name; this.age = age; this.run = function(){ console.log(this.age + "歲的" + this.name + "正在跑步"); } } var per = testNew(Person, "王大錘", 18); console.log( per ); per.run(); /* Person {name: "王大錘", age: 18, run: ƒ} 18歲的王大錘正在跑步 */
如果調用構造函數的時候,忘記使用new關鍵字,則構造函數裏面的this爲全局對象window,屬性也會變成全局屬性,
則被構造函數賦值的變量不再是一個對象,而是一個未定義的變量,js不允許給undefined添加屬性,所以調用undefined的屬性會報錯。
示例:
var Person = function(){ console.log( this == window ); //true this.price = 5188; } var per = Person(); console.log(price); //5188 console.log(per); //undefined console.log('......_-_'); //......_-_ console.log(per.price); //Uncaught TypeError: Cannot read property 'helloPrice' of undefined
爲了規避忘記new關鍵字現象,有一種解決方式,就是在函數內部第一行加上 : 'use strict';
表示函數使用嚴格模式,函數內部的this不能指向全局對象window, 默認爲undefined, 導致不加new調用會報錯
var Person = function(){ 'use strict'; console.log( this ); //undefined this.price = 5188; //Uncaught TypeError: Cannot set property 'helloPrice' of undefined } var per = Person();
另外一種解決方式,就是在函數內部手動添加new命令:
var Person = function(){ //先判斷this是否爲Person的實例對象,不是就new一個 if (!(this instanceof Person)){ return new Person(); } console.log( this ); //Person {} this.price = 5188; } var per = Person(); console.log(per.price); //5188
二、this關鍵字
var Person = function(){ console.log('1111'); console.log(this); this.name = '王大錘'; this.age = 18; this.run = function(){ console.log('this is Person的實例對象嗎:' + (this instanceof Person) ); console.log(this); } } var per = new Person(); per.run(); /* 打印日誌: 1111 Person {} this is Person的實例對象嗎:true Person {name: "王大錘", age: 18, run: function} */ console.log('---------------'); var Employ = { email: '[email protected]', name: '趙日天', eat: function(){ console.log(this); } } console.log(Employ.email + ', ' + Employ.name); Employ.eat(); /* 打印日誌: --------------- [email protected], 趙日天 Object {email: "[email protected]", name: "趙日天", eat: function} */
1、this總是返回一個對象,返回屬性或方法當前所在的對象, 如上示例代碼
2、對象的屬性可以賦值給另一個對象,即屬性所在的當前對象可變化,this的指向可變化
var A = { name: '王大錘', getInfo: function(){ return '姓名:' + this.name; } } var B = { name: '趙日天' }; B.getInfo = A.getInfo; console.log( B.getInfo() ); //姓名:趙日天 //A.getInfo屬性賦給B, 於是B.getInfo就表示getInfo方法所在的當前對象是B, 所以這時的this.name就指向B.name
3、由於this指向的可變化性,在層級比較多的函數中需要注意使用this。一般來說,在多層函數中需要使用this時,設置一個變量來固定this的值,然後在內層函數中這個變量。
示例1:多層中的this
//1、多層中的this (錯誤演示) var o = { f1: function(){ console.log(this); //這個this指的是o對象 var f2 = function(){ console.log(this); }(); //由於寫法是(function(){ })() 格式, 則f2中的this指的是頂層對象window } } o.f1(); /* 打印日誌: Object {f1: function} Window {stop: function, open: function, alert: function, confirm: function, prompt: function…} */ //2、上面代碼的另一種寫法(相同效果) var temp = function(){ console.log(this); } var o = { f1: function(){ console.log(this); //這個this指o對象 var f2 = temp(); //temp()中的this指向頂層對象window } } o.f1(); /* 打印日誌 Object {f1: function} Window {stop: function, open: function, alert: function, confirm: function, prompt: function…} */ //表示上面兩種寫法是一樣的效果,this的錯誤演示 //3、多層中this的正確使用:使用一個變量來固定this對象,然後在內層中調用該變量 var o = { f1: function(){ console.log(this); //o對象 var that = this; var f2 = function(){ console.log(that); //這個that指向o對象 }(); } } o.f1(); /* 打印日誌: Object {f1: function} Object {f1: function} */
示例2: 數組遍歷中的this
//1、多層中數組遍歷中this的使用 (錯誤演示) var obj = { email: '大錘@sina.com', arr: ['aaa', 'bbb', '333'], fun: function(){ //第一個this指的是obj對象 this.arr.forEach(function(item){ //這個this指的是頂層對象window, 由於window沒有email變量,則爲undefined console.log(this.email + ': ' + item); }); } } obj.fun(); /* 打印結果: undefined: aaa undefined: bbb undefined: 333 */ //2、多層中數組遍歷中this的使用 (正確演示,第一種寫法) var obj = { email: '大錘@sina.com', arr: ['aaa', 'bbb', '333'], fun: function(){ //第一個this指的是obj對象 var that = this; //將this用變量固定下來 this.arr.forEach(function(item){ //這個that指的是對象obj console.log(that.email + ': ' + item); }); } } obj.fun(); //調用 /* 打印日誌: 大錘@sina.com: aaa 大錘@sina.com: bbb 大錘@sina.com: 333 */ //3、多層中數組遍歷中this正確使用第二種寫法:將this作爲forEach方法的第二個參數,固定循環中的運行環境 var obj = { email: '大錘@sina.com', arr: ['aaa', 'bbb', '333'], fun: function(){ //第一個this指的是obj對象 this.arr.forEach(function(item){ //這個this從來自參數this, 指向obj對象 console.log(this.email + ': ' + item); }, this); } } obj.fun(); //調用 /* 打印日誌: 大錘@sina.com: aaa 大錘@sina.com: bbb 大錘@sina.com: 333 */
4、關於js提供的call、apply、bind方法對this的固定和切換的用法
1)、function.prototype.call(): 函數實例的call
方法,可以指定函數內部this
的指向(即函數執行時所在的作用域),然後在所指定的作用域中,調用該函數。
如果call(args)裏面的參數不傳,或者爲null、undefined、window, 則默認傳入全局頂級對象window;
如果call裏面的參數傳入自定義對象obj, 則函數內部的this指向自定義對象obj, 在obj作用域中運行該函數
var obj = {}; var f = function(){ console.log(this); return this; } console.log('....start.....'); f(); f.call(); f.call(null); f.call(undefined); f.call(window); console.log('**** call方法的參數如果爲空、null和undefined, 則默認傳入全局等級window;如果call方法傳入自定義對象obj,則函數f會在對象obj的作用域中運行 ****'); f.call(obj); console.log('.....end.....'); /* 打印日誌: ....start..... Window {stop: function, open: function, alert: function, confirm: function, prompt: function…} Window {stop: function, open: function, alert: function, confirm: function, prompt: function…} Window {stop: function, open: function, alert: function, confirm: function, prompt: function…} Window {stop: function, open: function, alert: function, confirm: function, prompt: function…} Window {stop: function, open: function, alert: function, confirm: function, prompt: function…} **** call方法的參數如果爲空、null和undefined, 則默認傳入全局等級window;如果call方法傳入自定義對象obj,則函數f會在對象obj的作用域中運行 **** Object {} .....end..... */
call可以接收多個參數:第一個參數是this所指向的那個對象,後面的參數就是函數調用時需要的參數,模擬簡寫爲:
func.call(thisValue, arg1, arg2, ...); 示例:
var obj = { name: '張三', age: 10, email: '張三@sina.com' } function change(name, age){ this.name = name; this.age = age; console.log(this); } change.call(obj, '王大錘', '28'); //Object {name: "王大錘", age: "28", email: "張三@sina.com"}
2)、function.prototype.apply(): 函數實例的apply方法,和call的作用差不多,也是改變this的指向,調用該函數。 唯一的區別就是,它接受一個數組作爲函數執行時的參數,使用格式如下: func.apply( thisValue, [arg1, arg2, ...] );
var obj = { name: '張三', age: 10, email: '張三@sina.com' } function change(name, age){ this.name = name; this.age = age; console.log(this); } change.apply(obj, ['王大錘', '28']); //Object {name: "王大錘", age: "28", email: "張三@sina.com"}
3)、function.prototype.bind() : bind方法主要用於將函數體內的this綁定到某個對象,然後返回新函數。 先來個簡單示例,展示普通將方法賦值給變量,和使用bind()綁定的區別:
var d = new Date(); //第一次:將getTime方法賦值給print變量 var print = d.getTime; try{ print(); //執行時報錯,進入異常處理 }catch(e){ console.log('報錯:' + e.message); //捕捉異常 //打印結果:報錯:this is not a Date object. } //第二次賦值:bind()應用,bind方法將getTime方法內部的this綁定到d對象,這時就可以安全的將這個方法賦值給其他變量 print = d.getTime.bind(d); print(); //1498740682340
var obj = { count:0, run: function(){ console.log(this); this.count++; } } //1、對象直接調用自己方法,run裏面的this指向obj對象 obj.run(); console.log( obj.count ); /* 打印日誌: Object {count: 0, run: function} 1 */ //2、將對象方法賦值給變量,這時run裏面的this指向了頂層對象window var run2 = obj.run; run2(); //執行obj.count不會變化,創建了全局變量count=undefined console.log( obj.count ); //值未變化 console.log( count ); //undefined++ 等於NaN /* 打印日誌: Window {stop: function, open: function, alert: function, confirm: function, prompt: function…} 1 NaN */ //3、應用bind方法將run方法內部的this, 綁定到obj對象。 var run2 = obj.run.bind(obj); run2(); console.log( obj.count ); //值有變化了 /* 打印日誌: Object {count: 1, run: function} 2 */
bind除了綁定this外,還可以綁定原函數的參數
var add = function(x, y){ return x * this.m + y * this.n; } var obj = { m: 2, n: 2}; var newAdd = add.bind(obj, 5); console.log( newAdd(5) ); //20 //另一種寫法 var newAdd2 = add.bind(obj, 5, 5); console.log( newAdd2() ); //20
bind方法使用注意點:
a、bind方法沒執行一次,都返回一個新函數。所以在我們日常進行綁定監聽事件的時候要特別注意: 舉個列子,給某個標籤綁定點擊事件:
/* 給element標籤綁定和單擊事件,和取消綁定單擊事件 //1、第一種綁定和取消綁定方法 element.addEventListener('click', o.m.bind(o)); //綁定 element.removeEventListener('click', o.m.bind(o)); //取消綁定事件 //2、第二種綁定和取消綁定方法 var listenter = o.m.bind(o); element.addEventListener('click', listenter); //綁定 element.removeEventListener('click', listenter);//取消綁定 //說明:第一種綁定的事件是不能取消綁定的,爲什麼,因爲這種綁定bind方法生成的一個匿名函數 //第二種綁定的事件才能取消綁定 */
b、將包含this的方法直接當做回調函數
var obj = { name: '王大錘', arr: ['aa', 'bb', 'cc'], print: function(){ this.arr.forEach(function(n){ console.log( n + ':' + this.name ); //這個this指向window }); }, print2: function(){ this.arr.forEach(function(n){ console.log( n + ':' + this.name ); //這個this指向obj }.bind(this)); } } obj.print(); /* 打印結果:此時print裏面的forEach裏面的this指向頂層對象window aa: bb: cc: */ obj.print2(); /* 打印結果:通過bind綁定this aa:王大錘 bb:王大錘 cc:王大錘 */
三、prototype對象
先看關於原型對象的一張經典圖片,其次要了解prototype和__proto__的區別
配合上圖,相關的代碼如下:
var Foo = function(){ } var f1 = new Foo(); var f2 = new Foo(); var o1 = new Object(); var o2 = new Object(); var isTrue1 = Foo.prototype === f1.__proto__, isTrue2 = Foo.prototype === f2.__proto__, isTrue3 = Object.prototype === o1.__proto__, isTrue4 = Object.prototype === o2.__proto__, isTrue5 = Foo.prototype.__proto__ === Object.prototype, isTrue6 = f1.__proto__.__proto__ === Object.prototype, isTrue7 = f1.__proto__.__proto__.__proto__ === Object.prototype.__proto__, isTrue8 = f1.__proto__.__proto__.__proto__ === null; console.log(isTrue1 + ", " + isTrue2 + ", " + isTrue3 + ", " + isTrue4 + ", " + isTrue5 + ", " + isTrue6 + ", " + isTrue7 + ", " + isTrue8); //true, true, true, true, true, true, true, true var isTrue9 = Foo.prototype.constructor === Foo; var isTrue10 = f1.constructor === Foo; var isTrue11 = Foo.prototype.__proto__ === Object.prototype; var isTrue12 = Foo.prototype.__proto__.__proto__ === null; var isTrue13 = Foo.__proto__ === Function.prototype; /*Foo.constructor這裏把Foo看成是Function構造函數的一個實例對象 所以構造函數Function的對象Foo的constructor 指向原構造函數Function */ var isTrue14 = Foo.constructor === Function; /* Function要特別理解一下: Function.constructor === Function === Function.prototype.constructur 當調用Function.constror時這時把Function看做是構造函數Function的一個實例對象, 即構造函數是Function, 實例對象也是Function, 然後對象Function的屬性constructor指向構造函數Function */ var isTrue15 = Function.constructor === Function; var isTrue16 = Function.constructor === Function.prototype.constructor; console.log(isTrue9 + ", " + isTrue10 + ", " + isTrue11 + ", " + isTrue12 + ", " + isTrue13 + ", " + isTrue14 + " ," + isTrue15 + ", " + isTrue16); //true, true, true, true, true, true ,true, true
1、在JS裏,萬物皆對象。方法(Function)是對象,方法的原型(Function.prototype)也是對象。
2、對象具有屬性__proto__,可稱爲隱式原型,一個對象的隱式原型指向構造該對象的構造函數的原型,
這也保證了實例能夠訪問在構造函數原型中定義的屬性和方法。
比如上圖例子: f1.__proto__, f2.__proto__ , Function.prototype這三個對象指向同一個對象。
3、方法(Function): 方法這個特殊的對象,除了和其他對象一樣有上述_proto_屬性之外,
還有自己特有的屬性——原型屬性(prototype),這個屬性是一個指針,指向一個對象,
這個對象的用途就是包含所有實例共享的屬性和方法(我們把這個對象叫做原型對象)。
原型對象也有一個屬性,叫做constructor,這個屬性包含了一個指針prototype,指回原構造函數。
比如上圖例子:Foo.prototype.constructor == Foo
總結:1)、對象有屬性__proto__,指向該對象的構造函數的原型對象;
2)、方法除了有屬性__proto__,還有屬性prototype,prototype指向該方法的原型對象。
(方法調用__proto__屬性的時候,其實是可看做把該方法當做構造函數Function的一個實例對象來使用;
調用prototype則是把該方法當做一個構造函數來使用)
配合上圖和相關代碼,我們看到:
4、構造函數Foo()的原型屬性Foo.prototype指向了原型對象,在原型對象裏有共有的方法,所有構造函數聲明的實例(這裏是f1,f2)都可以共享這個方法。
5、原型對象Foo.prototype有一個指針constructor指回構造函數。
6、f1和f2是Foo這個對象的兩個實例,這兩個對象也有屬性__proto__,指向構造函數的原型對象,這樣子就可以訪問原型對象的所有方法了。
7、構造函數Foo()除了是方法,也是對象,也有__proto__屬性,指向它的構造函數的原型對象。函數的構造函數是Function嘛,因此這裏的__proto__指向了Function.prototype。
其實除了Foo(),Function(), Object()也是一樣的道理。
8、原型對象也是對象,它的__proto__屬性指向它的構造函數的原型對象。這裏是Object.prototype。
最後,Object.prototype的__proto__屬性指向null。終於走到大結局,null沒有自己的原型對象。
Object和Function的關聯:
/* Object和Function的關係: 1、Function.prototype.__proto__ === Object.prototype 2、Object.__proto__ === Function.prototype 3、Object.constructor === Function */ //Function.prototype是可看做是構造函數Object的一個實例對象,原型指向Object /* 不過Function.prototype和其他如Array, String, Number,還有自定義構造函數的prototype有點不一樣 typeof Function.prototype === "function" 其他如Array, String, Number, 還有自定義構造函數的typeof都是"object" */ var isTrue17 = Function.prototype.__proto__ === Object.prototype; //Object.__proto__ 這裏的Object可看做是構造函數Function的一個實例對象 var isTrue18 = Object.__proto__ === Function.prototype; //Object.constructor 這裏的Object可看做是構造函數Function的一個實例對象 var isTrue19 = Object.constructor === Function; console.log(isTrue17 + ", " + isTrue18 + ", " + isTrue19); //true, true, true
原型對象的屬性爲所有實例對象所共有,修改原型對象屬性的值,變動體現在所有的實例對象上;
當實例對象有與原型對象同名屬性,則優先尋找實例對象自有屬性,當沒找到對象自有屬性時,纔會去原型對象去尋找該屬性。
舉個例子: 第一步,先創建一個構造函數,再實例化兩個對象
var Person = function(name){ this.name = name; } var per1 = new Person('王大錘'); var per2 = new Person('艾邊城'); console.log( per1 ); console.log( per2 );
展開日誌截圖所示:
第二步:通過構造函數的原型對象添加一個屬性Color
Person.prototype.Color = "white"; console.log(per1);
展開日誌,看到在實例對象的構造函數裏面多了一個所有該構造函數實例對象所共享的屬性Color
第三步:給per1對象設置Color屬性
per1.Color = "Green"; console.log( per1 ); console.log( per2 );
四、對象的Copy, 和麪向對象三大特性模擬(繼承,多態,封裝)
1、拷貝對象,需要滿足以下兩個條件:
- 拷貝後的對象,與原對象具有同樣的prototype原型對象。
- 拷貝後的對象,與原對象具有同樣的屬性。
對象拷貝的函數,和示例測試code
function copyObject(orig) { //創建要複製的對象 var copy = Object.create(Object.getPrototypeOf(orig)); copyOwnPropertiesFrom(copy, orig); return copy; } //拷貝對象的屬性 function copyOwnPropertiesFrom(target, source) { Object .getOwnPropertyNames(source) .forEach(function(propKey) { var desc = Object.getOwnPropertyDescriptor(source, propKey); Object.defineProperty(target, propKey, desc); }); return target; } //測試code var Student = function(name, age, otherInfo){ this.name = name; this.age = age; this.otherInfo = otherInfo; this.run = function(){ console.log(this.name + "正在 run..."); } } var stu = new Student("大錘", 18, {sports:["籃球", "跑步", "游泳"]}); var stu2 = stu; var stu3 = copyObject(stu); stu.name = "張三"; console.log(stu.name + ", " + stu2.name + ", " + stu3.name); console.log((stu==stu2) + ", " + (stu == stu3) + ", " + (stu2==stu3)); /* 張三, 張三, 大錘 true, false, false */
2、js面向對象之繼承特性體現,讓一個構造函數繼承另外一個構造函數:
1)、在子類的構造函數中,調用父類構造函數
2)、讓子類的原型指向父類的原型,這樣子類原型繼承了父類原型
多態的模擬則是重寫子構造函數的繼承自父構造函數的方法
示例演示
//1、繼承 //第一步,創建構造函數 function Shape(){ this.x = 2; this.y = 2; console.log("hello, I'm Shape constructor !"); } Shape.prototype.move = function(x, y){ this.x += x; this.y += y; console.log("shape move, this.x=" + this.x + ", this.y=" + this.y); } //定義子類,繼承父類 function Rectangle(){ Shape.call(this); //調用父類構造函數 console.log("哈嘍,I'm Rectangle's constructor"); } //另外一種子類繼承父類的寫法 function RectangleTwo(){ this.base = Shape; this.base(); console.log("hello, I'm RectangleTwo's constructor..."); } function RectangleThree(){ console.log("Hello, I'm RectangleThree's constructor..."); } //第二步,子類繼承父類的原型 Rectangle.prototype = Object.create(Shape.prototype); Rectangle.prototype.constructor = Rectangle; RectangleTwo.prototype = Object.create(Shape.prototype); RectangleTwo.prototype.constructor = RectangleTwo; RectangleThree.prototype = new Shape(); RectangleThree.prototype.constructor = RectangleThree; //測試結果 var rect1 = new Rectangle(); rect1.move(1, 1); console.log(rect1 instanceof Rectangle); console.log(rect1 instanceof Shape); /* hello, I'm Shape constructor ! 哈嘍,I'm Rectangle's constructor shape move, this.x=3, this.y=3 true true true */ var rect2 = new RectangleTwo(); rect2.move(2, 2); console.log(rect2 instanceof RectangleTwo); console.log(rect2 instanceof Shape); console.log(rect2 instanceof Object); console.log(rect2 instanceof Rectangle); /* hello, I'm Shape constructor ! hello, I'm RectangleTwo's constructor... shape move, this.x=4, this.y=4 true true true false */ var rect3 = new RectangleThree(); rect3.move(3, 3); console.log(rect3 instanceof RectangleThree); console.log(rect3 instanceof Shape); console.log(rect3 instanceof Object); console.log(rect3 instanceof Rectangle); console.log(rect3 instanceof RectangleTwo); /* Hello, I'm RectangleThree's constructor... shape move, this.x=5, this.y=5 true true true false false */ //2、多態的體現 Shape.prototype.info = function(){ console.log("Shape's x = " + this.x + ", y = " + this.y); } Rectangle.prototype.info = function(){ console.log("Rectangle's x = " + this.x + ", y = " + this.y); } RectangleThree.prototype.info = function(){ console.log("RectangleThree's x = " + this.x + ", y = " + this.y); } //測試: Rectangle和RectangleThree子構造函數重寫了原型的info方法,RectangleTwo沒有重寫,繼承自Shape的info方法 var shape = new Shape(); shape.info(); // Shape's x = 2, y = 2 rect1.info(); //Rectangle's x = 3, y = 3 rect2.info(); //Shape's x = 4, y = 4 rect3.info(); //RectangleThree's x = 5, y = 5
3、對象封裝的簡單模擬
//3、對象的封裝:隱藏細節,對外暴露需要的屬性和方法 var person = (function(){ var obj = new Object(); obj.name = "大錘"; var perAge = 18; var perRun = function(){ console.log(this.name + "... 正在run..."); } var perJump = function(){ console.log(this.name + "今年" + perAge + "....正在jump"); } obj.run = perRun; obj.jump = perJump; return obj; })(); //測試, person對象對外公開的name, run, jump可以訪問,其他內部的perAge 和perRun等不能訪問 console.log( person.name ); //大錘 person.run(); //大錘... 正在run... person.jump(); //大錘今年18....正在jump person.name = "大錘三"; person.run(); //大錘三... 正在run...
五、異步執行之Ajax和Promise
1、傳統的異步操作Ajax, 示例寫法:
function testAjax(keyword, onloadBlock, onerrorBlock){ var xhr = new XMLHttpRequest(); var url = "http://www.cnblogs.com/tandaxia/p/7079214.html?search=" + keyword; xhr.open('GET', url, true); xhr.onload = function(e){ console.log(e); console.log(this); console.log("\n************ start ********\n"); if (this.status == 200){ onloadBlock(this.responseText); //處理服務器返回的結果 } } xhr.onerror = function(e){ onerrorBlock(e); } xhr.send(); //發送請求 } //測試調用 testAjax('hello world', console.log, console.error);
2、Promise實現的異步操作,區別與Ajax的,它的異步任務立刻返回一個Promise對象,使得程序具備正常的同步運動的流程,回調函數不必要一層層嵌套。 Promise對象只有三種狀態:未完成(pending)、已完成(resolved)、失敗(rejected)
操作的狀態變化途徑只有兩種:未完成 --> 已完成; 未完成 --> 失敗
function testPromise(keyword){ var xhr = new XMLHttpRequest(); var url = "http://www.cnblogs.com/tandaxia/p/7079214.html?search=" + keyword; var proObj = new Promise(function(resolveBlock, rejectBlock){ xhr.open('GET', url, true); xhr.onload = function(e){ if (this.status == 200){ resolveBlock(this.responseText); } } xhr.onerror = function(e){ rejectBlock(e); } xhr.send(); //發送請求 }); return proObj; //返回Promise對象 } //測試調用 testPromise('hello world').then(console.log, console.error);
六、DOM對象
1、基本概念
1.1、DOM : DOM是JavaScript操作網頁的接口,全稱“文檔對象模型”(Document Object Model)。
它的作用是將網頁轉爲一個JavaScript對象,從而可以用腳本進行各種操作。
瀏覽器根據DOM模型,將結構化文檔(比如HTML和XML)解析成一系列的節點,再有這些節點組成一個樹狀結構(DOM Tree)。
所有的節點和最終的樹狀結構,都有規範的對外接口。所以,DOM可以理解成網頁的編程接口。
1.2、節點:DOM的最小組成單位叫做節點(Node)。文檔的樹形結構(DOM樹),就是由各種不同類型的節點組成。
節點的類型有七種:
a、Document: 整個文檔樹的頂層節點
b、DocumentType: doctype標籤(比如<!DOCTYPE html>)
c、Element: 網頁的各種HTML標籤(比如<body>、<a>等)
d、Attribute: 網頁元素的屬性(比如class="right")
e、Text: 標籤之間或標籤包含的文本
f、Comment: 註釋
g、DocumentFragment: 文檔的片段
這七種節點都屬於瀏覽器原生提供的節點對象的派生對象,具有一些共同的屬性和方法。
1.3、節點樹
一個文檔的所有的節點,按照所在的層級,可以抽象成一種樹狀結構。這種樹狀結構就是DOM。
最頂層的節點就是document節點,它代表了整個文檔。文檔裏面最高一層的HTML標籤,一般是<html>,它構成樹結構的根節點(root node),
其他html標籤節點都是它的下級。
除了根節點以外,其他節點對於周圍的節點都存在三種關係: 父節點關係(parentNode) : 直接的那個上級節點;
子節點關係(childNodes) : 直接的下級節點;
同級節點關係(sibling) : 擁有同一個父節點的節點
DOM提供操作接口,用來獲取三種關係的節點。其中,子節點接口包括firstChild(第一個節點)和lastChild(最後一個節點)等屬性,
同級節點接口包括nextSibling(緊鄰在後的那個同級節點)和previousSibling(緊鄰在前的那個同級節點)屬性。
七、元素的clientWidth、offsetWidth、scrollWidth的區別
1、clientWidth和clientHeight: 表示元素節點的CSS寬度和高度, 返回一個整數值,
只對塊級元素有效,對於行內元素返回0;
包括元素內容+padding部分,不包含border和margin, 如果有滾動條,要減去滾動條的尺寸;
document.documentElement的clientHeight屬性,返回當前視口的高度(即瀏覽器窗口的高度),
等同於window.innerHeight屬性減去水平滾動條的高度;
document.body的高度則是網頁的實際高度。
一般來說,document.body.clientHeight大於document.documentElement.clientHeight
2、clientLeft表示元素節點左邊框的寬度,不包括左側的padding和margin;
clientTop表示網頁元素的頂部邊框的寬度
3、offsetWidth和offsetHeight: 表示元素節點的水平寬度和垂直高度,
包括元素本身的寬度或高度、padding和border, 以及水平滾動條的尺寸
4、offsetLeft表示元素左上角相對於父節點的水平位移;
offsetTop返回垂直位移
5、scrollWidth和scrollHeight:表示當前元素的總寬度和總高度,
包括溢出容器、當前不可見的部分,包括padding,
不包括boder、margin以及水平滾動條和垂直滾動條的高度。
6、scrollLeft表示當前元素的水平滾動條向右側滾動的像素數量,
scrollTop表示當前元素的垂直滾動向下滾動的像素數量。
對於沒有滾動條的網頁元素的這兩個屬性的值爲0。