1.for in、Object.keys 和 Object.getOwnPropertyNames 對屬性遍歷有什么區別?
參考答案:
解析:
ECMAScript 將對象的屬性分為兩種:數據屬性和訪問器屬性。
var parent = Object.create(Object.prototype, {
a: {
value: 123,
writable: true,
enumerable: true,
configurable: true
}
});
// parent繼承自Object.prototype,有一個可枚舉的屬性a(enumerable:true)。
var child = Object.create(parent, {
b: {
value: 2,
writable: true,
enumerable: true,
configurable: true
},
c: {
value: 3,
writable: true,
enumerable: false,
configurable: true
}
});
//child 繼承自 parent ,b可枚舉,c不可枚舉
for (var key in child) {
console.log(key);
}
// b
// a
// for in 會遍歷自身及原型鏈上的可枚舉屬性
如果只想輸出自身的可枚舉屬性,可使用 hasOwnProperty 進行判斷(數組與對象都可以,此處用數組做例子)
let arr = [1, 2, 3];
Array.prototype.xxx = 1231235;
for (let i in arr) {
if (arr.hasOwnProperty(i)) {
console.log(arr[i]);
}
}
// 1
// 2
// 3
console.log(Object.keys(child));
// ["b"]
// Object.keys 會將對象自身的可枚舉屬性的key輸出
console.log(Object.getOwnPropertyNames(child));
// ["b","c"]
// 會將自身所有的屬性的key輸出
參與互動
文最初發布于 Dropbox 技術博客,經 Dropbox 授權由 InfoQ 中文站翻譯并分享。譯文經過了 Dropbox 團隊的審核和修訂。
2017 年 5 月,我首度加入 Dropbox 的時候,從 CoffeeScript 向 TypeScript 遷移的工作已經接近尾聲。彼時,需要對已有的 CoffeeScript 文件更改時,一般都會先將它轉換為 TypeScript。我們的部分代碼庫仍在使用 react-dom-factories,并且在 Redux 之前有一個自定義的 flux 實現。
那時我們的 Web 平臺團隊正全速向 TypeScript 遷移,但這一工作的規模或復雜性尚不為外人所知。如今 TypeScript 已成為 JavaScript 事實上的超集,我們的這段往事也是時候公之于眾了。故事主要發生在 2017 年,在今天依舊頗具參考價值。
我聯絡到該項目的首席工程師之一 David Goldstein 來撰寫本文。此外,還找到了另一位見證者,Web 平臺工程師 Samer Masterson 來補充細節。
將數十萬行 CoffeeScript 代碼遷移到 TypeScript 是一項龐大的工程,本文將涉及其中的方方面面。我們將介紹一開始為什么選擇了 TypeScript,如何規劃遷移工作,還有那些計劃外的各種細節。
遷移在 2017 年秋季結束。在此過程中我們開發了一些優秀的工具,并成為了首批大規模采用 TypeScript 的公司之一。——Matthew Gerstman
早在 2012 年,我們還是一家只有約 150 名員工的新興公司。當時瀏覽器中的最新技術是 jQuery 和 ES5。HTML5 還有兩年才會正式登臺,而 ES6 還要等三年。由于 JavaScript 技術似乎停滯不前,我們想要找到一種更先進的 Web 開發方法。
當時,CoffeeScript 非常流行。它支持箭頭函數,智能 this 綁定,甚至可選鏈,都比標準 JavaScript 領先數年。最后,我們的兩名工程師在 2012 年的“黑客周”中將整個 dropbox.com Web 應用程序從 JavaScript 遷移到了 CoffeeScript 上。彼時 dropbox 規模不大,所以遷移很容易。我們從 CoffeeScript 社區獲得了指導,并采納了他們的樣式建議,最終將 coffeelint 集成到了工作流程中。
在 CoffeeScript 中,花括號、圓括號,有時甚至逗號都是非必須的,是可有可無的選項。
例如,foo 12 與 foo(12) 是等同的。
多行數組可以不用逗號:
復制代碼
// CoffeeScript["foo""bar"] // JavaScript["foo", "bar"]
這種語法方法在那時很流行,我們甚至采納了社區的“可選的符號就不用寫”建議。
當時,代碼庫包含約 100,000 行 JavaScript。所有文件按預先指定的順序串聯在一起打包發布。盡管公司的許多工程師都可以看到這些代碼,但其中全職的 Web 工程師卻不到 10 位。
自然,這種方法無法很好地擴展;在 2013 年我們采用了 RequireJS 模塊系統,并開始編寫新代碼以符合“異步模塊定義”(簡稱 AMD)規范。我們確實考慮過 CommonJS,但那時 npm 和 Node 生態系統尚未成熟,因此我們選擇了專為在瀏覽器中使用而設計的工具。如果是幾年后再做同樣的決策,我們可能會改用 CommonJS。
一開始還好,但到了 2015 年底,產品工程師開始對 CoffeeScript 愈加不滿。ES6 于當年早些時候發布,覆蓋了 CoffeeScript 的那些最佳特性,與 CoffeeScript 相比,它具備更多優勢。它支持對象和數組解構、類語法和箭頭函數。結果一些團隊搶先一步,開始在自己的獨立項目中使用 ES6。
與此同時,CoffeeScript 代碼庫維護難度在加大。由于 CoffeeScript(和標準 JavaScript)都沒有類型,因此很容易在無意間破壞某些內容。防御式編程隨處可見,但卻使代碼難以理解。我們為 null 和 undefined 添加了額外的保護措施,還針對某種極端場景采用了特殊對策,無需 new 操作便可以安全構造一個函數。
復制代碼
class URI constructor: (x) -> # enable URI as a global function that returns a new URI instance unless @ instanceof URI return new URI(x) ...
此外,CoffeeScript 是一種基于空格的語言,即 tab 和空格具備不同的含義的,這與用 Python 構建 Dropbox 類似。然而,CoffeeScript 對標點卻過于寬容。通常,“可選的標點”實際上意味著“CoffeeScript 會將其編譯為意想不到的含義。”
舉個例子:在 2013 年的秋天,曾經遇到過一個關于標點符號的 bug,Python 無法編譯通過,CoffeeScript 將它進行了錯誤的編譯。雖然 Coffee Script 與 Python 的相似性可能有助于 Dropbox 的應用,但這些差異往往會出問題。一些更有經驗的開發人員選擇通過將 JavaScript 與 CoffeeScript 代碼并排打開來工作。
2015 年 11 月,對 Dropbox 的前端工程師進行了一項調查,發現只有 15%的受訪者認為應該繼續使用 CoffeeScript,而 62%的受訪者則認為應該放棄它:
開發人員經常抱怨:
基于開發人員的這些反饋,于是我們將目光轉向業界,決定試用 TypeScript 和標準 ES6。我們將它們都集成到了 dropbox.com 技術棧中以選出更適合的選項。我們也考慮過 Flow,但它不如 TypeScript 流行,相關支持也較少。最后我們決定,如果要用類型語言就用 TypeScript,這在 2015 年是不尋常的決策。
2016 年上半年,有一位工程師將 Babel 和 Type 腳本集成到我們的構建腳本中。我們現在可以在主網站試用兩種語言。經過生產測試,我們認為 TypeScript 實際上是帶有類型的 ES6。由于團隊偏愛類型,最終選擇了 TypeScript。
但是有一個小問題:那時我們的代碼庫已增長到 329,000 行 CoffeeScript;我們的工程團隊也大幅擴張,不再由單個團隊負責整個網站。所以我們的遷移速度不會像上次那么快了。
最初的計劃有 5 大里程碑:
2016 年下半年,M1、M2 和 M3 順利完成。我們成功構建了穩健的 Coffee/TypeScript 互操作程序。測試很簡單:重用現有的基于 Jasmine 的基礎架構來測試兩種語言(之后遷移到了 Jest,但這是另一個故事了)。我們整合了 TSLint 并編寫了樣式指南。
M4 和 M5 遇到了不少障礙,因為產品團隊需要將已有代碼移植到 TypeScript 上。我們希望各個團隊負責遷移各自開發的代碼,并決定給產品團隊留出一年中 20%的時間用于“基礎工作”,后文會詳細說明。
我們實現了 CoffeeScript 和 TypeScript 的互操作,如下所示:對于每個 CoffeeScript 文件,在類型文件夾中創建了一個相應的.d.ts 聲明文件。這些都是自動創建的,如:
復制代碼
declare module "foo/bar" { const exports: any; export = exports;}
也就是說所有內容都變成了 any 類型。重要模塊可以轉換為 TypeScript,或者逐步改變類型。對于流行的外部庫(如 jQuery 或 React),可以從 DefinitelyTyped 找出可用的類型。對于不太常見的庫,采用與默認存根相同的方法。
將所有 TypeScript 和 CoffeeScript 文件放在同一文件夾中,所以兩種語言的文件模塊 ID 都一樣。在學習 AMD import/export 與 TypeScript 的語法如何對應時我們遇到了些麻煩,還好問題不大。我們沒有使用 --esModuleInterop。
等效的 import 語句如下:
TypeScript(推薦)
復制代碼
import * as foo from "foo";
TypeScript(不推薦)
復制代碼
import foo = require("foo");
與 AMD JavaScript(或等效的 CoffeeScript)相同
復制代碼
define(["foo", ...], function(foo, ...) { ... }
將導出命名為 export const foo 類;可以導入模塊然后解構{foo},實現在 CoffeeScript 中讀取。這樣就和標準的 ES6 命名 import 建立了良好的語法關系。TypeScript 的 export default 導入到 AMD 模塊后,等效于對象{default: …},真是令人驚訝。
大多數模塊都可以用這些等效方法,但有些模塊會動態確定它們將導出的內容。我們從每個文件導出了所有可能的導出,如果沒有返回的話就改為 undefined。
之前
復制代碼
define([...], function(...) { ... if (foo) { return {bar}; } else { return {baz}; }})
之后
復制代碼
let foo, bar;if (foo) { bar = // define bar;} else { baz = // define baz;}// Export both regardless.export {bar, baz}
M2 階段代碼庫不再接收新的 CoffeeScript 文件。已有 CoffeeScript 的編輯不受影響,但多數工程師也因此開始學習 TypeScript 了。
一開始我們編寫了一個遍歷代碼庫的測試,找到所有.coffee 文件并將其路徑加入白名單。對此測試文件的任何更改都需要經過一位 Web 平臺工程師的審核。
同時我們采用了 Bazel 作為構建系統。在遷移到 Bazel 期間這一測試暫時失效了,為已有的 CoffeeScript 文件返回了一個空列表,還斷言該空列表是已有 CoffeeScript 文件白名單的子集。還好我們很快修復了這個問題,沒有造成嚴重影響。
我們在這里學到了一個教訓:如果測試中帶有任何假設,請試著確保它們能夠測試這些假設并在中斷時報錯。原始測試應該斷言 CoffeeScript 文件列表為非空,這樣一旦出錯時,就能立刻發現問題。
修復這個問題時,我們對白名單加入了嚴格的檢查,這樣文件刪除時也必須從白名單中移除,且不能重新引入(除非明確地重新添加文件)。這種方法之后用在了所有白名單相關工作上,既能讓不符合測試假設的問題快速暴露,又能避免人們無意間回退遷移工作。這里有一個小的缺陷:縮小白名單會阻斷代碼審核,但問題不大,我們會盡快(在一個工作日內)接受這些審核。
最初選擇要遷移的語言時,我們擔心的一個問題是:ES6 和 TypeScript 并沒有包括 CoffeeScript 的所有特性,比如說沒有? 和?. 運算符。
起初,我們以為會遺漏這些:但當采用了 TypeScript2.0 的 --strictNullChecks 后,這就不是問題了。可選鏈運算符主要用來處理 undefined 或 null 之類的不確定性,而 TypeScript 幫助我們消除了這種不確定性。
有趣的是,optional chaining 和 nulllish coallescing 最近都被重新添加到 vanilla Java 腳本中,并以類型腳本語言顯示,盡管有一些小的語法變化與原始 CoffeeScript 變量之間略有差異。
2016 年下半年,公司成立了一個并行團隊,用 React 重新設計和重構我們的網站。他們的目標是:到 2017 年第一季度末(時間接近最初的 M4 里程碑)發布新網站。該項目稱為“Maestro”,優先級比將他們負責的部分遷移到 TypeScript 的工作更高。此外其他一些團隊也會參與其中。
經過討價還價,Maestro 團隊最終承諾在第二季度完成遷移工作。前面他們就用 React 和 TypeScript 重寫了很多功能,剩下的文件則在第二季度遷移完畢。
遷移過程中用到“highly edited ”這個工具,強烈鼓勵社區轉換它們。可惜 100 個文件好像太多了,這個里程碑沒有按時交付。
這樣來看,刪除 CoffeeScript 編譯器的計劃也得推遲了。除了這 100 個熱門文件,后面還有 2000 多個雖然沒那么常用,但也時不時用得上的 CoffeeScript 老文件呢。
M5 里程碑在組織中引起了很多混亂,通常把它總結為“去除 CoffeeScript 編譯器”。
公司內卻出現了另一種解釋。許多人認為,雖然無法在截止日期之后編寫 CoffeeScript,但產品團隊可以編輯本應該只讀的代碼,甚至可以編輯 CoffeeScript,然后檢查新的編譯后的代碼。
可如果只 check in 已編譯的代碼,那么大部分代碼就不會有 i18n 與 linting 支持了;不想追加投資的話,應假設代碼沒變才能找回這些支持。
此外,從平臺的角度來看,這個里程碑意義不大。去除編譯器主要是為了有一個單語言的代碼庫,并讓注意力集中在 TypeScript 工具鏈上。
不知道“只讀 JavaScript”是否比保留為 CoffeeScript 文件更好,用 Bazel 重新實現構建系統的工作即將完成,并已對 CoffeeScript 和 TypeScript 編譯器都提供了支持。
因此在 6 月,TypeScript 的遷移工作被無限期推遲,完成時間沒有 ETA。
事后看來這一決定似乎是不可避免的。假設每個工程日(包括測試和代碼審查)大約要轉換 1000 行代碼,那么一位工程師要花一年的時間才能完成遷移。這個速度實際上是非常樂觀的,因為實際報告的進度每天大約是 100 行,指望一兩個月就完成根本做不到。
至于之前承諾的“20%的時間用于基礎工作”,我們也沒有達成共識。有的人知道這是用來滿足基礎架構需求的時間,有的人則認為這些時間可以用來償還自己的技術債。而且 20% 這個限制也形同虛設,沒人真的遵守它。
2017 年后,我們再做遷移時就不再開這種空頭支票了。
早在 2017 年 1 月,一些工程師就曾使用 decaffeinate 來簡化代碼轉換工作,甚至開始圍繞它構建一些工具來處理 AMD,并通過一些開源代碼來清理 React 樣式。
不幸的是,我們首次嘗試 decaffeinate 時出現了嚴重的故障。我們轉換了 i18n 庫,然后審查,測試并交付生產,結果發現 decaffeinate 誤轉換了未測試的,可識別語言環境的排序函數。只有一個頁面用了這個函數,但在 Safari 中這個頁面完全錯亂了。之后我們查看了 decaffeinate 的錯誤積壓,結果發現了幾十個類似問題。我們也不知道需要花多久才能真正信任 decaffeinate,所以當時沒打算用這種方法。
不過一些工程師還是決定使用它來手動轉換代碼,我們在文檔中將其記為一種可行的工作流程。基于 decaffeinate 的腳本通常會生成明顯無效的代碼,這沒什么大不了的,因為 TypeScript 在編譯時會報告它們。真正的問題是潛在的 bug,它們改變了代碼的語義,編譯器卻發現不了。
2017 年夏天,decaffeinate 聲明自己做到了無 bug。于是我們開始重新考慮這一選項,經過研究發現:
于是我們制定了新計劃:將剩余的遷移工作自動化。
現在對于 decaffeinate 無法提供類型的情況,可以添加為 any,直到 TypeScript 滿意為止。這種方法有以下優點:
遷移結束后,團隊可以按自己的進度修復代碼中的類型;無需再維護指向未轉換的 CoffeeScript 的聲明文件。
此時,產品團隊的空閑時間不多了,遷移得不到代碼所屬團隊的大量支持。而且要完成目標就要盡量減少引入的錯誤,有超過 2000 個文件要遷移,但錯誤超過一打就可能讓項目延遲或取消。這意味著我們必須在保持保持現有代碼語義的同時進行轉換。
需要針對所有文件創建一個多步流水線方法來完成遷移。
首先,運行 decaffeinate 以生成有效的 ES6。該代碼沒有類型,甚至包括了 pre-JSX React。然后我們用一個自制的 ES6 到 TypeScript 轉換器處理這段 ES6 代碼。
decaffeinate 有一些選項可以生成更漂亮的代碼,代價是降低代碼的正確率。這些選項以 --loose 開頭。最初包括以下選項:
這樣就無需用 Array.from() 包裝代碼的大部分內容。但嘗試并測試后,我們發現了很多足以讓我們對這些選項失去信心的錯誤——它們很可能引入了回歸。
而下面這些選項引發錯誤為數不多,因此最終使用了它們:
decaffeinate 會留下有關潛在樣式問題的注釋,例如,
復制代碼
/* * decaffeinate suggestions: * DS102: Remove unnecessary code created because of implicit returns * DS207: Consider shorter variations of null checks * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md */
此后,我們使用了幾個 codemod 來清理生成的代碼。首先,使用 JavaScript-codemod 轉換函數,例如 function() {}.bind(this) 轉換為箭頭函數:() => {}。接下來,對于導入了 React 的文件,使用 react-codemod 更新了舊的 React.createElement 調用,并將 React.createClass 的實例轉換為 class MyComponent extends React.Component。
這一過程生成了可運行的 Javascript,但仍使用 AMD 模塊格式。就算修復了這個問題,它也沒有使用我們的設置進行類型檢查。我們希望最終的 TypeScript 代碼使用與其余代碼相同的標志,尤其是 noImplicitAny 和 strictNullChecks。
我們必須編寫自己的自定義轉換才能進行類型檢查。
自制轉換器有很多工作要做:通過迭代便能解決影響文件的所有問題,為此需要編寫一種工具來自動處理以下問題。
為了開發這些工具,我們主要使用 https://astexplorer.net/ 來探索在構建原型轉換時將要使用的抽象語法樹。
首先,需要將 AMD import 更新為 ES6 import。
下面的代碼:
復制代碼
define(['library1', 'library2'], function(lib1, lib2) {})
會變成:
復制代碼
import * as lib1 from 'library1'; import * as lib2 from 'library2';
在 CoffeeScript 中,銷毀 import 是一種常見的模式,與 named import 關系很近。因此我們將:
復制代碼
define(['m1', 'm2'], function(M1, {somethingFromM2}) { var tmp = M1(somethingFromM2);});
轉換為:
復制代碼
import * as M1 from 'm1';import {somethingFromM2} from 'm2'; var tmp = M1(somethingFromM2);
對導出進行轉換。如下代碼:
復制代碼
define(function() { return {hello: 1}}
變為:
復制代碼
export {1 as hello}
當無法轉換為 named export 時,便回退到使用 export = 。例如:
復制代碼
define([], function() { let Something; return Something = (function() { Something = class Something { } return Something; })();});
變為:
復制代碼
let Something; Something = (function() { Something = class Something { } return Something; })(); export = Something;
對于未用到的導入,之后會再做清理,以避免某些模塊會產生全局副作用。因此我們改為將其轉換為 import “x”; 樣式,并注釋說這可能是沒必要的。
接下來,我們必須將每個函數參數和 var 聲明注解為 any 類型。例如,function(hello) {} 變為 function(hello: any) {} 。
我們還需要為在類內部分配給 this 的每個屬性添加一個類屬性。例如:
復制代碼
class Hello { constructor() { this.hi = 1; } someFunc() { this.sup = 1; }}
會轉換為:
復制代碼
class Hello { hi: any; sup: any; ...
另外,需要使用帶有類型的 React.Component 對 React 類組件進行注解。這些更改消除了許多 TypeScript 錯誤。
因為不想丟失任何給定文件的版本控制歷史,所以我們自動在每個文件的頂部添加了一條消息,說明如何查找原始 coffeescript 版本。
復制代碼
//// NOTE This file was converted from a Coffeescript file.// The original content is available through git with the command:// git show 21e537318b56:metaserver/static/js/legacy_js/widgets/bubble.coffee//
我們不想添加不必要的 any;但就算經過上述管道處理,仍然會遇到數千種類型錯誤。因此,轉換管道中的最后一步是一個腳本,其運行類型檢查,解析類型檢查輸出,然后根據每個錯誤代碼嘗試在關聯的 AST 節點上插入適當的 any 用法。
一開始,我們在腳本里使用了 node-falafel,但發現用它時需要解析 TypeScript,所以我們 fork 了 falafel,進而使用 tslint-eslint-parser 來替代它;這樣我們只需重寫需要更改的代碼即可。
我們的目標不是要做出最優秀的轉換工具,而是要轉換代碼庫。首先,從小的內部功能入手來測試工具,用它們來捕獲轉換工具中的崩潰以及讀取輸出時發現的明顯錯誤,當不再出現轉換崩潰之后,便開始在隨機的代碼庫子集中查看數據類型錯誤。這暴露出一些非常常見的問題,例如無效變量和復雜表達式中的類型錯誤,這些問題都不難解決:可以直接刪除無效變量,盡管在默認狀態下,保留它們的初始化器,以防表達式會產生其它副作用 - 將類似這樣的復雜表達式封裝成:(this as any).foo 。但是:這種方法變得越來越低效,所以后來我們開始改變策略。
當將整個代碼庫可靠地轉換為 TypeScript 后,便開始在整個代碼庫上試運行,并對結果進行類型檢查。我們將類型錯誤按代碼分組 (例如。“TS7030”),并統計了發生的情況。這樣就可以專心針對最常見的錯誤開發修復程序,避免浪費時間和精力了。
這是一個重大轉折點。在此之前,我們一直在不停地編寫修補程序,以修復我們決定手動測試的各個文件中不時出現的各種錯誤。即便這樣,我們還是不能確定能否得到一個成熟的工具。通過對每個錯誤代碼的出現情況進行分組和計數,我們能夠了解到還有多少工作要做,并且能夠集中精力處理發生了十幾次以上的類型錯誤。
對于那些發生頻次比較少或至少頻次少到不足以需要費力去通過工具修復的類型錯誤,我們計劃稍后再手動進行修復。有一個令人難忘的例子是,在我們更改策略之前我們發現的一個問題:ES6 類構造器在調用 super() 之前無法執行任何操作。在 CoffeeScript 類構造器中隨時調用 super() 都是合法的,因此當將它們轉換為 ES6 類時 TypeScript 會報錯。下面這種 CoffeeScript 代碼最容易出這種問題:
復制代碼
class Foo extends Bar constructor: (@bar, @baz) -> super()
decaffeinate 后變成:
復制代碼
class Foo extends Bar { constructor(bar, baz) { this.bar = bar; this.baz = baz; super(); // illegal: must come first }}
在幾乎每個這樣的實例中,在作業之前調用 super() 都是有效的,但是需要幾分鐘讀取超類構造器以對此進行檢查。我們發現的 super() 函數的誤調用只有一兩次真正存在問題, 這種情況對于自動更新代碼庫過程中發生的錯誤來說,錯誤次數不算太多(大約有 20 多次),所以手工對它們修復的難度不是太大。將容易修復的代碼單列出來,安全地進行重新排序,對于那些較為復雜的情況,需要人工反復檢查,不值得花時間重寫。
轉換完成時,我們的類型錯誤率約為:每個轉換的文件有 0.5–1 個類型檢查錯誤,需要手動修復。
在編寫工具的后期階段,我們更關注如何安全地部署轉換后的代碼。只對轉換后的代碼進行類型檢查是不夠的,特別是考慮到我們要自動添加很多 any。
因此,在代碼通過管道之前和之后,都會對代碼運行的所有單元測試。這樣就可以找出更多的錯誤,主要是隱藏的 CoffeeScript 錯誤代碼,轉換為 ts 后就會報錯。每當發現一個錯誤,都會在整個代碼庫中搜索類似的模式來修復它。這種辦法不行的時候,我們會在轉換工具中添加一個斷言,讓它們在遇到可疑代碼時迅速失效。
這個錯誤是意外覆蓋了導出的函數。
CoffeeScript 與大多數語言的不同之處在于:它沒有變量陰影的概念。例如在 Javascript 中,如果你運行:
復制代碼
let myVar = "top-level";function testMyVar() { let myVar = "shadowed"; console.log(myVar);} testMyVar();console.log(myVar);
它會打印出來:
復制代碼
shadowedtop-level
盡管它們共享相同的名稱,但在 testMyVar 中創建的 myVar 與頂級 myVar 是不同的。這在 CoffeeScript 中是不可能做到的。等效代碼如下所示:
復制代碼
myVar = "top-level"testMyVar -> myVar = "shadowed" console.log(myVar) testMyVar()console.log(myVar)
打印出來:
復制代碼
shadowedshadowed
在代碼中找到一個實例,如下所示:
復制代碼
define(() -> sortedEntries = (...) -> ... sortedEntries = entries.sortBy(getSortKey, cmpSortKey) ... return { sortedEntries }
sortedEntries 被聲明為一個函數,但其自身的函數主體被一個實體數組覆蓋。第一次調用該模塊后,對模塊內部 sortedEntries 的任何調用都將失敗;但由于 sortedEntries 函數導出的是副本,因此我們從未發現此問題。該代碼翻譯為:
復制代碼
let sortedEntries = function() { ... sortedEntries = entries.sortBy(getSortKey, cmpSortKey)} export { sortedEntries };
由于 TypeScript 代碼使用的是 ES6 模塊而不是 AMD 模塊,因此 sortedEntries 將作為引用而不是副本導出。這意味著當另一個模塊導入 sortedEntries 并調用它時,sortedEntries 成為了一個數組,隨后對其進行的任何調用均將無效。
遇到過一次這個錯誤后,我們在翻譯代碼中添加了一個 assert ,如果發現導出的函數被重新分配時就能解決問題。
在構建這些工具的過程中,我們意識到從 AMD 轉換為 ES6 模塊的副作用是:將有史以來第一次為絕大多數代碼啟用嚴格模式。
乍聽起來,這似乎很可怕;為此我們通讀了 嚴格模式的 MDN 文檔,并制作了可預期行為的更改清單,然后逐一瀏覽清單,并找出減輕它們影響的方法。
對于大多數更改,我們發現 TypeScript 解析器或類型檢查器就能處理了 -——TypeScript 會正常抱怨新的語法錯誤。有些更改則可以通過我們的代碼搜索工具輕松驗證。還有些更改則不是問題,因為 CoffeeScript 實際上在其代碼生成中并未使用有問題的結構。
關于 eval、.caller 和.callee 的更改:我們在代碼庫中很少使用 eval,在 CoffeeScript 中都沒有使用。并且我們沒有使用.caller 和.callee,因此不必擔心它們。
剩下的最后一類:只能通過運行代碼來驗證的更改。其中,與 eval 有關的更改是無關緊要的,而 arguments 很少用,很容易處理。這下需要擔心的行為更改只剩下 3 種:
1、給不可寫屬性、getter-only 屬性以及非擴展對象的屬性的分配時會報錯。向由 Object.freeze 凍結的對象寫入屬性是我們最有可能遇到的形式。
2、刪除不可刪除的屬性現在會報錯。
3、對 this 行為的更改——不再有 boxing,也不再有隱式 this=window 行為。
我們實際上無法提前知道這三個更改是否會帶來問題,但現在這份簡短的清單使我們更容易管理風險了。
還值得一提的是,代碼庫中最古老的部分是在引入 AMD 和 RequireJS 之前就以非模塊化代碼編寫的內容,其中我們最擔心的是非嚴格模式的行為可能是代碼正常運行所必需的。
我們發現可以將代碼轉換為 TypeScript,而無需將其轉換為 ES6 模塊。這樣一來便可以保持稀松模式。雖然這意味著我們在這部分代碼中基本上沒有跨模塊的類型檢查,但我們認為這是可以接受的折衷方案。
我們首先對 Jasmine 測試套件開始了大規模轉換(后來我們遷移到了 Jest),這樣一來,便可以確保以后的遷移不會同時更改測試和代碼,于是更有信心不引入靜默錯誤。轉換了 Jasmine 測試之后,我們開始尋找生產代碼中第一個轉換的候選者。
在 Dropbox,我們有一種在發布功能之前進行 bug 修復的文化:QA 和團隊的許多工程師會坐在一起,嘗試手動找出功能的 bug。與 QA 和許多團隊討論之后,我們決定首先轉換內部工具和共享鏈接頁面的評論 UI。
然后開始轉換內部崩潰報告、功能 gating 和電子郵件發送工具,接著開始大批量開始轉換其余面向用戶的代碼庫。
附帶說明:因為我們最近投資采用了 Bazel 作為構建工具,并且以此工具作為我們開發和集成測試框架的基礎,所以很容易確定一個 bug 是否是由更改引起的。由于我們使用 Bazel 和自己的 itest 工具提供服務,我們可以輕松查看之前的版本,并對其運行 itest。通過在代碼的確切版本上重建和啟動 dev 服務的副本,很容易看到錯誤是否是由更改引入的。Dropbox 工程師本杰明·彼得森(Benjamin Peterson)在 2017 年 Bazel 大會上發表的關于集成測試的演講中談到了 itest 是如何運行的。
從這里開始轉換內部崩潰報告、功能門控和電子郵件發送工具,然后開始批量轉換其余面向用戶的代碼庫。
編寫代碼轉換器時我們學到的一條經驗是:你必須嚴謹,涵蓋每個角落才行。明確指出哪些內容沒有覆蓋是非常重要的,因為錯過的任何場景都可能會出錯。如果要編寫自己的轉換工具,請參考以下提示:
在項目的最后幾周,我們一次轉換大約 100-200 個文件。通過改進工具,讓這種規模的轉換可以在幾個小時的工程時間內完成。這意味著可以在一兩天內就從零開始集成到主分支中,盡量降低重新部署的開銷。大部分時間都花在類型檢查和調整上了,因為在前期驗證工作中已經解決了 Jasmine 和 Selenium 測試的大多數問題。
我們的一個技巧是在代碼庫上運行 tsc --noEmit --watch 快速迭代,這樣就可以在大約 10 秒內獲得增量類型檢查結果。之所以能這么快,部分是因為在遷移過程中從 TypeScript 2.5 升級到了 2.6,后者大幅提升了 --watch 的速度。
為了保持專注,我們還在團隊區的白板上寫上了剩余的 CoffeeScript 文件的計數,并在每次將代碼合并到 master 分支時更新數據。
轉換完最后的 CoffeeScript 之后,我們與內部客戶一起暢飲咖啡,歡送 CoffeeScript。
我們一開始就知道,如果引發了太多錯誤,整個項目最后都會報銷。結果,我只記得有兩個錯誤進入了生產環境。大多數潛在錯誤是在手動修復類型檢查錯誤時引入的,盡管我們的測試覆蓋率不高,但它們并沒有闖過我們 Jasmine 和 Selenium 測試的考驗。
因此,大多數團隊除了意識到他們的代碼現在是 TypeScript 之外,并沒有感到有什么變化。雖然他們需要重做一些工作,但他們很滿意新的 TypeScript 環境,因此我們沒有收到太多抱怨。
我們最后才轉換那些最擔心出問題的團隊的代碼,這樣就能用之前零錯誤的表現說服他們了。但有一個團隊還是不放心,于是我們承諾說:即便出現了重大錯誤,我們也會 24 小時快速響應并修復(只要他們告訴我們如何重現),還會在一個工作日內解決次要錯誤。
之所以做出這一承諾,是因為我們對轉換腳本充滿信心。結果他們并沒有遇到重大錯誤,唯一一個小錯誤我們也是在異常報告中發現的,在他們第二天上班之前就解決掉了。
還有一些錯誤一開始他們說是我們的轉換造成的,但最后都被我們證明來自于其他原因。
最終,自動遷移過程僅花費了大約兩個月時間,有三名工程師參與,花費了大約 19 個工程師周。當然,遷移輸出的不是大多數人最初想要的理想的 TypeScript,而是一些雜亂無章,遍布 any 的 TypeScript。
這一代價是值得的。它讓我們更快地擺脫了 CoffeeScript,這樣就不用繼續支持 CoffeeScript,也不用讓新員工學習這種語言。可以在所有地方使用 TypeScript,同時逐步改進代碼樣式和類型安全。
在整個過程中我們吸取了很多技術教訓,其中可能最重要的教訓是:應該將政治和組織資源省下來,用在不能為所有人自動化的那些任務上。盡管沒有人特別喜歡 CoffeeScript,而且有些團隊可能已經自愿將代碼轉換為 TypeScript,但讓其他人在一年時間里手動轉換到 TypeScript 的要求太不切實際了。
事后看來,我們應該盡量自動化那些重復性的勞動,遇到無法自動化,真正需要專業編程知識的問題時才去動用寶貴的人力資源。
后記:快進到 2020 年,Dropbox 已經有了 200 萬行 TypeScript 代碼。我們的整個代碼庫都是靜態類型的,并且內部有一個繁榮的 TypeScript 社區。TypeScript 使我們能夠擴展工程組織,使各個團隊可以獨立工作,同時在整個代碼庫中保持清晰的聯系。
TypeScript 這種語言已迅速普及,我們很幸運能成為最早遷移的大公司之一。因此我們得以發展這一領域的專業知識并與外界分享。我們的 JS 公會定期分享 TypeScript 的技巧和竅門,我們的工程師喜歡他們使用的語言。一位工程師甚至撰寫了一份案例研究,總結 TypeScript 不是 JavaScript 嚴格超集的那些情況。
仍然有少數文件帶有“此文件從 coffeescript 遷移過來”的注釋,但這些文件僅占代碼庫的一小部分。我們現在的代碼有良好的類型,并且一般會 push back 那些 any。最近,我們將所有代碼庫都升級到了 TypeScript 3.8。——Matthew Gerstman
關注我并轉發此篇文章,私信我“領取資料”,即可免費獲得InfoQ價值4999元迷你書!
本文是關注微信小程序的開發和面試問題,
由基礎到困難循序漸進,
適合面試和開發小程序。
并有熱點框架(vue react node.js 全棧)前端資源以及后端視頻資源和源碼
并基于前端進階和面試的需求 總結了常用插件和js算法
以及html/css 和js熱點面試題
并總結了熱點React/ES6/Vue面試題
生命周期函數面試題
1.什么是 vue 生命周期
2.vue生命周期的作用是什么
3.第一次頁面加載會觸發哪幾個鉤子
4.簡述每個周期具體適合哪些場景
5.created和mounted的區別
6.vue獲取數據在哪個周期函數
7.請詳細說下你對vue生命周期的理解?
vue路由面試題
1.mvvm 框架是什么?
2.vue-router 是什么?它有哪些組件
3.active-class 是哪個組件的屬性?
4.怎么定義 vue-router 的動態路由? 怎么獲取傳過來的值
5.vue-router 有哪幾種導航鉤子?
6.$route 和 $router 的區別
7.vue-router響應路由參數的變化
8.vue-router傳參
9.vue-router的兩種模式
10.vue-router實現路由懶加載( 動態加載路由 )
vue常見面試題
1.vue優點
2.vue父組件向子組件傳遞數據?
3.子組件像父組件傳遞事件
4.v-show和v-if指令的共同點和不同點
5.如何讓CSS只在當前組件中起作用
6.<keep-alive></keep-alive>的作用是什么?
7.如何獲取dom
8.說出幾種vue當中的指令和它的用法?
9. vue-loader是什么?使用它的用途有哪些?
10.為什么使用key
11.axios及安裝
12.axios解決跨域
13.v-modal的使用
14.scss的安裝以及使用
15. 請說出vue.cli項目中src目錄每個文件夾和文件的用法?
16.分別簡述computed和watch的使用場景
17.v-on可以監聽多個方法嗎
18.$nextTick的使用
19.vue組件中data為什么必須是一個函數
20.vue事件對象的使用
21 組件間的通信
22.漸進式框架的理解
23.Vue中雙向數據綁定是如何實現的
24.單頁面應用和多頁面應用區別及優缺點
25.vue中過濾器有什么作用及詳解
26.v-if和v-for的優先級
27.assets和static的區別
28.列舉常用的指令
29.vue常用的修飾符
30.數組更新檢測
31.Vue.set視圖更新
32.自定義指令詳解
33.vue的兩個核心點
34.vue和jQuery的區別
35 引進組件的步驟
36.Vue-cli打包命令是什么?打包后悔導致路徑問題,應該在哪里修改
37.三大框架的對比
38. 跨組件雙向數據綁定
39.delete和Vue.delete刪除數組的區別
40.SPA首屏加載慢如何解決
41.Vue-router跳轉和location.href有什么區別
42. vue slot
43.你們vue項目是打包了一個js文件,一個css文件,還是有多個文件?
44.vue遇到的坑,如何解決的?
45.Vue里面router-link在電腦上有用,在安卓上沒反應怎么解決?
46.Vue2中注冊在router-link上事件無效解決方法
47.RouterLink在IE和Firefox中不起作用(路由不跳轉)的問題
48.axios的特點有哪些
49.請說下封裝 vue 組件的過程?
50.vue 各種組件通信方法(父子 子父 兄弟 爺孫 毫無關系的組件)
51.params和query的區別
52. vue mock數據
53 vue封裝通用組件
54.vue初始化頁面閃動問題
55.vue禁止彈窗后的屏幕滾動
56.vue更新數組時觸發視圖更新的方法
57.vue常用的UI組件庫
58. vue如何引進本地背景圖片
59. vue如何引進sass
60.vue修改打包后靜態資源路徑的修改
vuex常見面試題
1.vuex是什么?怎么使用?哪種功能場景使用它?
2.vuex有哪幾種屬性
3.不使用Vuex會帶來什么問題
4.Vue.js中ajax請求代碼應該寫在組件的methods中還是vuex的actions中?
5.vuex一個例子方法
6.Vuex中如何異步修改狀態
7.Vuex中actions和mutations的區別
vue項目實戰
1.頂部懸停效果
2.電話本列表效果( 右邊字母分類 上下滑動 旁邊字母顯示高亮)
3.vue做代理
4.Vue路由切換時的左滑和右滑效果示例
ES6新增方法面試題
1.let const var比較
2.反引號(`)標識
3.函數默認參數
4.箭頭函數
5.屬性簡寫
6.方法簡寫
7.Object.keys()方法,獲取對象的所有屬性名或方法名
8.Object.assign ()原對象的屬性和方法都合并到了目標對象
9.for...of 循環
10.import和export
11.Promise對象
12.解構賦值
13.set數據結構(可用于快速去重)
14.Spread Operator 展開運算符(...)
15.字符串新增方法
ES6數組面試題
1.forEach()
2.map()
3.filter()
4.reduce()
5.some()
6.every()
7.all()方法
ES6編程題
1.使用解構,實現兩個變量的值的交換
2.利用數組推導,計算出數組 [1,2,3,4] 每一個元素的平方并組成新的數組。
3.使用ES6改下面的模板
4.把以下代碼使用兩種方法,來依次輸出0到9?
react生命周期面試題
1.react 生命周期函數
2.react生命周期中,最適合與服務端進行數據交互的是哪個函數
3.運行階段生命周期調用順序
4.shouldComponentUpdate 是做什么的,(react 性能優化是哪個周期函數?)
5.指出(組件)生命周期方法的不同
react 基礎面試題
1.React 中 keys 的作用是什么?
2.React 中 refs 的作用是什么?
3.React 中有三種構建組件的方式
4.調用 setState 之后發生了什么?
5.react diff 原理(常考,大廠必考)
6.為什么建議傳遞給 setState 的參數是一個 callback 而不是一個對象
7.除了在構造函數中綁定 this,還有其它方式嗎
8.setState第二個參數的作用
9.(在構造函數中)調用 super(props) 的目的是什么
10.簡述 flux 思想
11.在 React 當中 Element 和 Component 有何區別?
12.描述事件在 React 中的處理方式。
13.createElement 和 cloneElement 有什么區別?
14.如何告訴 React 它應該編譯生產環境版本?
15.Controlled Component 與 Uncontrolled Component 之間的區別是什么?
react組件面試題
1.展示組件(Presentational component)和容器組件(Container component)之間有何不同
2.類組件(Class component)和函數式組件(Functional component)之間有何不同
3.(組件的)狀態(state)和屬性(props)之間有何不同
4.何為受控組件(controlled component)
5.何為高階組件(higher order component)
6.應該在 React 組件的何處發起 Ajax 請求
7.react中組件傳值
8.什么時候在功能組件( Class Component )上使用類組件( Functional Component )?
9.受控組件( controlled component )與不受控制的組件( uncontrolled component )有什么區別?
10.react 組件的劃分業務組件技術組件?
redux面試題
1.redux中間件
2.redux有什么缺點
3.了解 redux 么,說一下 redux 把
react性能比較面試題
1.vue和react的區別
2.react性能優化的方案
3.React 項目用過什么腳手架
4.介紹一下webpack webpack
5.如果你創建了類似于下面的 Twitter 元素,那么它相關的類定義是啥樣子的?
6.為什么我們需要使用 React 提供的 Children API 而不是 JavaScript 的 map?
1.簡述同步和異步的區別
2.怎么添加、移除、復制、創建、和查找節點
3.實現一個函數clone 可以對Javascript中的五種主要數據類型(Number、string、Object、Array、Boolean)進行復制
4.如何消除一個數組里面重復的元素
5.寫一個返回閉包的函數
6.使用遞歸完成1到100的累加
7.Javascript有哪幾種數據類型
8.如何判斷數據類型
9.console.log(1+'2')和console.log(1-'2')的打印結果
10.Js的事件委托是什么,原理是什么
11.如何改變函數內部的this指針的指向
12.列舉幾種解決跨域問題的方式,且說明原理
13.談談垃圾回收機制的方式及內存管理
14.寫一個function ,清除字符串前后的空格
15.js實現繼承的方法有哪些
16.判斷一個變量是否是數組,有哪些辦法
17.let ,const ,var 有什么區別
18.箭頭函數與普通函數有什么區別
19.隨機取1-10之間的整數
20.new操作符具體干了什么
21.Ajax原理
22.模塊化開發怎么做
23.異步加載Js的方式有哪些
24.xml和 json的區別
25.webpack如何實現打包的
26.常見web安全及防護原理
27.用過哪些設計模式
28.為什么要同源限制
29.offsetWidth/offsetHeight,clientWidth/clientHeight與scrollWidth/scrollHeight的區別
30.javascript有哪些方法定義對象
31.說說你對promise的了解
32.談談你對AMD、CMD的理解
33.web開發中會話跟蹤的方法有哪些
34.介紹js有哪些內置對象?
35.說幾條寫JavaScript的基本規范?
36.javascript創建對象的幾種方式?
37.eval是做什么的?
38.null,undefined 的區別?
39.[“1”, “2”, “3”].map(parseInt) 答案是多少?
40.javascript 代碼中的”use strict”;是什么意思 ? 使用它區別是什么?
41.js延遲加載的方式有哪些?
42.defer和async
43.說說嚴格模式的限制
44.attribute和property的區別是什么?
45.ECMAScript6 怎么寫class么,為什么會出現class這種東西?
46.常見兼容性問題
47.函數防抖節流的原理
48.原始類型有哪幾種?null是對象嗎?
49.為什么console.log(0.2+0.1==0.3) //false
50.說一下JS中類型轉換的規則?
51.深拷貝和淺拷貝的區別?如何實現
52.如何判斷this?箭頭函數的this是什么
53.== 和 ===的區別
54.什么是閉包
55.JavaScript原型,原型鏈 ? 有什么特點?
56.typeof()和instanceof()的用法區別
57.什么是變量提升
58.all、apply以及bind函數內部實現是怎么樣的
59.為什么會出現setTimeout倒計時誤差?如何減少
60.談談你對JS執行上下文棧和作用域鏈的理解
61.new的原理是什么?通過new的方式創建對象和通過字面量創建有什么區別?
62.prototype 和 proto 區別是什么?
63.使用ES5實現一個繼承?
64.取數組的最大值(ES5、ES6)
65.ES6新的特性有哪些?
66.promise 有幾種狀態, Promise 有什么優缺點 ?
67.Promise構造函數是同步還是異步執行,then呢 ?promise如何實現then處理 ?
68.Promise和setTimeout的區別 ?
69.如何實現 Promise.all ?
70.如何實現 Promise.finally ?
71.如何判斷img加載完成
72.如何阻止冒泡?
73.如何阻止默認事件?
74.ajax請求時,如何解釋json數據
75.json和jsonp的區別?
76.如何用原生js給一個按鈕綁定兩個onclick事件?
77.拖拽會用到哪些事件
78.document.write和innerHTML的區別
79.jQuery的事件委托方法bind 、live、delegate、on之間有什么區別?
80.瀏覽器是如何渲染頁面的?
81.$(document).ready()方法和window.onload有什么區別?
82. jquery中$.get()提交和$.post()提交有區別嗎?
83.對前端路由的理解?前后端路由的區別?
84.手寫一個類的繼承
85.XMLHttpRequest:XMLHttpRequest.readyState;狀態碼的意思
1.給一個連字符串例如:get-element-by-id轉化成駝峰形式。
2.匹配二進制數字
3.非零的十進制數字 (有至少一位數字, 但是不能以0開頭)
4.匹配一年中的12個月
5.匹配qq號最長為13為
6.匹配常見的固定電話號碼
7.匹配ip地址
8.匹配用尖括號括起來的以a開頭的字符串
9.分割數字每三個以一個逗號劃分
10.判斷字符串是否包含數字
11.判斷電話號碼
12.判斷是否符合指定格式
13.判斷是否符合USD格式
14.JS實現千位分隔符
15.獲取 url 參數
16.驗證郵箱
17.驗證身份證號碼
18.匹配漢字
19.去除首尾的'/'
20.判斷日期格式是否符合 '2017-05-11'的形式,簡單判斷,只判斷格式
21.判斷日期格式是否符合 '2017-05-11'的形式,嚴格判斷(比較復雜)
22.IPv4地址正則
23.十六進制顏色正則
24.車牌號正則
25.過濾HTML標簽
26.密碼強度正則,最少6位,包括至少1個大寫字母,1個小寫字母,1個數字,1個特殊字符
27.URL正則
28.匹配浮點數
1.什么是盒模型
2.行內元素有哪些?塊級元素有哪些? 空(void)元素有那些?行內元素和塊級元素有什么區別?
3.簡述src和href的區別
4.什么是css Hack
5.什么叫優雅降級和漸進增強
6.px和em的區別
7.HTML5 為什么只寫
8.Http的狀態碼有哪些
9.一次完整的HTTP事務是怎么一個過程
10.HTTPS是如何實現加密
11.瀏覽器是如何渲染頁面的
12.瀏覽器的內核有哪些?分別有什么代表的瀏覽器
13.頁面導入時,使用link和@import有什么區別
14.如何優化圖像,圖像格式的區別
15.列舉你了解Html5. Css3 新特性
16.可以通過哪些方法優化css3 animation渲染
17.列舉幾個前端性能方面的優化
18.如何實現同一個瀏覽器多個標簽頁之間的通信
19.瀏覽器的存儲技術有哪些
20.css定位方式
21.盡可能多的寫出瀏覽器兼容性問題
22.垂直上下居中的方法
23.響應式布局原理
25.清除浮動的方法
26.http協議和tcp協議
27.刷新頁面,js請求一般會有哪些地方有緩存處理
28.如何對網站的文件和資源進行優化
29.你對網頁標準和W3C重要性的理解
30.Http和https的區別
31.data-屬性的作用
32.如何讓Chrome瀏覽器顯示小于12px的文字
33.哪些操作會引起頁面回流(Reflow)
34.CSS預處理器的比較less sass
35.如何實現頁面每次打開時清除本頁緩存
36.什么是Virtual DOM,為何要用Virtual DOM
37.偽元素和偽類的區別
38.http的幾種請求方法和區別
39.前端需要注意哪些SEO
40.的title和alt有什么區別
41.從瀏覽器地址欄輸入url到顯示頁面的步驟
42.如何進行網站性能優化
43.語義化的理解
44.HTML5的離線儲存怎么使用,工作原理能不能解釋一下?
45.瀏覽器是怎么對HTML5的離線儲存資源進行管理和加載的呢
46.iframe有那些缺點?
47.WEB標準以及W3C標準是什么?
48.Doctype作用? 嚴格模式與混雜模式如何區分?它們有何意義?
49.HTML全局屬性(global attribute)有哪些
50.Canvas和SVG有什么區別?
51.如何在頁面上實現一個圓形的可點擊區域?
52.網頁驗證碼是干嘛的,是為了解決什么安全問題
53.請描述一下 cookies,sessionStorage 和 localStorage 的區別?
54. CSS選擇器有哪些?哪些屬性可以繼承?
55.CSS優先級算法如何計算?
56.CSS3有哪些新特性?
57.請解釋一下CSS3的flexbox(彈性盒布局模型),以及適用場景?
58.用純CSS創建一個三角形的原理是什么?
59.常見的兼容性問題?
60.為什么要初始化CSS樣式
61.absolute的containing block計算方式跟正常流有什么不同?
62.CSS里的visibility屬性有個collapse屬性值?在不同瀏覽器下以后什么區別?
63.display:none與visibility:hidden的區別?
64.position跟display、overflow、float這些特性相互疊加后會怎么樣?
65.對BFC規范(塊級格式化上下文:block formatting context)的理解?
66.為什么會出現浮動和什么時候需要清除浮動?清除浮動的方式?
67.上下margin重合的問題
68. 設置元素浮動后,該元素的display值是多少?
69.移動端的布局用過媒體查詢嗎?
70.CSS優化、提高性能的方法有哪些?
71.瀏覽器是怎樣解析CSS選擇器的?
72.在網頁中的應該使用奇數還是偶數的字體?為什么呢?
73.margin和padding分別適合什么場景使用?
74.元素豎向的百分比設定是相對于容器的高度嗎?
75.全屏滾動的原理是什么?用到了CSS的哪些屬性?
76.什么是響應式設計?響應式設計的基本原理是什么?如何兼容低版本的IE?
77. 視差滾動效果?
78.::before 和 :after中雙冒號和單冒號有什么區別?解釋一下這2個偽元素的作用
79.讓頁面里的字體變清晰,變細用CSS怎么做?
80. position:fixed;在android下無效怎么處理?
81.如果需要手動寫動畫,你認為最小時間間隔是多久,為什么?
82.li與li之間有看不見的空白間隔是什么原因引起的?有什么解決辦法?
83.display:inline-block 什么時候會顯示間隙?
84. 有一個高度自適應的div,里面有兩個div,一個高度100px,希望另一個填滿剩下的高度
85.png、jpg、gif 這些圖片格式解釋一下,分別什么時候用。有沒有了解過webp?
86.style標簽寫在body后與body前有什么區別?
87.CSS屬性overflow屬性定義溢出元素內容區的內容會如何處理?
88.闡述一下CSS Sprites
89. 一行或多行文本超出隱藏
?
初識小程序
1.注冊小程序
2.微信開發者工具
3.小程序與普通網頁開發的區別
4.小程序尺寸單位rpx
5.樣式導入(WeUI for)
6.選擇器
7.小程序image高度自適應及裁剪問題
8.微信小程序長按識別二維碼
9.給頁面加背景色
10.微信小程序獲取用戶信息
11.代碼審核和發布
12.小程序微信認證
13.小程序申請微信支付
14.小程序的目錄解構及四種文件類型
15.小程序文件的作用域
16.小程序常用組件
1.view
2.scroll-view
3.swiper組件
4.movable-view
5.cover-view
6.cover-image
小程序基礎
17.授權得到用戶信息
18.數據綁定
19.列表渲染
20.條件渲染
21.公共模板建立
22.事件及事件綁定
23.引用
24.頁面跳轉
1.wx.switchTab
2.wx.reLaunch
3.wx.redirectTo
4.wx.navigateTo
5.wx.navigateBack
25.設置tabBar
26.頁面生命周期
27.轉發分享
小程序高級
28.request請求后臺接口
29.http-promise 封裝
30.webview
31.獲取用戶收貨地址
32.獲取地里位置
33.自定義組件
34.微信小程序支付問題
小程序項目實戰
35.微信小程序本地數據緩存
36.下拉刷新和下拉加載
37.列表頁向詳情頁跳轉(動態修改title)
38.客服電話
39.星級評分組件
40.小程序插槽的使用slot
41.模糊查詢
42.wxs過濾
43.小程序動畫
44.列表根據索引值渲染
45.小程序動態修改class
46.小程序常用框架
47.參數傳值的方法
48.提高小程序的應用速度
49.微信小程序的優劣勢
50.小程序的雙向綁定和vue的區別
51.微信小程序給按鈕添加動畫
52.微信小程序的tab按鈕的轉換
53.微信小程序引進echarts
54.APP打開小程序流程
55.小程序解析富文本編輯器
小程序常見bug
1.域名必須是HTTPS
2. input組件placeholder字體顏色
3. wx.navigateTo無法跳轉到帶tabbar的頁面
4. tabbar在切換時頁面數據無法刷新
5.如何去掉自定義button灰色的圓角邊框
6.input textarea是APP的原生組件,z-index層級最高
7.一段文字如何換行
8.設置最外層標簽的margin-bottom在IOS下不生效
9.小程序中canvas的圖片不支持base64格式
10.回到頁面頂部
11.wx.setStorageSync和wx.getStorageSync報錯問題
12.如何獲取微信群名稱?
13.new Date跨平臺兼容性問題
14.wx.getSystemInfoSync獲取windowHeight不準確
15.圖片本地資源名稱,盡量使用小寫命名
1. 1px border問題
2.2X圖 3X圖適配
3.圖片在安卓上,有些設備模糊問題
4.固定定位布局 鍵盤擋住輸入框內容
5.click的300ms延遲問題和點擊穿透問題
6.phone及ipad下輸入框默認內陰影
7.防止手機中頁面放大和縮小
8.flex布局
9.px、em、rem、%、vw、vh、vm這些單位的區別
10. 移動端適配- dpr淺析
11.移動端擴展點擊區域
12 上下拉動滾動條時卡頓、慢
13 長時間按住頁面出現閃退
14. ios和android下觸摸元素時出現半透明灰色遮罩
15. active兼容處理 即 偽類:active失效
16.webkit mask兼容處理
17. pc端與移動端字體大小的問題
18. transiton閃屏
19.圓角bug
20.如何解決禁用表單后移動端樣式不統一問題?
輪播圖插件
二級城市插件
三級城市插件
文字滑動效果
手風琴效果
視頻播放插件
彈層插件
百度編輯器
ACE編輯器(輕巧)
上傳圖片(裁剪)
頁面加載效果
全選反選各種效果
京東樓層效果
懶加載
dedecms(文章累)
discuz(論壇)
ecshop(電商)
PHPEMS(考試)
*請認真填寫需求信息,我們會在24小時內與您取得聯系。