者|FullStack.Cafe
出處|前端之巔
根據 Stack Overflow 的 2018 年度調查,JavaScript 連續六年成為最常用的編程語言。所以我們必須面對這樣的現實,JavaScript 已經成為全棧開發技能的基石,在全棧開發面試中都會不可避免地涉及到與 JavaScript 有關的問題。FullStack.Cafe 匯編了最常見的 JavaScript 面試問題和答案,希望能夠幫助讀者找到下一份夢想中的工作。
Q1:JavaScript 中的強制轉型(coercion)是指什么?
難度:0
在 JavaScript 中,兩種不同的內置類型間的轉換被稱為強制轉型。強制轉型在 JavaScript 中有兩種形式:顯式和隱式。
這是一個顯式強制轉型的例子:
var a="42"; var b=Number( a ); a; // "42" b; // 42 -- 是個數字!
這是一個隱式強制轉型的例子:
var a="42"; var b=a * 1; // "42" 隱式轉型成 42 a; // "42" b; // 42 -- 是個數字!
Q2:JavaScript 中的作用域(scope)是指什么?
難度:?
在 JavaScript 中,每個函數都有自己的作用域。作用域基本上是變量以及如何通過名稱訪問這些變量的規則的集合。只有函數中的代碼才能訪問函數作用域內的變量。
同一個作用域中的變量名必須是唯一的。一個作用域可以嵌套在另一個作用域內。如果一個作用域嵌套在另一個作用域內,最內部作用域內的代碼可以訪問另一個作用域的變量。
Q3:解釋 JavaScript 中的相等性。
難度:?
JavaScript 中有嚴格比較和類型轉換比較:
var a="42"; var b=42; a==b; // true a===b; // false
一些簡單的規則:
Q4:解釋什么是回調函數,并提供一個簡單的例子。
難度:??
回調函數是可以作為參數傳遞給另一個函數的函數,并在某些操作完成后執行。下面是一個簡單的回調函數示例,這個函數在某些操作完成后打印消息到控制臺。
function modifyArray(arr, callback) { // 對 arr 做一些操作 arr.push(100); // 執行傳進來的 callback 函數 callback(); } var arr=[1, 2, 3, 4, 5]; modifyArray(arr, function() { console.log("array has been modified", arr); });
Q5:“use strict”的作用是什么?
難度:??
use strict 出現在 JavaScript 代碼的頂部或函數的頂部,可以幫助你寫出更安全的 JavaScript 代碼。如果你錯誤地創建了全局變量,它會通過拋出錯誤的方式來警告你。例如,以下程序將拋出錯誤:
function doSomething(val) { "use strict"; x=val + 10; }
它會拋出一個錯誤,因為 x 沒有被定義,并使用了全局作用域中的某個值對其進行賦值,而 use strict 不允許這樣做。下面的小改動修復了這個錯誤:
function doSomething(val) { "use strict"; var x=val + 10; }
Q6:解釋 JavaScript 中的 null 和 undefined。
難度:??
JavaScript 中有兩種底層類型:null 和 undefined。它們代表了不同的含義:
Q7:編寫一個可以執行如下操作的函數。
難度:??
var addSix=createBase(6); addSix(10); // 返回 16 addSix(21); // 返回 27
可以創建一個閉包來存放傳遞給函數 createBase 的值。被返回的內部函數是在外部函數中創建的,內部函數就成了一個閉包,它可以訪問外部函數中的變量,在本例中是變量 baseNumber。
function createBase(baseNumber) { return function(N) { // 我們在這里訪問 baseNumber,即使它是在這個函數之外聲明的。 // JavaScript 中的閉包允許我們這么做。 return baseNumber + N; } } var addSix=createBase(6); addSix(10); addSix(21);
Q8:解釋 JavaScript 中的值和類型。
難度:??
JavaScript 有類型值,但沒有類型變量。JavaScript 提供了以下幾種內置類型:
Q9:解釋事件冒泡以及如何阻止它?
難度:??
事件冒泡是指嵌套最深的元素觸發一個事件,然后這個事件順著嵌套順序在父元素上觸發。
防止事件冒泡的一種方法是使用 event.cancelBubble 或 event.stopPropagation()(低于 IE 9)。
Q10:JavaScript 中的 let 關鍵字有什么用?
難度:??
除了可以在函數級別聲明變量之外,ES6 還允許你使用 let 關鍵字在代碼塊({..})中聲明變量。
Q11:如何檢查一個數字是否為整數?
難度:??
檢查一個數字是小數還是整數,可以使用一種非常簡單的方法,就是將它對 1 進行取模,看看是否有余數。
function isInt(num) { return num % 1===0; } console.log(isInt(4)); // true console.log(isInt(12.2)); // false console.log(isInt(0.3)); // false
Q12:什么是 IIFE(立即調用函數表達式)?
難度:???
它是立即調用函數表達式(Immediately-Invoked Function Expression),簡稱 IIFE。函數被創建后立即被執行:
(function IIFE(){ console.log( "Hello!" ); })(); // "Hello!"
在避免污染全局命名空間時經常使用這種模式,因為 IIFE(與任何其他正常函數一樣)內部的所有變量在其作用域之外都是不可見的。
Q13:如何在 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 這個庫,或者自己實現遞歸比較算法。
Q14:你能解釋一下 ES5 和 ES6 之間的區別嗎?
難度:???
以下是 ES5 和 ES6 之間的一些主要區別:
const greetings=(name)=> { return `hello ${name}`; } const greetings=name=> `hello ${name}`;
常量在很多方面與其他語言中的常量一樣,但有一些需要注意的地方。常量表示對值的“固定引用”。因此,在使用常量時,你實際上可以改變變量所引用的對象的屬性,但無法改變引用本身。
const NAMES=[]; NAMES.push("Jim"); console.log(NAMES.length===1); // true NAMES=["Steve", "John"]; // error
新的 ES6 關鍵字 let 允許開發人員聲明塊級別作用域的變量。let 不像 var 那樣可以進行提升。
默認參數允許我們使用默認值初始化函數。如果省略或未定義參數,則使用默認值,也就是說 null 是有效值。
// 基本語法 function multiply (a, b=2) { return a * b; } multiply(5); // 10
ES6 引入了對類(關鍵字 class)、構造函數(關鍵字 constructor)和用于繼承的 extend 關鍵字的支持。
for…of 語句將創建一個遍歷可迭代對象的循環。
const obj1={ a: 1, b: 2 } const obj2={ a: 2, c: 3, d: 4} const obj3={...obj1, ...obj2}
promise 提供了一種機制來處理異步操作結果。你可以使用回調來達到同樣的目的,但是 promise 通過方法鏈接和簡潔的錯誤處理帶來了更高的可讀性。
const isGreater=(a, b)=> { return new Promise ((resolve, reject)=> { if(a > b) { resolve(true) } else { reject(false) } }) } isGreater(1, 2) .then(result=> { console.log('greater') }) .catch(result=> { console.log('smaller') })
const myModule={ x: 1, y: ()=> { console.log('This is ES5') }} export default myModule; import myModule from './myModule';
問題 15:解釋 JavaScript 中“undefined”和“not defined”之間的區別。
難度:???
在 JavaScript 中,如果你試圖使用一個不存在且尚未聲明的變量,JavaScript 將拋出錯誤“var name is not defined”,讓后腳本將停止運行。但如果你使用 typeof undeclared_variable,它將返回 undefined。
在進一步討論之前,先讓我們理解聲明和定義之間的區別。
“var x”表示一個聲明,因為你沒有定義它的值是什么,你只是聲明它的存在。
var x; // 聲明 x console.log(x); // 輸出: undefined
“var x=1”既是聲明又是定義(我們也可以說它是初始化),x 變量的聲明和賦值相繼發生。在 JavaScript 中,每個變量聲明和函數聲明都被帶到了當前作用域的頂部,然后進行賦值,這個過程被稱為提升(hoisting)。
當我們試圖訪問一個被聲明但未被定義的變量時,會出現 undefined 錯誤。
var x; // 聲明 if(typeof x==='undefined') // 將返回 true
當我們試圖引用一個既未聲明也未定義的變量時,將會出現 not defined 錯誤。
console.log(y); // 輸出: ReferenceError: y is not defined
Q16:匿名和命名函數有什么區別?
難度:???
var foo=function() { // 賦給變量 foo 的匿名函數 // .. }; var x=function bar(){ // 賦給變量 x 的命名函數 bar // .. }; foo(); // 實際執行函數 x();
Q17:Javascript 中的“閉包”是什么?舉個例子?
難度:????
閉包是在另一個函數(稱為父函數)中定義的函數,并且可以訪問在父函數作用域中聲明和定義的變量。
閉包可以訪問三個作用域中的變量:
var globalVar="abc"; // 自調用函數 (function outerFunction (outerArg) { // outerFunction 作用域開始 // 在 outerFunction 函數作用域中聲明的變量 var outerFuncVar='x'; // 閉包自調用函數 (function innerFunction (innerArg) { // innerFunction 作用域開始 // 在 innerFunction 函數作用域中聲明的變量 var innerFuncVar="y"; console.log( "outerArg=" + outerArg + "\n" + "outerFuncVar=" + outerFuncVar + "\n" + "innerArg=" + innerArg + "\n" + "innerFuncVar=" + innerFuncVar + "\n" + "globalVar=" + globalVar); // innerFunction 作用域結束 })(5); // 將 5 作為參數 // outerFunction 作用域結束 })(7); // 將 7 作為參數
innerFunction 是在 outerFunction 中定義的閉包,可以訪問在 outerFunction 作用域內聲明和定義的所有變量。除此之外,閉包還可以訪問在全局命名空間中聲明的變量。
上述代碼的輸出將是:
outerArg=7 outerFuncVar=x innerArg=5 innerFuncVar=y globalVar=abc
Q18:如何在 JavaScript 中創建私有變量?
難度:????
要在 JavaScript 中創建無法被修改的私有變量,你需要將其創建為函數中的局部變量。即使這個函數被調用,也無法在函數之外訪問這個變量。例如:
function func() { var priv="secret code"; } console.log(priv); // throws error
要訪問這個變量,需要創建一個返回私有變量的輔助函數。
function func() { var priv="secret code"; return function() { return priv; } } var getPriv=func(); console.log(getPriv()); //=> secret code
Q19:請解釋原型設計模式。
難度:????
原型模式可用于創建新對象,但它創建的不是非初始化的對象,而是使用原型對象(或樣本對象)的值進行初始化的對象。原型模式也稱為屬性模式。
原型模式在初始化業務對象時非常有用,業務對象的值與數據庫中的默認值相匹配。原型對象中的默認值被復制到新創建的業務對象中。
經典的編程語言很少使用原型模式,但作為原型語言的 JavaScript 在構造新對象及其原型時使用了這個模式。
Q20:判斷一個給定的字符串是否是同構的。
難度:????
如果兩個字符串是同構的,那么字符串 A 中所有出現的字符都可以用另一個字符替換,以便獲得字符串 B,而且必須保留字符的順序。字符串 A 中的每個字符必須與字符串 B 的每個字符一對一對應。
isIsomorphic("egg", 'add'); // true isIsomorphic("paper", 'title'); // true isIsomorphic("kick", 'side'); // false function isIsomorphic(firstString, secondString) { // 檢查長度是否相等,如果不相等, 它們不可能是同構的 if (firstString.length !==secondString.length) return false var letterMap={}; for (var i=0; i < firstString.length; i++) { var letterA=firstString[i], letterB=secondString[i]; // 如果 letterA 不存在, 創建一個 map,并將 letterB 賦值給它 if (letterMap[letterA]===undefined) { letterMap[letterA]=letterB; } else if (letterMap[letterA] !==letterB) { // 如果 letterA 在 map 中已存在, 但不是與 letterB 對應, // 那么這意味著 letterA 與多個字符相對應。 return false; } } // 迭代完畢,如果滿足條件,那么返回 true。 // 它們是同構的。 return true; }
Q21:“Transpiling”是什么意思?
難度:????
對于語言中新加入的語法,無法進行 polyfill。因此,更好的辦法是使用一種工具,可以將較新代碼轉換為較舊的等效代碼。這個過程通常稱為轉換(transpiling),就是 transforming + compiling 的意思。
通常,你會將轉換器(transpiler)加入到構建過程中,類似于 linter 或 minifier。現在有很多很棒的轉換器可選擇:
Q22:“this”關鍵字的原理是什么?請提供一些代碼示例。
難度:????
在 JavaScript 中,this 是指正在執行的函數的“所有者”,或者更確切地說,指將當前函數作為方法的對象。
function foo() { console.log( this.bar ); } var bar="global"; var obj1={ bar: "obj1", foo: foo }; var obj2={ bar: "obj2" }; foo(); // "global" obj1.foo(); // "obj1" foo.call( obj2 ); // "obj2" new foo(); // undefined
Q23:如何向 Array 對象添加自定義方法,讓下面的代碼可以運行?
難度:????
var arr=[1, 2, 3, 4, 5]; var avg=arr.average(); console.log(avg);
JavaScript 不是基于類的,但它是基于原型的語言。這意味著每個對象都鏈接到另一個對象(也就是對象的原型),并繼承原型對象的方法。你可以跟蹤每個對象的原型鏈,直到到達沒有原型的 null 對象。我們需要通過修改 Array 原型來向全局 Array 對象添加方法。
Array.prototype.average=function() { // 計算 sum 的值 var sum=this.reduce(function(prev, cur) { return prev + cur; }); // 將 sum 除以元素個數并返回 return sum / this.length; } var arr=[1, 2, 3, 4, 5]; var avg=arr.average(); console.log(avg); //=> 3
Q24:什么是 JavaScript 中的提升操作?
難度:????
提升(hoisting)是 JavaScript 解釋器將所有變量和函數聲明移動到當前作用域頂部的操作。有兩種類型的提升:
無論 var(或函數聲明)出現在作用域的什么地方,它都屬于整個作用域,并且可以在該作用域內的任何地方訪問它。
var a=2; foo(); // 因為`foo()`聲明被"提升",所以可調用 function foo() { a=3; console.log( a ); // 3 var a; // 聲明被"提升"到 foo() 的頂部 } console.log( a ); // 2
Q25:以下代碼輸出的結果是什么?
難度:????
0.1 + 0.2===0.3
這段代碼的輸出是 false,這是由浮點數內部表示導致的。0.1 + 0.2 并不剛好等于 0.3,實際結果是 0.30000000000000004。解決這個問題的一個辦法是在對小數進行算術運算時對結果進行舍入。
Q26:請描述一下 Revealing Module Pattern 設計模式。
難度:?????
暴露模塊模式(Revealing Module Pattern)是模塊模式的一個變體,目的是維護封裝性并暴露在對象中返回的某些變量和方法。如下所示:
var Exposer=(function() { var privateVariable=10; var privateMethod=function() { console.log('Inside a private method!'); privateVariable++; } var methodToExpose=function() { console.log('This is a method I want to expose!'); } var otherMethodIWantToExpose=function() { privateMethod(); } return { first: methodToExpose, second: otherMethodIWantToExpose }; })(); Exposer.first(); // 輸出: This is a method I want to expose! Exposer.second(); // 輸出: Inside a private method! Exposer.methodToExpose; // undefined
它的一個明顯的缺點是無法引用私有方法。
英文原文
https://www.fullstack.cafe/blog/top-26-javascript-interview-questions-and-answers-in-2019
、定義變量
ECMAScript的變量是松散型類型的,所謂松散型類型就是可以用來保存任何類型的數據。換句話說,每個變量僅僅是一個用于保存值的占位符而已。定義變量時要使用var操作符(var是一個關鍵字),后跟變量名(即一個標識符),如下所示:
var message;
這行代碼定義了一個名為message的變量,該變量可以用來保存任何值(像這樣未經過初始化的變量,會保存一個特殊的值—undefined)。ECMAScript也支持直接初始化變量,因此在定義變量的同時就可以設置變量的值,如下所示:
var message="hi";
在此,變量message保存了一個字符串"hi"。像這樣初始化變量并不會把它標記為字符串類型;初始化的過程就是給變量賦一個值那么簡單。因此,可以在修改變量值的同時修改值的類型,如下所示:
var message="hi"; message=100; //有效,但不推薦
在這個例子中,變量message一開始保存了一個字符串值"hi",然后該值又被一個數字值100取代。雖然我們不建議修改變量所保存的類型,但這種操作在ECMAScript中完全有效。
可以使用一條語句定義多個變量,只要像下面這樣把每個變量(初始化或不初始化均可)用逗號分隔開即可:
var message="hi", found=false, age=29;
這個例子定義并初始化了3個變量。同樣由于ECMAScript是松散類型的,因而使用不同類型初始化變量的操作可以放在一條語句中來完成。雖然代碼里的換行和變量縮進不是必需的,但這樣做可以提高可讀性。
在嚴格模式下,不能定義名為eval和arguments的變量,否則會導致語法錯誤。
二、定義局部變量和全局變量
有一點必須注意,即用var操作符定義的變量將成為定義該變量的作用域中的局部變量。也就是說,如果在函數中使用var定義一個變量,那么這個變量在函數退出后就會被銷毀,例如:
function test(){ var message="hi"; //局部變量 } test(); alert(message); //錯誤!
這里,變量message是在函數中使用var定義的。當函數被調用時,就會創建該變量并為其賦值。而在此之后,這個變量又會立即被銷毀,因此例子中的下一行代碼就會導致錯誤。不過可以像下面這樣省略var操作符,從而創建一個局部變量:
function test(){ message="hi"; //全局變量 } test(); alert(message); //"hi"
這個例子省略了var操作符,因而message就成了全局變量。這樣,只要調用過一次test()函數,這個變量就有了定義,就可以在函數外部的任何地方被訪問到。
雖然省略var操作符可以定義全局變量,但這也不是我們推薦的做法。因為在局部作用域中定義的全局變量很難維護,而且如果有意地忽略了var操作符,也會由于相應變量不會馬上就有定義而導致不必要的混亂。給未經聲明的變量賦值在嚴格模式下會導致拋出ReferenceError錯誤。
avaScript中變量分為命名,聲明,類型,作用域四個方面。
變量不只是你想的那樣簡單粗暴,其實還有更好的方式方法,要學會更簡單更高效的去使用它,讓你的開發簡潔明了,看了下面這篇文章后,你講改掉你的陋習,從基礎做起,做最好的程序猿。
JavaScript 變量是存儲數據值的容器。
關于JavaScript變量我們將會從下面幾個方面出發:
命名主要分為命名的方法和規則。
1、命名的方法
關于命名的方法主要分為兩種匈牙利命名法和駝峰命名法。
匈牙利命名法:
變量名=類型+對象描述
這就是匈牙利命名法的規則了,它的變量名是根據你想定義的變量的類型 + 你想定義變量的描述來進行變量的命名。在我的JavaScript編程中我用的還是比較多的,這樣可以高效的區分多個變量名的意義了,看看下面這張圖就明白了:
匈牙利命名法
我們利用變量類型的首字母+對象描述來完成。
駝峰命名法
這就很常見了,作為程序員基本上都已這種方法來進行命名,駝峰命名法也可分為全部小寫和大小寫混合,全部小寫是將單詞與單詞間用下劃線分割,大小寫混合又分為大駝峰和小駝峰,大駝峰是將每個單詞的首字母大寫,小駝峰是將第一個單詞的首字母小寫,其他首字母大寫。同樣的來看看這張圖:
駝峰命名法
小結:你定義的變量,我們要直接從變量名中得到信息,就像看見你我就知道你叫什么名,長得帥不帥,要有它存在的意義,你有你爸媽做主,那名字當然響亮有意義,你的代碼只能你做主,它也是代表你的臉面,好不好,有沒有意義你說了算。別再用a,b,c,d啦。
2、命名的規則
所有JavaScript變量必須以唯一的名稱的標識存在,這些唯一的名稱被稱為標識符,你的名字可以存在兩個以上,但JS就不行,看看它構造變量名稱(唯一標識符)的通用規則是什么?
遵守上面的這些規則再加上好的命名方法就可以是一個好的變量名,這里的關鍵詞和保留字是不能作為JavaScript變量名的,例如:
關鍵詞不能用作變量名
保留字:以后可能做為關鍵字的存在
在ES5的時候,JavaScript 使用 var 和 function 來聲明變量, 在ES6 中又添加了let、const、import、 Class 這幾種聲明變量的方式。
長期以來我們經常用 var 來聲明變量,我們不難看出使用 var 聲明變量有幾個特點:
注意在聲明時候的陋習:
這些都是在聲明變量時的陋習,盡可能不要犯這些錯誤。
我們來看看下面這道題:
var getName;
getName=function(){
alert(1)
};
function getName(){
alert(2)
};
function getName(){
alert(2)
};
var getName=function()alert(1)};
getName();// 1
這就是JavaScript變量的提升,不僅只有var 聲明才會提前,以function fn(){}這種形式聲明的函數,會被提升到作用域的最最頂部,然后再是變量的提升。
此外再教你一個javascript如何用變量值做變量名聲明?用eval就可以這樣聲明。
var ar=["a","b","c","d"];
for(var i=0;i<ar.length;i++){
eval('var '+ar[i]+'=0;');
}
alert(a);
alert(b);
JavaScript中的變量類型分為值類型和引用類型,而值類型又叫做數據類型。
值類型有:
值類型的特點是:
引用類型有:
引用類型的特點是:
關于數據類型這方面我會在以后的文章詳細說明使用方法。
JavaScript的作用域的內容分為全局變量,局部變量,優先級,作用域鏈這四個知識點。作用域決定了這些變量的可訪問性,在JavaScript函數中聲明的變量,會成為函數的局部變量,局部變量的作用域是局部的,只能在函數內訪問他們,在JavaScript中,作用域是很關鍵的知識點。
1、全局變量
全局變量是包含在函數體外的變量,在函數體內定義的無var的變量,是可以在任何位置進行調用的。全局變量的作用域是全局的,網頁的所有腳本和函數都能夠訪問它,例如:
var carName=" BMW";
// 此處的代碼能夠使用 carName 變量
function myFunction() {
// 此處的代碼也能夠使用 carName 變量
}
2、局部變量
局部變量是函數中聲明的變量,會成為函數的局部變量,不能作為函數外的變量使用,局部變量的作用域是局部的,只能在函數內訪問,例如:
// 此處的代碼不能使用 carName 變量
function myFunction() {
var carName="porsche";
// 此處的代碼能使用 carName 變量
}
額外注意的是:
自動全局:如果你沒有為未聲明的變量賦值,此變量會成為全局變量,就是在函數外面聲明了一個為賦值的變量,在函數體內進行賦值,那它也是一個全局變量,例如:
myFunction();
// 此處的代碼能夠使用 carName 變量
function myFunction() {
carName="BMW";
}
嚴格模式:所有的瀏覽器都支持“嚴格模式”運行JavaScript,在嚴格模式中不會自動創建全局變量。
JavaScript變量的有效期:JavaScript變量的有效期始于被創建時,局部變量會在函數完成時被刪除,全局變量會在你關閉頁面時被刪除。
函數參數也是函數內的局部變量。
3、優先級
這里面你就明白訪問的優先級就行了,
局部變量高于同名全局變量
參數變量高于同名全局變量
局部變量高于同名參數變量
4、作用域鏈
這么說吧,通過標識符查找標識符的值,會從當前作用域向上查找,直到作用域找到第一個匹配的標識符位置,這就是JavaScript的作用域鏈。
先來看看這段代碼:
var a=1;
function fn1 () {
var a=2;
function fn2 () {
var a=3;
console.log(a);
}
fn2 ();
}
fn1(); // 3
在這段代碼JavaScript在查找a變量標識符的值的時候,會從函數體fn2內部向上查找變量聲明,這時候就會發現內部已經有了a變量,它就不會繼續查找了,如果沒有,它會向上查找,這時候已經找到了,它就會打印3。
在腳本開頭聲明所有的變量是個好習慣,在日常程序中,被聲明的變量經常是不帶值得,基本上是當做被計算的內容,或是之后被提供的數據,比如數據的輸入,不帶有值的變量,它的值將是undefined。
知道了對象的描述,我們就要用有意義的變量名去聲明,然后使用不同的作用域去訪問它,使用它,基礎的心里清楚了,這對你寫代碼是個很好地幫助。
本文為‘Web前端進階指南’原創,轉載請說明出處,手動碼字不易,喜歡的小伙伴們別忘了順手點個贊加個關注哈,有什么不懂的下方留言評論或私信。謝謝大家哈!
*請認真填寫需求信息,我們會在24小時內與您取得聯系。