頁編程之表單詳解。
同學們好,今天我要跟大家分享的是html中前后臺數據交互的重要內容——form標簽。通過form標簽,用戶可以在前臺填寫資料,然后通過form的method屬性中設置的提交方式,提交至action屬性規定的后端處理程序中進行處理。
我們經常在網頁上看到的填寫、注冊、登錄、修改資料等操作都需要使用form標簽。讓我們來看看今天的實例效果吧。
form標簽對于用戶來說是不可見的,如果沒有這一句話,你們只能看到一個完全空白的網頁。
可以將form標簽想象成一個盒子,其中放置了輸入框、單選框、復選框等控件。當用戶填寫完所有控件的內容并點擊提交按鈕時,這個盒子就會被蓋上,并被直接發送到服務器進行處理。
接下來,我們來看看實現代碼。form標簽是一個圍堵標簽,它具有許多屬性,但并非所有屬性都是必需的。因此,根據需要設置相應的屬性即可。
首先,我要重點介紹幾個常用的屬性:action、name、method。
action屬性設置了服務端處理程序的URL地址。以郵寄盒子的理解來說,action就是收件人地址。
name屬性設置了表單的名稱,這個名稱可以被前臺的JS調用。同時,在一個網頁中存在多個表單標簽時,它也可以用來給服務器進行區分。
method屬性設置了表單的提交方式,它有兩個值post和get。post是密文,而get是明文,post沒有數據長度限制,而get只能傳遞大約3000個字符。
以郵寄舉例,get是直接將寫著內容的信紙寄出,而post則是將信紙裝進信封里寄出。enctype不是常用屬性,但在使用時非常重要。它規定了從表單數據發送到服務器之前如何對其進行編碼,共有三個值,分別對應文字數據、多媒體數據和文件數據。詳情請參考匯總文檔。值得一提的是,只有在method等于post時,enctype的值才會生效。
除了這三個屬性外,還有accept-charset、autocomplete和history。accept-charset聲明了服務端可以處理的字符集,autocomplete是否開啟前臺自動填充歷史數據及用戶填寫一次之后,再填寫會在瀏覽器中詢問是否根據過往填寫的內容進行自動填充。
·將logo的"novalidate"設置為"true",可以直接提交未經驗證的表單數據。通常,我們需要在前臺使用JS或控件驗證用戶填寫的數據是否符合格式要求。然而,這個屬性可以直接繞過驗證。
·將"target"設置為"_blank",可以打開新的窗口進行提交。這與a標簽的"target"屬性相同。現在,我的理解是這樣的。在后面的C#章節中,我們將結合具體的使用場景進行詳細的講解。
今天的分享就到這里。希望各位同學能按照我的要求,認真練習寫三次。這樣,即使不看視頻,你們也能寫出正確的代碼。所有的案例和相關文檔都可以向我索取。我們下期再見。網頁編程、服務端編程、數據庫和算法。如果你想學習編程,記得關注我哦!
好程序員web前端教程系列之JavaScript典型面試題及答案,JavaScript是web前端三要素之一,是互聯網上最流行的腳本語言。一個合格的web前端工程師一定要掌握JavaScript,而企業在招聘前端人員時也會考察其對JavaScript的掌握。接下來的好程序員web前端教程就給大家分享幾個常見的JavaScript面試題及答案。
1、JavaScript中 undefined 和 not defined 的區別
JavaScript未聲明變量直接使用會拋出異常:var name is not defined,如果沒有處理異常,代碼就停止運行了。但是,使用typeof undeclared_variable并不會產生異常,會直接返回 undefined。
var x; // 聲明 xconsole.log(x); //output: undefined
console.log(typeof y); //output: undefined
console.log(z); // 拋出異常: ReferenceError: z is not defined
2、在JavaScript中創建一個真正的private方法有什么缺點?
每一個對象都會創建一個private方法的方法,這樣很耗費內存
代碼示例:
var Employee = function (name, company, salary) {
this.name = name || "";
this.company = company || "";
this.salary = salary || 5000;
// Private method
var increaseSalary = function () {
this.salary = this.salary + 1000;
};
// Public method
this.dispalyIncreasedSalary = function() {
increaseSlary();
console.log(this.salary);
};
};
// Create Employee class object
var emp1 = new Employee("John","Pluto",3000);
// Create Employee class object
var emp2 = new Employee("Merry","Pluto",2000);
// Create Employee class object
var emp3 = new Employee("Ren","Pluto",2500);
在這里emp1、emp2、emp3都有一個increaseSalary私有方法的副本,所以除非必要,非常不推薦使用私有方法。
3、怎么判斷一個Object是否是數組(array)?
方法一
使用Object.prototype.toString來判斷是否是數組
function isArray(obj){
return Object.prototype.toString.call( obj ) === '[object Array]';
}
這里使用call來使toString中this指向obj,進而完成判斷。
方法二
使用原型鏈來完成判斷
function isArray(obj){
return obj.__proto__ === Array.prototype;
}
基本思想是利用實例,如果是某個構造函數構造出來的那么它的__proto__是指向構造函數的 prototype屬性。
方法三
利用JQuery
function isArray(obj){
return $.isArray(obj)
}
JQuery isArray的實現其實就是方法1。
4、下面代碼輸出什么?
var output = (function(x){
delete x;
return x;
})(0);
console.log(output);
輸出是0。delete操作符是將object的屬性刪去的操作。但是這里的x是并不是對象的屬性,delete操作符并不能作用。
5、如何理解JS中的this關鍵字?
JS初學者總是對this關鍵字感到困惑,因為與其他現代編程語言相比,JS中的這this關鍵字有點棘手。“this”一般是表示當前所在的對象,但是事情并沒有像它應該的那樣發生。JS中的this關鍵字由函數的調用者決定,誰調用就this就指向哪個。如果找不到調用者,this將指向windows對象。
6、如何在JavaScript中比較兩個對象?
對于兩個非原始值,比如兩個對象(包括函數和數組),== 和 === 比較都只是檢查它們的引用是否匹配,并不會檢查實際引用的內容。
例如,默認情況下,數組將被強制轉型成字符串,并使用逗號將數組的所有元素連接起來。所以,兩個具有相同內容的數組進行==比較時不會相等:
var a = [1,2,3];
var b = [1,2,3];
var c = "1,2,3";
a == c; // true
b == c; // true
a == b; // false
對于對象的深度比較,可以使用 deep-equal 這個庫,或者自己實現遞歸比較算法。
7、解釋原型設計模式
原型模式可用于創建新對象,但它創建的不是非初始化的對象,而是使用原型對象(或樣本對象)的值進行初始化的對象。原型模式也稱為屬性模式。
原型模式在初始化業務對象時非常有用,業務對象的值與數據庫中的默認值相匹配。原型對象中的默認值被復制到新創建的業務對象中。
經典的編程語言很少使用原型模式,但作為原型語言的 JavaScript 在構造新對象及其原型時使用了這個模式。
當然,以上只是JavaScript經典面試題的一小部分,更是web前端工程師面試的一小部分。如果你想成為一個高薪的web人才,不僅要熟練掌握扎實的理論知識,還要具備較多的項目實戰經驗。
avaScript 是一種功能強大的語言,是網絡的主要構建塊之一。這種強大的語言也有一些怪癖。例如,您是否知道 0 === -0 的計算結果為 true,或者 Number("") 的結果為 0?
問題是,有時這些怪癖會讓你摸不著頭腦,甚至質疑 Brendon Eich 發明 JavaScript 的那一天。好吧,重點不在于 JavaScript 是一種糟糕的編程語言,或者像它的批評者所說的那樣它是邪惡的。所有編程語言都有某種與之相關的奇怪之處,JavaScript 也不例外。
因此,在今天這篇文章中,我們將會看到一些重要的 JavaScript 面試問題的深入解釋。我的目標是徹底解釋這些面試問題,以便我們能夠理解基本概念,并希望在面試中解決其他類似問題。
1、仔細觀察 + 和 - 運算符
console.log(1 + '1' - 1);
您能猜出 JavaScript 的 + 和 - 運算符在上述情況下的行為嗎?
當 JavaScript 遇到 1 + '1' 時,它會使用 + 運算符處理表達式。+ 運算符的一個有趣的屬性是,當操作數之一是字符串時,它更喜歡字符串連接。在我們的例子中,“1”是一個字符串,因此 JavaScript 隱式地將數值 1 強制轉換為字符串。因此,1 + '1' 變為 '1' + '1',結果是字符串 '11'。
現在,我們的等式是 '11' - 1。- 運算符的行為恰恰相反。無論操作數的類型如何,它都會優先考慮數字減法。當操作數不是數字類型時,JavaScript 會執行隱式強制轉換,將其轉換為數字。在本例中,“11”被轉換為數值 11,并且表達式簡化為 11 - 1。
把它們放在一起:
'11' - 1 = 11 - 1 = 10
2、復制數組元素
考慮以下 JavaScript 代碼并嘗試查找此代碼中的任何問題:
function duplicate(array) {
for (var i = 0; i < array.length; i++) {
array.push(array[i]);
}
return array;
}
const arr = [1, 2, 3];
const newArr = duplicate(arr);
console.log(newArr);
在此代碼片段中,我們需要創建一個包含輸入數組的重復元素的新數組。初步檢查后,代碼似乎通過復制原始數組 arr 中的每個元素來創建一個新數組 newArr。然而,重復函數本身出現了一個關鍵問題。
重復函數使用循環來遍歷給定數組中的每個項目。但在循環內部,它使用 push() 方法在數組末尾添加一個新元素。這使得數組每次都變得更長,從而產生循環永遠不會停止的問題。循環條件 (i < array.length) 始終保持為 true,因為數組不斷變大。這使得循環永遠持續下去,導致程序卡住。
為了解決數組長度不斷增長導致無限循環的問題,可以在進入循環之前將數組的初始長度存儲在變量中。
然后,您可以使用該初始長度作為循環迭代的限制。這樣,循環將僅針對數組中的原始元素運行,并且不會因添加重復項而受到數組增長的影響。這是代碼的修改版本:
function duplicate(array) {
var initialLength = array.length; // Store the initial length
for (var i = 0; i < initialLength; i++) {
array.push(array[i]); // Push a duplicate of each element
}
return array;
}
const arr = [1, 2, 3];
const newArr = duplicate(arr);
console.log(newArr);
輸出將顯示數組末尾的重復元素,并且循環不會導致無限循環:
[1, 2, 3, 1, 2, 3]
3、原型和__proto__之間的區別
原型屬性是與 JavaScript 中的構造函數相關的屬性。構造函數用于在 JavaScript 中創建對象。定義構造函數時,還可以將屬性和方法附加到其原型屬性。
然后,從該構造函數創建的對象的所有實例都可以訪問這些屬性和方法。因此,prototype 屬性充當在實例之間共享的方法和屬性的公共存儲庫。
考慮以下代碼片段:
// Constructor function
function Person(name) {
this.name = name;
}
// Adding a method to the prototype
Person.prototype.sayHello = function() {
console.log(`Hello, my name is ${this.name}.`);
};
// Creating instances
const person1 = new Person("Haider Wain");
const person2 = new Person("Omer Asif");
// Calling the shared method
person1.sayHello(); // Output: Hello, my name is Haider Wain.
person2.sayHello(); // Output: Hello, my name is Omer Asif.
在此示例中,我們有一個名為 Person 的構造函數。通過使用 sayHello 之類的方法擴展 Person.prototype,我們將此方法添加到所有 Person 實例的原型鏈中。這允許 Person 的每個實例訪問和利用共享方法。而不是每個實例都有自己的方法副本。
另一方面, __proto__ 屬性(通常發音為“dunder proto”)存在于每個 JavaScript 對象中。在 JavaScript 中,除了原始類型之外,所有東西都可以被視為對象。這些對象中的每一個都有一個原型,用作對另一個對象的引用。__proto__ 屬性只是對此原型對象的引用。當原始對象不具備屬性和方法時,原型對象用作屬性和方法的后備源。默認情況下,當您創建對象時,其原型設置為 Object.prototype。
當您嘗試訪問對象的屬性或方法時,JavaScript 會遵循查找過程來查找它。這個過程涉及兩個主要步驟:
對象自己的屬性:JavaScript 首先檢查對象本身是否直接擁有所需的屬性或方法。如果在對象中找到該屬性,則直接訪問和使用它。
原型鏈查找:如果在對象本身中找不到該屬性,JavaScript 將查看該對象的原型(由 __proto__ 屬性引用)并在那里搜索該屬性。此過程在原型鏈上遞歸地繼續,直到找到屬性或查找到達 Object.prototype。
如果即使在 Object.prototype 中也找不到該屬性,JavaScript 將返回 undefined,表明該屬性不存在。
4、范圍
編寫 JavaScript 代碼時,理解作用域的概念很重要。范圍是指代碼不同部分中變量的可訪問性或可見性。在繼續該示例之前,如果您不熟悉提升以及 JavaScript 代碼的執行方式,可以從此鏈接了解它。這將幫助您更詳細地了解 JavaScript 代碼的工作原理。
讓我們仔細看看代碼片段:
function foo() {
console.log(a);
}
function bar() {
var a = 3;
foo();
}
var a = 5;
bar();
該代碼定義了 2 個函數 foo() 和 bar() 以及一個值為 5 的變量 a。所有這些聲明都發生在全局范圍內。在 bar() 函數內部,聲明了一個變量 a 并賦值為 3。那么當調用 thebar() 函數時,你認為它會打印 a 的值是多少?
當 JavaScript 引擎執行此代碼時,聲明全局變量 a 并為其賦值 5。然后,調用 bar() 函數。在 bar() 函數內部,聲明了一個局部變量 a 并賦值為 3。該局部變量 a 與全局變量 a 不同。之后,從 bar() 函數內部調用 foo() 函數。
在 foo() 函數內部,console.log(a) 語句嘗試記錄 a 的值。由于 foo() 函數的作用域內沒有定義局部變量 a,JavaScript 會查找作用域鏈以找到最近的名為 a 的變量。作用域鏈是指函數在嘗試查找和使用變量時可以訪問的所有不同作用域。
現在,我們來解決 JavaScript 將在哪里搜索變量 a 的問題。它會在 bar 函數的范圍內查找,還是會探索全局范圍?事實證明,JavaScript 將在全局范圍內進行搜索,而這種行為是由稱為詞法范圍的概念驅動的。
詞法作用域是指函數或變量在代碼中編寫時的作用域。當我們定義 foo 函數時,它被授予訪問其自己的本地作用域和全局作用域的權限。無論我們在哪里調用 foo 函數,無論是在 bar 函數內部還是將其導出到另一個模塊并在那里運行,這個特征都保持一致。詞法范圍不是由我們調用函數的位置決定的。
這樣做的結果是輸出始終相同:在全局范圍內找到的 a 值,在本例中為 5。
但是,如果我們在 bar 函數中定義了 foo 函數,則會出現不同的情況:
function bar() {
var a = 3;
function foo() {
console.log(a);
}
foo();
}
var a = 5;
bar();
在這種情況下, foo 的詞法作用域將包含三個不同的作用域:它自己的局部作用域、 bar 函數的作用域和全局作用域。詞法范圍由編譯時將代碼放置在源代碼中的位置決定。
當此代碼運行時,foo 位于 bar 函數內。這種安排改變了范圍動態。現在,當 foo 嘗試訪問變量 a 時,它將首先在其自己的本地范圍內進行搜索。由于它在那里找不到 a,因此它將搜索范圍擴大到 bar 函數的范圍。你瞧,a 存在,其值為 3。因此,控制臺語句將打印 3。
5、對象強制
const obj = {
valueOf: () => 42,
toString: () => 27
};
console.log(obj + '');
值得探索的一個有趣的方面是 JavaScript 如何處理對象到原始值(例如字符串、數字或布爾值)的轉換。這是一個有趣的問題,測試您是否知道強制轉換如何與對象一起使用。
在字符串連接或算術運算等場景中處理對象時,這種轉換至關重要。為了實現這一點,JavaScript 依賴于兩個特殊的方法:valueOf 和 toString。
valueOf 方法是 JavaScript 對象轉換機制的基本部分。當在需要原始值的上下文中使用對象時,JavaScript 首先在對象中查找 valueOf 方法。
如果 valueOf 方法不存在或未返回適當的原始值,JavaScript 將回退到 toString 方法。該方法負責提供對象的字符串表示形式。
回到我們原來的代碼片段:
const obj = {
valueOf: () => 42,
toString: () => 27
};
console.log(obj + '');
當我們運行此代碼時,對象 obj 被轉換為原始值。在本例中,valueOf 方法返回 42,然后,由于與空字符串連接而隱式轉換為字符串。因此,代碼的輸出將為 42。
但是,如果 valueOf 方法不存在或未返回適當的原始值,JavaScript 將回退到 toString 方法。讓我們修改一下之前的例子:
const obj = {
toString: () => 27
};
console.log(obj + '');
這里,我們刪除了 valueOf 方法,只留下 toString 方法,該方法返回數字 27。在這種情況下,JavaScript 將訴諸 toString 方法進行對象轉換。
6、理解對象鍵
在 JavaScript 中使用對象時,了解如何在其他對象的上下文中處理和分配鍵非常重要。考慮以下代碼片段并花一些時間猜測輸出:
let a = {};
let b = { key: 'test' };
let c = { key: 'test' };
a[b] = '123';
a[c] = '456';
console.log(a);
乍一看,這段代碼似乎應該生成一個具有兩個不同鍵值對的對象 a。然而,由于 JavaScript 對對象鍵的處理方式,結果完全不同。
JavaScript 使用默認的 toString() 方法將對象鍵轉換為字符串。但為什么?在 JavaScript 中,對象鍵始終是字符串(或符號),或者它們通過隱式強制轉換自動轉換為字符串。當您使用字符串以外的任何值(例如數字、對象或符號)作為對象中的鍵時,JavaScript 會在將該值用作鍵之前在內部將該值轉換為其字符串表示形式。
因此,當我們使用對象 b 和 c 作為對象 a 中的鍵時,兩者都會轉換為相同的字符串表示形式:[object Object]。由于這種行為,第二個賦值 a[b] = '123'; 將覆蓋第一個賦值 a[c] = '456';。
現在,讓我們逐步分解代碼:
兩個分配都使用相同的鍵字符串 [object Object]。結果,第二個賦值會覆蓋第一個賦值設置的值。
當我們記錄對象 a 時,我們觀察到以下輸出:
{ '[object Object]': '456' }
7、==運算符
console.log([] == ![]);
這個有點復雜。那么,您認為輸出會是什么?讓我們逐步評估一下。讓我們首先看看兩個操作數的類型:
typeof([]) // "object"
typeof(![]) // "boolean"
對于[]來說它是一個對象,這是可以理解的。JavaScript 中的一切都是對象,包括數組和函數。但是操作數![]如何具有布爾類型呢?讓我們試著理解這一點。當你使用 ! 對于原始值,會發生以下轉換:
假值:如果原始值是假值(例如 false、0、null、undefined、NaN 或空字符串 ''),則應用 ! 會將其轉換為 true。
真值:如果原始值是真值(任何非假值),則應用!會將其轉換為 false。
在我們的例子中,[] 是一個空數組,它是 JavaScript 中的真值。由于 [] 為真,所以 ![] 變為假。所以,我們的表達式就變成了:
[] == ![]
[] == false
現在讓我們繼續了解 == 運算符。每當使用 == 運算符比較 2 個值時,JavaScript 就會執行抽象相等比較算法。
該算法有以下步驟:
正如您所看到的,該算法考慮了比較值的類型并執行必要的轉換。
對于我們的例子,我們將 x 表示為 [],將 y 表示為 ![]。我們檢查了 x 和 y 的類型,發現 x 是對象,y 是布爾值。由于 y 是布爾值,x 是對象,因此應用抽象相等比較算法中的條件 7:
如果 Type(y) 為 Boolean,則返回 x == ToNumber(y) 的比較結果。
這意味著如果其中一種類型是布爾值,我們需要在比較之前將其轉換為數字。ToNumber(y) 的值是多少?正如我們所看到的,[] 是一個真值,否定則使其為假。結果,Number(false)為0。
[] == false
[] == Number(false)
[] == 0
現在我們有了比較 [] == 0,這次條件 8 開始發揮作用:
如果 Type(x) 是 String 或 Number 并且 Type(y) 是 Object,則返回比較結果 x == ToPrimitive(y)。
基于這個條件,如果其中一個操作數是對象,我們必須將其轉換為原始值。這就是 ToPrimitive 算法發揮作用的地方。我們需要將 [] x 轉換為原始值。數組是 JavaScript 中的對象。正如我們之前所看到的,當將對象轉換為基元時,valueOf 和 toString 方法就會發揮作用。
在這種情況下, valueOf 返回數組本身,它不是有效的原始值。因此,我們轉向 toString 進行輸出。將 toString 方法應用于空數組會得到一個空字符串,這是一個有效的原語:
[] == 0
[].toString() == 0
"" == 0
將空數組轉換為字符串會得到一個空字符串“”,現在我們面臨比較:“”== 0。
現在,其中一個操作數是字符串類型,另一個操作數是數字類型,則條件 5 成立:
如果 Type(x) 是 String 并且 Type(y) 是 Number,則返回比較結果 ToNumber(x) == y。
因此,我們需要將空字符串“”轉換為數字,即為 0。
"" == 0
ToNumber("") == 0
0 == 0
最后,兩個操作數具有相同的類型并且條件 1 成立。由于兩者具有相同的值,因此,最終輸出為:
0 == 0 // true
到目前為止,我們在探索的最后幾個問題中使用了強制轉換,這是掌握 JavaScript 和在面試中解決此類問題的重要概念,這些問題往往會被問到很多。
8、閉包
這是與閉包相關的最著名的面試問題之一:
const arr = [10, 12, 15, 21];
for (var i = 0; i < arr.length; i++) {
setTimeout(function() {
console.log('Index: ' + i + ', element: ' + arr[i]);
}, 3000);
}
如果您知道輸出,那就好了。那么,讓我們嘗試理解這個片段。從表面上看,這段代碼片段將為我們提供以下輸出:
Index: 0, element: 10
Index: 1, element: 12
Index: 2, element: 15
Index: 3, element: 21
但這里的情況并非如此。由于閉包的概念以及 JavaScript 處理變量作用域的方式,實際的輸出會有所不同。當延遲 3000 毫秒后執行 setTimeout 回調時,它們都將引用同一個變量 i,循環完成后該變量的最終值為 4。結果,代碼的輸出將是:
Index: 4, element: undefined
Index: 4, element: undefined
Index: 4, element: undefined
Index: 4, element: undefined
出現此行為的原因是 var 關鍵字沒有塊作用域,并且 setTimeout 回調捕獲對同一 i 變量的引用。當回調執行時,它們都會看到 i 的最終值,即 4,并嘗試訪問未定義的 arr[4]。
為了實現所需的輸出,您可以使用 let 關鍵字為循環的每次迭代創建一個新范圍,確保每個回調捕獲 i 的正確值:
const arr = [10, 12, 15, 21];
for (let i = 0; i < arr.length; i++) {
setTimeout(function() {
console.log('Index: ' + i + ', element: ' + arr[i]);
}, 3000);
}
通過此修改,您將獲得預期的輸出:
Index: 0, element: 10
Index: 1, element: 12
Index: 2, element: 15
Index: 3, element: 21
使用 let 在每次迭代中為 i 創建一個新的綁定,確保每個回調引用正確的值。
通常,開發人員已經熟悉涉及 let 關鍵字的解決方案。然而,面試有時會更進一步,挑戰你在不使用 let 的情況下解決問題。在這種情況下,另一種方法是通過立即調用循環內的函數(IIFE)來創建閉包。這樣,每個函數調用都有自己的 i 副本。您可以這樣做:
const arr = [10, 12, 15, 21];
for (var i = 0; i < arr.length; i++) {
(function(index) {
setTimeout(function() {
console.log('Index: ' + index + ', element: ' + arr[index]);
}, 3000);
})(i);
}
在此代碼中,立即調用的函數 (function(index) { ... })(i); 為每次迭代創建一個新范圍,捕獲 i 的當前值并將其作為索引參數傳遞。這確保每個回調函數都有自己單獨的索引值,防止與閉包相關的問題并為您提供預期的輸出:
Index: 0, element: 10
Index: 1, element: 12
Index: 2, element: 15
Index: 3, element: 21
最后總結
以上就是我今天這篇文章想與您分享的8個關于JS的前端面試題, 我希望這篇文章對您的面試準備之旅有所幫助。
*請認真填寫需求信息,我們會在24小時內與您取得聯系。