日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 前端技术 > javascript >内容正文

javascript

javascript 高级程序设计_重读《JavaScript高级程序设计》

發(fā)布時(shí)間:2023/12/4 javascript 29 豆豆
生活随笔 收集整理的這篇文章主要介紹了 javascript 高级程序设计_重读《JavaScript高级程序设计》 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

最近自己在休假,打算閉門幾天將《JavaScript高級(jí)程序設(shè)計(jì)》(第3版)這本良心教材再回顧一遍。目前自己進(jìn)入前端領(lǐng)域兩年多,現(xiàn)在重讀并記錄下這本教材的“硬”知識(shí)點(diǎn) 。

本文原文鏈接請(qǐng)戳

重讀《JavaScript高級(jí)程序設(shè)計(jì)》?link.jianshu.com

函數(shù)沒有重載

ECMAScript 函數(shù)不能像傳統(tǒng)意義上那樣實(shí)現(xiàn)重載。而在其他語言(如Java)中,可以為一個(gè)函數(shù)編寫兩個(gè)定義,只要這兩個(gè)定義的簽名(接受的參數(shù)類型和數(shù)量)不同即可[p66]。ECMAScript的類型是松散形的,沒有簽名,所以是沒有重載的。

function load(num){return num + 100; } function load(num,name){return num + 200; } var result = load(100); // 300 # 后面的函數(shù)聲明覆蓋掉前面的函數(shù)聲明

基本的數(shù)據(jù)類型

基本類型值指的是簡(jiǎn)單的數(shù)據(jù)段,而引用類型指那些可能由多個(gè)值構(gòu)成的對(duì)象[p68]。這里指出來的基本的數(shù)據(jù)類型是說的es5的哈:Undefined,Null,Boolean,Number和String。

傳遞參數(shù)

ECMAScript 中所有的函數(shù)的參數(shù)都是按值傳遞的[p70]。也就是說,把函數(shù)外部的值復(fù)制給函數(shù)內(nèi)部的參數(shù),就是把值從一個(gè)變量復(fù)制到另一個(gè)變量一樣。基本類型值的傳遞如同基本類型變量的復(fù)制一樣,而引用類型值的傳遞,則如同引用類型變量的復(fù)制一樣。下面分開例子介紹兩種不同類型為什么是按值傳遞。

基本類型值

基本類型這個(gè)按值傳遞比較好理解,直接復(fù)制變量的值傳遞:

function addTen(num){num += 10;return num; } var count = 20; var result = addTen(10); console.log(count); // 20 ,沒有變化哈 console.log(result); // 30

引用類型值

有些人認(rèn)為引用類型的傳參是按照引用來傳的,那暫且認(rèn)為他們的理解是正確的,那下面的示例結(jié)果怎么解析呢?

function setName(obj){obj.name = '嘉明';obj = new Object();obj.name = '龐嘉明'; } var person = new Object(); setName(person); console.log(person.name); // '嘉明',為啥不是'龐嘉明'呢?

如果是按照引用傳的話,那么新建的對(duì)象obj = new Object()應(yīng)該是指向堆內(nèi)容的對(duì)象啊,那么改變它本有的name屬性值應(yīng)該生效,然而并沒有生效。所以它也是按值傳遞滴。

函數(shù)聲明與函數(shù)表達(dá)式

解析器在向執(zhí)行環(huán)境中加載數(shù)據(jù)時(shí),對(duì)函數(shù)聲明和函數(shù)表達(dá)式并非一視同仁[p111]。解析器會(huì)率先讀取函數(shù)聲明,并使其執(zhí)行任何代碼之前可用(可以訪問);至于函數(shù)表達(dá)式,則必須等到解析器執(zhí)行到它所在的代碼行,才會(huì)真正被解析。

console.log(sum(10 , 10)); // 20 function sum(num1 , num2){return num1 + num2; } console.log(sum(10 , 10)); //TypeError: sum is not a function var sum = function(num1 , num2){return num1 + num2; }

apply和call

每個(gè)函數(shù)都包含兩個(gè)非繼承而來的方法:apply()和call()。這兩個(gè)方法的用途都是在特定的作用域中調(diào)用函數(shù),實(shí)際上等于設(shè)置函數(shù)體內(nèi)this對(duì)象的值[116]。call和apply在對(duì)象中還是挺有用處的。

apply()方法和call()方法的作用是相同的,區(qū)別在于接收參數(shù)的方式不同。

apply

apply()方法接收兩個(gè)參數(shù):一個(gè)是在其中運(yùn)行函數(shù)的作用域,另一個(gè)是參數(shù)數(shù)組,這里的參數(shù)數(shù)組可以是Array的實(shí)例,也可以是arguments對(duì)象(類數(shù)組對(duì)象)。

function sum(num1 , num2){return num1 + num2; } function callSum1(num1,num2){return sum.apply(this,arguments); // 傳入arguments類數(shù)組對(duì)象 } function callSum2(num1,num2){return sum.apply(this,[num1 , num2]); // 傳入數(shù)組 } console.log(callSum1(10 , 10)); // 20 console.log(callSum2(10 , 10)); // 20

call

call()方法接收的第一個(gè)參數(shù)和apply()方法接收的一樣,變化的是其余的參數(shù)直接傳遞給函數(shù)。換句話說,在使用call()方法時(shí),傳遞給函數(shù)的參數(shù)必須逐個(gè)列舉出來。

function sum(num1 , num2){return num1 + num2; } function callSum(num1 , num2){return sum.call(this , sum1 , sum2); } console.log(callSum(10 , 10)); // 20

創(chuàng)建對(duì)象

雖然Object構(gòu)造函數(shù)或者對(duì)象字面量都可以用來創(chuàng)建單個(gè)對(duì)象,但是這些方式有個(gè)明顯的缺點(diǎn):使用同一個(gè)接口創(chuàng)建很多對(duì)象,會(huì)產(chǎn)生大量的重復(fù)代碼。[p144]

工廠模式

工廠模式就是造一個(gè)模子產(chǎn)生一個(gè)個(gè)對(duì)象。

function createPerson(name , age ,job){var o = new Object();o.name = name;o.age = age;o.job = job;o.sayName = function(){alert(this.name);};return o;}var person1 = createPerson('nicholas' , 29 , 'software engineer');var person2 = createPerson('greg' , 27 , 'doctor');

工廠模式解決了創(chuàng)建多個(gè)相似對(duì)象的問題(解決創(chuàng)建對(duì)象時(shí)產(chǎn)生大量重復(fù)代碼),但是沒有解決對(duì)象識(shí)別的問題(即怎么知道一個(gè)對(duì)象的類型,是Person還是Animal啊)。

構(gòu)造函數(shù)模式

下面使用構(gòu)造函數(shù)創(chuàng)建特定類型的對(duì)象。這里是Person類型:

function Person(name , age , job){ // 注意構(gòu)造函數(shù)的首字母為大寫哦this.name = name;this.age = age;this.job = job;this.sayName = function(){alert(this.name);} }var person1 = new Person('nicholas' , 29 , 'software engineer'); var person2 = new Person('greg' , 27 , 'doctor');alert(person1.constructor == Person); // true 可以理解為person1的創(chuàng)造者是Person,也就是對(duì)象的類型Person

在創(chuàng)建Person的新實(shí)例,必須使用new操作符。以這種方式調(diào)用構(gòu)造函數(shù)實(shí)際上會(huì)經(jīng)歷以下4個(gè)步驟:

  • 創(chuàng)建一個(gè)新對(duì)象
  • 將構(gòu)造函數(shù)的作用域賦給新對(duì)象(因此this指向了這個(gè)新對(duì)象)
  • 執(zhí)行構(gòu)造函數(shù)中的代碼(為這個(gè)新對(duì)象添加屬性)
  • 返回新對(duì)象
  • 構(gòu)造函數(shù)解決了重復(fù)實(shí)例話問題(也就是創(chuàng)建多個(gè)相似對(duì)象的問題)和解決了對(duì)象識(shí)別的問題。但是,像上面那樣,person1和person2共有的方法,實(shí)例化的時(shí)候都創(chuàng)建了,這未免多余了。當(dāng)然可以將共有的方法提取到外面,像這樣:

    function Person(name , age , job){this.name = name;this.age = age;this.job = job;this.sayName = sayName; } function sayName(){alert(this.name); } var person1 = new Person('nicholas' , 29 , 'software engineer'); var person2 = new Person('greg' , 27 , 'doctor');

    將sayName提取出來,就成了全局的方法了,然而這里只有Person類創(chuàng)建對(duì)象的時(shí)候才使用到,這樣就大才小用了吧,所以提取出來到全局方法這種操作不推薦。

    原型模式

    創(chuàng)建的每個(gè)函數(shù)都有一個(gè)prototype(原型)屬性,這個(gè)屬性就是一個(gè)指針,指向一個(gè)對(duì)象,而這個(gè)對(duì)象的用途就是包含可以由特定類型的所有實(shí)例共享的屬性和方法。

    function Person(){ } Person.prototype.name = 'nicholas'; Person.prototype.age = 29; Person.prototype.sayName = function(){alert(this.name); };var person1 = new Person(); person1.sayName(); // nicholasvar person2 = new Person(); person2.sayName(); // nicholasconsole.log(person1.sayName == person2.sayName); // true

    可以有關(guān)系圖如下:

    life/learn/read/javascript/prototype_object

    上面的Person.prototype不建議使用字面量來寫Person.prototype={},雖讓效果一樣,但是這里重寫了原本Person.prototype的對(duì)象,因此constructor屬性會(huì)指向Ohject而不是Person。當(dāng)然也是可以處理的啦,將指向指正確并指定'construtor'的枚舉屬性為enumerable: false。

    原型模式解決了函數(shù)共享的問題,但是也帶了一個(gè)問題:實(shí)例化中對(duì)象的屬性是獨(dú)立的,而原型模式這里共享了。

    組合使用構(gòu)造函數(shù)模式和原型模式

    創(chuàng)建自定義類型的最常見的方式,就是組合使用構(gòu)造函數(shù)模式和原型模式。構(gòu)造函數(shù)模式用于定義實(shí)例屬性,而原型模式用于定義方法和共享屬性。

    function Person(name , age ,job){this.name = name;this.age = age;this.job = job;this.friends = ['shelby' , 'court']; } Person.prototype.sayName = function(){alert(this.name); }var person1 = new Person('nicholas' , 29 , 'software engineer'); var person2 = new Person('greg' , 27 , 'doctor');person1.friends.push('van'); console.log(person1.friends); // 'shelby,court,van' console.log(person2.friends); // 'shelby,court' console.log(person1.friends === person2.friends); // false console.log(person1.sayName === person2.sayName); // true

    動(dòng)態(tài)原型模式

    其他的OO語言,比如java,創(chuàng)建對(duì)象的類中是包含了自身的屬性、方法和共有的屬性、方法,如下小狗的例子:

    public class Dog{int age;public Dog(String name ){this.age = age;System.out.println('小狗的名字是: ' + name);}public void setAge(int age){age = age;}public int getAge(){System.out.println('小狗的年齡為: ' + age);return age;}public static void main(String []args){/* 創(chuàng)建對(duì)象 */Dog dog = new Dog('tom');/* 通過方法來設(shè)定age */dog.setAge(2);/* 調(diào)用另外一個(gè)方法獲取age */dog.getAge();/* 也可以通過 對(duì)象.屬性名 獲取 */System.out.println('變量值: ' + dog.age);} }

    為了看起來是類那么一會(huì)事,動(dòng)態(tài)原型模式把所有信息都封裝在了構(gòu)造函數(shù)中,而通過在構(gòu)造函數(shù)中初始化原型(僅在必要的情況下),又保持了同時(shí)使用構(gòu)造函數(shù)和原型的優(yōu)點(diǎn)。如下:

    function Person(name , age ,job){// 屬性this.name = name;this.age = age;this.job = job;// 方法if(typeof this.sayName != 'function'){Person.prototype.sayName = function(){alert(this.name);}} } var friend = new Person('nicholas' , 29 , 'software engineer'); friend.sayName();

    寄生構(gòu)造函數(shù)模式

    在前面幾種模式都不適應(yīng)的情況下,可以用寄生構(gòu)造函數(shù)模式(數(shù)據(jù)結(jié)構(gòu)中就使用到哈),寄生構(gòu)造函數(shù)模式可以看成是工廠模式和構(gòu)造函數(shù)模式的結(jié)合體。其基本思想是創(chuàng)建一個(gè)函數(shù),該函數(shù)的作用僅僅是封裝創(chuàng)建對(duì)象的代碼,然后再返回新創(chuàng)建的對(duì)象。

    function Person(name , age , job){var o = new Object();o.name = name;o.age = age;o.job = job;o.sayName = function(){alert(this.name);}return o; }var friend = new Person('nicholas', 29 , 'software engineer'); friend.sayName(); // nicholas

    關(guān)于寄生構(gòu)造函數(shù)模式,需要說明:返回的對(duì)象與構(gòu)造函數(shù)或者與構(gòu)造函數(shù)的原型屬性直接沒有什么關(guān)系;也就是說,構(gòu)造函數(shù)返回的對(duì)象與構(gòu)造函數(shù)外部創(chuàng)建的對(duì)象沒有什么區(qū)別。為此,不能依賴instanceof操作符來確定對(duì)象類型。由于存在上面的問題,建議在可以使用其他模式的情況下,不要使用這種模式。

    穩(wěn)妥構(gòu)造函數(shù)模式

    穩(wěn)妥對(duì)象適合在一些安全的環(huán)境中(這些環(huán)境中會(huì)禁止使用this和new),或者防止數(shù)據(jù)被其他應(yīng)用程序(如Mashup程序)改動(dòng)時(shí)使用。穩(wěn)妥構(gòu)造函數(shù)遵循與寄生構(gòu)造函數(shù)類似的模式,但是有兩點(diǎn)不同:意識(shí)新創(chuàng)建對(duì)象的實(shí)例方法不引用this,二是不使用new操作符調(diào)用構(gòu)造函數(shù)。

    function Person(name , age , job){// 創(chuàng)建要返回的對(duì)象var o = new Object();// 可以在這里定義私有的變量和函數(shù)// 添加方法o.sayName = function(){alert(name); // 不使用this.name};// 返回對(duì)象return o; }var friend = Person('nicholas', 29 , 'software engineer'); // 不使用new friend.sayName(); // 'nicholas'

    繼承

    許多的OO語言都支持兩種繼承方法:接口繼承和實(shí)現(xiàn)繼承。接口繼承只繼承方法簽名,而實(shí)現(xiàn)繼承則繼承實(shí)際的方法。由于函數(shù)沒有簽名,在ECMAScript中無法實(shí)現(xiàn)接口繼承。ECMAScript只支持實(shí)現(xiàn)繼承,而且實(shí)現(xiàn)主要是依靠原型鏈來實(shí)現(xiàn)的。[p162]

    原型鏈

    原型鏈的基本思想是利用原型讓一個(gè)引用類型繼承另一個(gè)引用類型的屬性和方法。回顧下構(gòu)造函數(shù)、原型和實(shí)例的關(guān)系: 每個(gè)構(gòu)造函數(shù)都有一個(gè)原型對(duì)象,原型對(duì)象都包含一個(gè)指向構(gòu)造函數(shù)的指針,而實(shí)例都包含一個(gè)指向原型對(duì)象的內(nèi)部指針。

    function SuperType(){this.property = true; } SuperType.prototype.getSuperValue = function(){return this.property; } function SubType(){this.subProperty = false; }// 繼承了SuperType,重點(diǎn)哦 SubType.prototype = new SuperType();SubType.prototype.getSubValue = function(){return this.subProperty; }var instance = new SubType(); console.log(instance.getSuperValue()); // true

    上面代碼中原型鏈如下:

    life/learn/read/javascript/prototype_chain

    原型鏈繼承帶來兩個(gè)問題:一是原型實(shí)際上變成了另一個(gè)類型的實(shí)例,于是,原先的實(shí)例屬性也就變成了現(xiàn)在原型的屬性,共享了屬性。二是在創(chuàng)建子類型的實(shí)例時(shí),不能在沒有影響所有對(duì)象實(shí)例的情況下向超類型的構(gòu)造函數(shù)傳遞參數(shù)。

    借用構(gòu)造函數(shù)

    借用構(gòu)造函數(shù)解決原型鏈繼承帶來的不能向構(gòu)造函數(shù)傳遞倉鼠的問題。這里使用到了apply()或者call()方法在新創(chuàng)建的對(duì)象上執(zhí)行構(gòu)造函數(shù)。

    function SuperType(){this.colors = ['red','blue','green']; } function SubType(){// 繼承SuperTypeSuperType.call(this); // 使用SuperType.apply(this)同效 }var instance1 = new SubType(); instance1.color.push('black'); console.log(instance1.colors); // 'red,blue,green,black'var instance2 = new SubType(); console.log(instance2.colors); // 'red,blue,green'

    上面的例子中,我在父類型構(gòu)造函數(shù)中沒有傳參數(shù),看者感興趣的話可以自己加下參數(shù)來實(shí)驗(yàn)一番咯。

    借用構(gòu)造函數(shù)解決了原型鏈繼承的確定,但是又沒有接納原型鏈的優(yōu)點(diǎn):共享。下面的組合繼承結(jié)合了原型鏈和借用構(gòu)造函數(shù),容納了兩者的優(yōu)點(diǎn)。

    組合繼承

    組合繼承的思路是使用原型鏈實(shí)現(xiàn)對(duì)原型屬性和方法的繼承,而通過借用構(gòu)造函數(shù)來實(shí)現(xiàn)對(duì)實(shí)例屬性的繼承。

    function SuperType(name){this.name = name;this.colors = ['red','blue','green']; } SuperType.prototype.sayName = function(){console.log(this.name); } function SubType(name,age){// 繼承屬性SuperType.call(this,name);this.age = age; }// 繼承方法 SubType.prototype = new SuperType(); SubType.prototype.constructor =SubType; // 避免重寫構(gòu)造函數(shù)指向錯(cuò)誤 SubType.prototype.sayAge = function(){console.log(this.age); }var instance1 = new SubType('nicholas' , 29); instance1.colors.push('black'); console.log(instance1.colors); // 'red,blue,green,black' instance1.sayName(); // 'nicholas' instance1.sayAge(); // 29var instance2 = new SubType('greg' , 27); console.log(instance2.colors); // 'red,blue,green' instance2.sayName(); // 'greg' instance2.sayAge(); // 27

    組合繼承避免了原型鏈和借用構(gòu)造函數(shù)的缺陷,融合了它們的優(yōu)點(diǎn),成為了JavaScript中最常用的繼承模式。而且,instanceof和isPrototypeOf()也能夠用于識(shí)別基于組合繼承創(chuàng)建的對(duì)象。

    原型式繼承

    原型式繼承是借助原型可以基于已有的對(duì)象創(chuàng)建新對(duì)象,同時(shí)還不必因此創(chuàng)建自定義的類型。

    function object(o){ // 傳入一個(gè)對(duì)象function F(){};F.prototype = o;return new F(); }var person = {name : 'nicholas',friends: ['shelby','court','van'] };var anotherPerson = object(person); anotherPerson.name = 'greg'; anotherPerson.friends.push('rob');var yetAnotherPerson = object(person); yetAnotherPerson.name = 'linda'; yetAnotherPerson.friends.push('barbie');console.log(person.friends); // 'shelby,court,van,rob,barbie'

    寄生式繼承

    寄生式繼承是與原型繼承緊密相關(guān)的一種思路。寄生式繼承的思路與寄生構(gòu)造函數(shù)和工廠模式類似,即是創(chuàng)建了一個(gè)僅用于封裝繼承過程的函數(shù),該函數(shù)在內(nèi)部以某種方式來增強(qiáng)對(duì)象,最后再像真的做了所有工作一樣返回對(duì)象。

    function object(o){ // 傳入一個(gè)對(duì)象function F(){};F.prototype = o;return new F(); } function createAnother(original){var clone = object(original);clone.sayHi = function(){console.log('hi');};return clone; } var person = {name : 'nicholas',friends : ['shelby','court','van'] } var anotherPerson = createAnother(person); anotherPerson.sayHi(); // 'hi'

    上面的例子中,新對(duì)象anotherPerson不僅具有person的所有屬性和方法,而且還有了自己的sayHi()方法。

    寄生組合式繼承

    組合繼承是JavaScript最常用的繼承模式;不過,它也有自己的不足。組合繼承最大的問題就是無論什么情況下,都會(huì)調(diào)用兩次超類型構(gòu)造函數(shù):一次是在創(chuàng)建子類型原型的時(shí)候,另一次是在子類型構(gòu)造函數(shù)內(nèi)部。寄生組合式繼承能夠解決這個(gè)問題。

    所謂寄生組合式繼承,即通過借用構(gòu)造函數(shù)來繼承屬性,通過原型鏈的混成形式來繼承方法。其背后的基本思路是不必為了指定子類型的原型而調(diào)用超類型的構(gòu)造函數(shù),我們所需要的無非就是超類型的原型的一個(gè)副本而已。寄生組合式繼承的基本模式如下:

    function inheritPrototype(subType,superType){var prototype = Object(superType.prototype); // 創(chuàng)建對(duì)象prototype.constructor = subType; // 增強(qiáng)對(duì)象,防止下面重寫constructor屬性subType.prototype = prototype; // 指定對(duì)象}

    一個(gè)完整的例子如下,相關(guān)插圖見書[p173]:

    function inheritPrototype(subType,superType){var prototype = Object(superType.prototype);prototype.constructor = subType;subType.prototype = prototype;} function SuperType(name){this.name = name;this.color = ['red','blue','green']; } SuperType.prototype.sayName = function(){alert(this.name); } function SubType(name, age){SuperType.call(this,age); // 只在這調(diào)用了一次超類型的構(gòu)造函數(shù) }inheritPrototype(SubType , SuperType);SubType.prototype.sayAge = function(){console.log(this.age); }var instance = new SubType('nicholas' , 29);

    上面的例子的高效處體現(xiàn)在它只調(diào)用了一次SuperType構(gòu)造函數(shù),并且避免了在SubType.prototype上創(chuàng)建不必要的,多余的屬性。與此同時(shí),原型鏈還能保持不變;因此還能正常使用instanceof和inPrototypeOf()。開發(fā)人員普遍認(rèn)為寄生組合式繼承是引用類型最理想的繼承范式。

    閉包

    閉包是指有權(quán)訪問另一個(gè)函數(shù)作用域中的變量的函數(shù)。我的理解是,函數(shù)內(nèi)的函數(shù)使用到外層函數(shù)的變量延長(zhǎng)變量的生存時(shí)間,造成常駐內(nèi)存。例子見下:

    function foo(){var a = 2;return function(){a += 1;console.log(a);} }var baz = foo();baz(); // 3 baz(); // 4 baz(); // 5 baz(); // 6

    上面的例子中,外部的函數(shù)foo()執(zhí)行完成之后,正常的情況下應(yīng)該銷毀a變量的,但是內(nèi)部的返回的匿名函數(shù)使用到該變量,不能銷毀。如果需要銷毀的話,可以改寫成下面:

    function foo(){var a = 2;return function(){a += 1;console.log(a);} } var baz = foo(); baz(); // 3baz = null; // 將內(nèi)部的匿名函數(shù)賦值為空

    從閉包說起

    談到了閉包,這讓我想起了不久前刷知乎看到一篇文章。自己整理如下:

    for(var i = 0 ; i < 5; i++){setTimeout(function(){console.log(i);},1000) } console.log(i);// 5,5,5,5,5,5

    上面的代碼是輸出了6個(gè)5,而這6個(gè)5是這樣執(zhí)行的,先輸出全局中的console.log(i),然后是過了1秒種后,瞬間輸出了5個(gè)5(為什么用瞬間這個(gè)詞呢,怕看者理解為每過一秒輸出一個(gè)5)。解讀上面的代碼的話,可以通過狹義范圍(es5)的理解:同步 => 異步 => 回調(diào) (回調(diào)也是屬于異步的范疇,所以我這里指明了狹義啦)。先是執(zhí)行同步的for,遇到異步的setTimeout(setTimeout和setInterval屬于異步哈)后將其放入隊(duì)列中等待,接著往下執(zhí)行全局的console.log(i),將其執(zhí)行完成后執(zhí)行異步的隊(duì)列。

    追問1:閉包

    改寫上面的代碼,期望輸出的結(jié)果為:5 => 0,1,2,3,4。改造的方式一:

    for(var i = 0; i < 5; i++){(function(j){setTimeout(function(){console.log(j);},1000);})(i); } console.log(i);// 5,0,1,2,3,4

    上面的代碼巧妙的利用IIFE(Immediately Invoked Function Expression:聲明即執(zhí)行的函數(shù)表達(dá)式)來解決閉包造成的問題,閉包的解析看上面。

    方法二:利用js中基本類型的參數(shù)傳遞是按值傳遞的特征,改造代碼如下

    var output = function(i){setTimeout(function(){console.log(i);},1000); }; for(var i = 0; i < 5; i++){output(i); // 這里傳過去的i值被復(fù)制了 } console.log(i);// 5,0,1,2,3,4

    上面改造的兩個(gè)方法都是執(zhí)行代碼后先輸出5,然后過了一秒種依次輸出0,1,2,3,4。

    如果不要考慮全局中的console.log(i)輸出的5,而是循環(huán)中輸出的0,1,2,3,4。你還可以使用ES6的let塊級(jí)作用域語法,實(shí)現(xiàn)超級(jí)簡(jiǎn)單:

    for(let i = 0; i < 5; i++){setTimeout(function(){console.log(i);},1000); }// 0,1,2,3,4

    上面是過了一秒鐘后,依次輸出0,1,2,3,4。這種做法類似于無形中添加了閉包。那么,如果使用ES6語法的話,會(huì)怎樣實(shí)現(xiàn)5,0,1,2,3,4呢?

    追問2:ES6

    改造剛開始的代碼使得輸出的結(jié)果是每隔一秒輸出0,1,2,3,4,大概第五秒輸出5。

    在不使用ES6的情況下:

    for(var i = 0; i < 5; i++){(function(j){setTimeout(function(){console.log(j);},1000*j);})(i); } setTimeout(function(){console.log(i); },1000*i);// 0,1,2,3,4,5

    上面的代碼簡(jiǎn)單粗暴,但是不推薦。看題目是每隔一秒輸出一個(gè)值,再回調(diào)實(shí)現(xiàn)最后的5輸出,這個(gè)時(shí)候應(yīng)該使用ES6語法來考慮,應(yīng)該使用Promise方案:

    const tasks = []; for(var i = 0; i < 5; i++){// 這里的i聲明不能改成let,改成let的話請(qǐng)看下一段代碼((j)=>{tasks.push(new Promise((resolve)=>{ // 執(zhí)行taskssetTimeout(()=>{console.log(j);resolve(); // 這里一定要resolve,否則代碼不會(huì)按照預(yù)期執(zhí)行},1000*j);}))})(i); }Promise.all(tasks).then(()=>{ // 執(zhí)行完tasks,回調(diào)setTimeout(()=>{console.log(i);},1000); });// 符合要求的每隔一秒輸出 // 0,1,2,3,4,5

    如果是使用let,我的改造如下:

    const tasks = []; for (let i = 0; i < 5; i++) {tasks.push(new Promise((resolve) => {setTimeout(() => {console.log(i);resolve();}, 1000 * i);})); }Promise.all(tasks).then(() => {setTimeout(() => {console.log(tasks.length);}, 1000); });// 0,1,2,3,4,5

    上面的代碼比較龐雜,可以將其顆粒話,模塊化。對(duì)上面兩段代碼的帶var那段進(jìn)行改造后如下:

    const tasks = []; // 這里存放異步操作的Promise const output = (i) => new Promise((resolve) => {setTimeout(()=>{console.log(i);},1000*i); });// 生成全部的異步操作 for(var i = 0; i < 5; i++){tasks.push(output(i)); } // 異步操作完成之后,輸出最后的i Promise.all(tasks).then(() => {setTimeout(() => {console.log(i);},1000); });// 符合要求的每隔一秒輸出 // 0,1,2,3,4,5

    追問3:ES7

    既然ES6的Promise可以寫,那么ES7是否可以寫呢,從而讓代碼更加簡(jiǎn)潔易讀?那就使用到到了異步操作的async await特性啦。

    // 模擬其他語言中的sleep,實(shí)際上可以是任何異步操作 const sleep = (time) => new Promise((resolve) => {setTimeout(resolve , time); });(async () => {for(var i = 0; i < 5; i++){await sleep(1000);console.log(i);}await sleep(1000);console.log(i); })();// 符合要求的每隔一秒輸出 // 0,1,2,3,4,5

    瀏覽器窗口位置

    IE、Safari、Opera和Chrome都提供了screenLeft和screenTop屬性,分別表示瀏覽器窗口相對(duì)于屏幕左上角和上邊的位置[p197]。Firefox則以screenX和screenY屬性來表示。為了兼容各個(gè)瀏覽器,可以入下面這樣寫:

    var leftPos = (typeof window.screenLeft == "number")?window.screenLeft : window.screenX; var topPos = (typeof window.screenTop == "number")? window.screenTop : window.screenY;

    瀏覽器窗口大小

    由于瀏覽器廠商以及歷史的問題,無法確認(rèn)瀏覽器本身的大小,但是可以取得視口的大小[p198]。如下:

    var pageWidth = window.innerWidth,pageHeight = window.innerHeight;if(typeof pageWidth != "number"){if(document.compatMode == 'CSS1Compat'){ // 標(biāo)準(zhǔn)模式下的低版本iepageWidth = document.documentElement.clientWidth;pageHeight = document.documentElement.clientHeight;}else{ // 混雜模式下的chromepageWidth = document.body.clientWidth;pageHeight = document.body.clientHeight;} }

    上面的示例可以簡(jiǎn)寫成下面這樣:

    var pageWidth = window.innerWidth || document.documentElement.clientWidth || document.body.clientHeight; var pageHeight = window.innerHeight || document.documentElement.clientHeight || document.body.clientHeight;

    canvas中的變換

    為繪制上下文應(yīng)用變換,會(huì)導(dǎo)致使用不同的變換矩陣應(yīng)用處理,從而產(chǎn)生不同的結(jié)果。[p453]

    可通過下面的方法來修改變換矩陣:

    • rotation(angle):圍繞原點(diǎn)旋轉(zhuǎn)圖像angle弧度
    • scale(scaleX,scaleY)
    • translate(x,y): 將坐標(biāo)原點(diǎn)移動(dòng)到(x,y)。執(zhí)行這個(gè)變換后,坐標(biāo)(0,0)會(huì)變成之前由(x,y)表示的點(diǎn)。

    JSON

    關(guān)于JSON,最重要的是要理解它是一種數(shù)據(jù)格式,不是一種編程語言。

    對(duì)象字面量和JSON格式比較

    先來看下對(duì)象字面量demo寫法:

    var person = {name : "nicholas",age : 29 };# 上面的代碼也可以寫成下面的 var person = {"name" : "nicholas","age" : 29 };

    而上面的對(duì)象寫成數(shù)據(jù)的話,就是下面這樣了:

    {"name": "nicholas ","age": 29 }# 可到網(wǎng)站 https://www.bejson.com/ 驗(yàn)證

    ?? 與JavaScript對(duì)象字面量相比,JSON對(duì)象又兩個(gè)地方不一樣。首先,沒有聲明變量(JSON中沒有變量的概念)。其次,沒有分號(hào)(因?yàn)檫@不是JavaScript語句,所以不需要分號(hào))。留意的是,對(duì)象的屬性必須加雙引號(hào)(不是單引號(hào)哦),這在JSON中是必須的。

    stringify()和parse()

    可以這么理解:JSON.stringify()是從一個(gè)object中解析成JSON數(shù)據(jù)格式,而JSON.parse()是從一個(gè)字符串中解析成JSON數(shù)據(jù)格式。

    var person = {name: 'nicholas',age: 29 };var jsonText = JSON.stringify(person);console.log(jsonText);// {"name":"nicholas","age":29} var strPerson = '{"name":"nicholas","age":29}'; var jsonText = JSON.parse(strPerson);console.log(jsonText); // { name: 'nicholas', age: 29 }

    XMLHttpRequest對(duì)象

    XMLHttpRequest對(duì)象用于在后臺(tái)與服務(wù)器交換數(shù)據(jù)。它是Ajax技術(shù)的核心[p571]。

    XMLHttpRequest對(duì)象能夠使你:

    • 在不重新加載頁面的情況下更新網(wǎng)頁
    • 在頁面已加載后從服務(wù)器請(qǐng)求數(shù)據(jù)
    • 在頁面已加載后從服務(wù)器接收數(shù)據(jù)
    • 在后臺(tái)向服務(wù)器發(fā)送數(shù)據(jù)

    XMLHttpRequest的使用:

    # 創(chuàng)建XHR對(duì)象 => open()準(zhǔn)備發(fā)送 => send()傳送數(shù)據(jù)// 創(chuàng)建對(duì)象,對(duì)瀏覽器做兼容 function createXHR(){if(typeof XMLHttpRequest != 'undefined'){ // IE7+和其他瀏覽器支持return new XMLHttpRequest();}else if(typeof ActiveXObject != 'undefined'){if(typeof arguments.callee.activeXString != 'string'){var versions = ['MSXML2.XMLHttp.6.0','MSXML2.XMLHttp.3.0','MSXML2.XMLHttp']; // 低版的ie可能遇到三種不同版本的XMR對(duì)象var i , len;for(i = 0,len = versions.length; i < len ; i++){try{new ActiveXObject(version[i]);arguments.callee.activeXString = versions[i];break;}catch (ex){// 跳過}}}return new ActiveXObject(arguments.callee.activeXString);}else{throw new Error("No XHR object available.");} } var xhr = createXHR();// 準(zhǔn)備發(fā)送數(shù)據(jù) xhr.open("get","path/to/example.txt",false);// 非異步,異步的話第三個(gè)參數(shù)改為true// 傳送數(shù)據(jù) xhr.send(null); // get方法不需要傳數(shù)據(jù)// 判斷狀態(tài)嘛,獲取服務(wù)器返回的數(shù)據(jù) if((xhr.status >= 200 && xhr.status < 300) || xhr.status == 304){console.log(xhr.responseText); }else{console.log("Request was nsuccessful : " + xhr.status); }

    跨域解決方案

    何為跨域呢?只要訪問的資源的協(xié)議、域名、端口三個(gè)不全相同,就可以說是非同源策略而產(chǎn)生了跨域了,這是狹義的說法。廣義的說法:通過XHR實(shí)現(xiàn)Ajax通信的一個(gè)主要限制,來源于跨域的安全策略;默認(rèn)情況下,XHR對(duì)象只能訪問包含它的頁面位于同一個(gè)域中的資源[p582]。注:部分文字和代碼引用自前端常見跨域解決方案(全)

    CORS

    CORS(Cross-Origin Resource Sharing,跨資源共享)定義了在必須訪問跨資源時(shí),瀏覽器與服務(wù)器應(yīng)該如何溝通。其背后的基本思想,就是使用自定義的HTTP頭部讓瀏覽器與服務(wù)器進(jìn)行溝通,從而決定請(qǐng)求或響應(yīng)是應(yīng)該成功,還是應(yīng)該失敗。 復(fù)雜的跨域請(qǐng)求應(yīng)當(dāng)考慮使用它。

    普通跨域請(qǐng)求:只服務(wù)端設(shè)置Access-Control-Allow-Origin即可,前端無需設(shè)置,如果要帶cookie請(qǐng)求:前后端都要設(shè)置。

    1.前端設(shè)置

    1.) 原生ajax

    function createCORSRequest(method,url){ // 兼容處理,ie8/9需要用到window.XDomainRequestvar xhr = new XMLHttpRequest();// 前端設(shè)置是否帶cookiexhr.withCredentials = true;if("withCredentials" in xhr){ // 其他的用到withCredentialsxhr.open(method,url,true);}else if(typeof XDomainRequest != 'undefined'){xhr = new XDomainRequest();xhr.open(method , url);}else{xhr = null;}return xhr; }// get請(qǐng)求 var request = createCORSRequest("get","http://www.somewhere-else.com/page/"); if(request){request.onload = function(){// 對(duì)request.responseText 進(jìn)行處理 };request.send(); }// post請(qǐng)求,帶cookie var requestXhr = createCORSRequest("post","http://www.somewhere-else.com/page/"); requestXhr.setRequestHeader("Content-Type","application/x-www-form-urlencoded"); requestXhr.send("user=admin"); xhr.onreadystatechange = function() {if (xhr.readyState == 4 && xhr.status == 200) {alert(xhr.responseText);} };

    2.)jquery ajax

    上面寫了一大堆原生的,看得頭都有點(diǎn)大了,還是使用jquery ajax 比較舒服:

    $.ajax({...xhrFields: {withCredentials: true // 前端設(shè)置是否帶cookie},crossDomain: true, // 會(huì)讓請(qǐng)求頭中包含跨域的額外信息,但不會(huì)含cookie... });

    3.) vue框架

    在vue-resource封裝的ajax組建中加入以下代碼:

    Vue.http.options.credentials = true;

    2.服務(wù)器設(shè)置

    若后端設(shè)置成功,前端瀏覽器控制臺(tái)上就不會(huì)出現(xiàn)跨域報(bào)錯(cuò)的信息,反之,說明沒有成功。

    1.) java后臺(tái)

    /** 導(dǎo)入包:import javax.servlet.http.HttpServletResponse;* 接口參數(shù)中定義:HttpServletResponse response*/ response.setHeader("Access-Control-Allow-Origin", "http://www.domain1.com"); // 若有端口需寫全(協(xié)議+域名+端口) response.setHeader("Access-Control-Allow-Credentials", "true");

    2.) node后臺(tái)

    var http = require('http'); var server = http.createServer(); var qs = require('querystring');server.on('request', function(req, res) {var postData = '';// 數(shù)據(jù)塊接收中req.addListener('data', function(chunk) {postData += chunk;});// 數(shù)據(jù)接收完畢req.addListener('end', function() {postData = qs.parse(postData);// 跨域后臺(tái)設(shè)置res.writeHead(200, {'Access-Control-Allow-Credentials': 'true', // 后端允許發(fā)送Cookie'Access-Control-Allow-Origin': 'http://www.domain1.com', // 允許訪問的域(協(xié)議+域名+端口)'Set-Cookie': 'l=a123456;Path=/;Domain=www.domain2.com;HttpOnly' // HttpOnly:腳本無法讀取cookie});res.write(JSON.stringify(postData));res.end();}); });server.listen('8080'); console.log('Server is running at port 8080...');

    JSONP

    JSONP是JSON with padding(填充式JSON或參數(shù)式JSON)的簡(jiǎn)寫,是應(yīng)用JSON的一種新方法,在后來的web服務(wù)中非常流行。簡(jiǎn)單的跨域請(qǐng)求用JSONP即可。

    通常為了減輕web服務(wù)器的負(fù)載,我們把js,css,img等靜態(tài)資源分離到另一臺(tái)獨(dú)立域名的服務(wù)器,在html頁面中再通過相應(yīng)的標(biāo)簽從不同域名下加載靜態(tài)資源,而被瀏覽器允許,基于此原理,我們可以通過動(dòng)態(tài)創(chuàng)建script,再請(qǐng)求一個(gè)帶參網(wǎng)址實(shí)現(xiàn)跨域通信。

    1.前端實(shí)現(xiàn)

    1.)原生實(shí)現(xiàn)

    <script>var script = document.createElement('script');script.type = 'text/javascript';// 傳參并指定回調(diào)執(zhí)行函數(shù)為onBackscript.src = 'http://www.domain2.com:8080/login?user=admin&callback=onBack';document.head.appendChild(script);// 回調(diào)執(zhí)行函數(shù)function onBack(res){console.log(JSON.stringify(res));} </script>

    服務(wù)器返回如下(返回時(shí)即執(zhí)行全局函數(shù)):

    onBack({"status": true,"user":"admin"})

    2.)jquery ajax

    $.ajax({url: 'http://www.domain2.com:8080/login',type: 'get',dataType: 'jsonp', // 請(qǐng)求方式為jsonp jsonpCallback: 'onBack', // 自定義回調(diào)函數(shù)名data: {} });

    3.)vue.js

    this.$http.jsonp('http://www.domain2.com:8080/login',{params: {},jsonp: 'onBack ' }).then((res)=>{console.log(res); });

    2.后端nodejs代碼的示范:

    var qs = require('querystring'); var http = require('http'); var server = http.createServer();server.on('request',function(req,res){var params = qs.parse(req.url.split('?')[1]);var fn = params.callback;// jsonp返回設(shè)置res.writeHead(200,{"Content-Type":"text/javascript"});res.write(fn + '('+JSON.stringify(params)+')');res.end(); });server.listen('8080'); console.log('Server is running at port 8080 ...');

    ?? jsonp缺點(diǎn):只能實(shí)現(xiàn)get一種請(qǐng)求。

    WebSocket協(xié)議跨域

    WebSocket protocol 是 HTML5一種新的協(xié)議。它實(shí)現(xiàn)了瀏覽器與服務(wù)器全雙工通信,同時(shí)允許跨域通訊。

    原生的WebSocket API使用起來不太方便,示例中使用了socket.io,它很好的封裝了webSocket接口,提供了更簡(jiǎn)單、靈活的接口,也對(duì)不支持webSocket的瀏覽器提供了向下兼容。

    1.前端代碼

    <div>user input:<input type="text"></div> <script src="./socket.io.js"></script> <script> var socket = io('http://www.domain2.com:8080');// 連接成功處理 socket.on('connect', function() {// 監(jiān)聽服務(wù)端消息socket.on('message', function(msg) {console.log('data from server: ---> ' + msg); });// 監(jiān)聽服務(wù)端關(guān)閉socket.on('disconnect', function() { console.log('Server socket has closed.'); }); });document.getElementsByTagName('input')[0].onblur = function() {socket.send(this.value); }; </script>

    2.node socket后臺(tái)

    var http = require('http'); var socket = require('socket.io');// 啟http服務(wù) var server = http.createServer(function(req, res) {res.writeHead(200, {'Content-type': 'text/html'});res.end(); });server.listen('8080'); console.log('Server is running at port 8080...');// 監(jiān)聽socket連接 socket.listen(server).on('connection', function(client) {// 接收信息client.on('message', function(msg) {client.send('hello:' + msg);console.log('data from client: ---> ' + msg);});// 斷開處理client.on('disconnect', function() {console.log('Client socket has closed.'); }); });

    requestAnimationFrame()幀動(dòng)畫

    requestAnimationFrame 創(chuàng)建平滑的動(dòng)畫[p682]。在此之前都是使用setTimeout或者setInterval實(shí)現(xiàn),requestAnimationFrame與它們相比:

    • 不需要時(shí)間間隔,會(huì)貼切瀏覽器的刷新頻率
    • 在切換到另外的頁面時(shí),會(huì)停止運(yùn)行

    使用的示范如下:

    <div id="num">1</div> // 兼容瀏覽器 (function(){var lastTime = 0;var vendors = ['webkit','moz','ms','-o'];for(var x = 0;x <vendors.length && !window.requestAnimationFrame; ++x){window.requestAnimationFrame = window[vendors[x] + 'RequestAnimationFrame'];window.cancelAnimationFrame = window[vendors[x] + 'cancelAnimationFrame'] || window[vendors[x] + 'CancelRequestAnimationFrame'];}if(!window.requestAnimationFrame){window.requestAnimationFrame = function(callback){var currTime = new Date().getTime();var timeToCall = Math.max(0, 16 - (currTime - lastTime));var id = window.setTimeout(function(){callback;},timeToCall);lastTime = currTime - timeToCall;return id;}}if(!window.cancelAnimationFrame){window.cancelAnimationFrame = function (id){clearTimeout(id);}} })();// 簡(jiǎn)單的計(jì)數(shù) var num = 1,timer; fn(); function fn(){document.getElementById('num').innerText = ++num;timer = requestAnimationFrame(fn); } document.onclick = function(){cancelAnimationFrame(timer); }

    原文鏈接請(qǐng)戳這里

    總結(jié)

    以上是生活随笔為你收集整理的javascript 高级程序设计_重读《JavaScript高级程序设计》的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。