(答案持續更新...)
1.簡述同步和異步的區別
js是一門單線程語言,所謂"單線程",就是指一次只能完成一件任務。如果有多個任務,就必須排隊,前面一個任務完成,再執行后面一個任務,以此類推。如果一個任務耗時過長,那么后面的任務就必須一直等待下去,會拖延整個程序,常見瀏覽器無反應,可能就是一段代碼死循環,造成程序卡住在這個位置,無法繼續
為了解決這個問題,js的執行模式分為兩種:同步和異步。
"同步模式"就是上一段的模式,后一個任務等待前一個任務結束,然后再執行,程序的執行順序與任務的排列順序是一致的、同步的;
"異步模式"則完全不同,每一個任務有一個或多個回調函數(callback),前一個任務結束后,不是執行后一個任務,而是執行回調函數,后一個任務則是不等前一個任務結束就執行,所以程序的執行順序與任務的排列順序是不一致的、異步的。
具體來說,異步運行機制如下:
(1)所有同步任務都在主線程上執行,形成一個執行棧(execution context stack)。
(2)主線程之外,還存在一個"任務隊列"(task queue)。只要異步任務有了運行結果,就在"任務隊列"之中放置一個事件。
(3)一旦"執行棧"中的所有同步任務執行完畢,系統就會讀取"任務隊列",看看里面有哪些事件。那些對應的異步任務,于是結束等待狀態,進入執行棧,開始執行。
(4)主線程不斷重復上面的第三步
2.怎么添加、移除、復制、創建、和查找節點
(1)創建新節點
createDocumentFragment() //創建一個DOM片段
createElement() //創建一個具體的元素
createTextNode() //創建一個文本節點
var para=document.createElement("p"); //創建新的 <p> 元素
var node=document.createTextNode("這是新段落。"); //創建了一個文本節點
para.appendChild(node); //向 <p> 元素追加這個文本節點
var element=document.getElementById("div1");//向一個已有的元素追加這個新元素
element.appendChild(para); //向這個已有的元素追加新元素
(2)添加、移除、替換、之前插入、之后插入、復制
appendChild()、removeChild()、replaceChild()、insertBefore()、insertAfter()、cloneNode()
(3)查找
document.getElementsByTagName("") //通過標簽名稱
document.getElementsByName("") //通過元素的Name屬性的值
document.getElementById("") //通過元素Id,唯一性
document.getElementsByClassName(""); //通過類查找
document.querySelector("")
3.實現一個函數clone 可以對Javascript中的五種主要數據類型(Number、string、Object、Array、Boolean)進行復制
function clone(obj) {var o;switch (typeof obj) {case 'undefined':break;case 'string':o = obj + '';break;case 'number':o = obj - 0;break;case 'boolean':o = obj;break;case 'object': //object分為兩種,一種為Object,一種為Arrayif (obj === null) {o = null} else {if (Object.prototype.toString.call(obj).slice(8, -1) === 'Array') {o = [];for (var i = 0; i < obj.length; i++) {o.push(clone(obj[i]))}} else {o = {};for (var k in obj) {o[k] = clone(obj[k])}}}break;default:o = obj;break;}return o;}
4.如何消除一個數組里面重復的元素
// 方法一:Array.prototype.clearRepeat = function () {var arr = []; //定義一個臨時數組for (var i = 0; i < this.length; i++) {//通過遍歷判斷當前數組下標為i的元素是否保存到臨時數組中//如果保存,則跳過,否則保存到臨時數組if (arr.indexOf(this[i]) == -1) {arr.push(this[i]);}}return arr;};var test = [1, 6, 8, 8, 9, 9, 9, "a", "a"];test.clearRepeat(); //結果為[1, 6, 8, 9, "a"]// 方法二:Array.prototype.clearRepeat = function () {var arr = [this[0]]; //直接定義結果數組for (var i = 1; i < this.length; i++) { //從第二項開始遍歷當前數組//對元素進行判斷://如果當前數組元素在此數組中第一次出現的位置不是i//則第i項是重復的,否則直接存入結果數組if (this.indexOf(this[i]) == i) {arr.push(this[i]);}}return arr;};var test = [1, 6, 8, 8, 9, 9, 9, "a", "a"];test.clearRepeat(); //結果為[1, 6, 8, 9, "a"]
// 上面兩種方法不推薦使用,因為indexOf()這個函數在執行的時候每次都會遍歷一次數組,
//對性能影響比較大。比較適合小數據量的數組。//第三種使用的是hash表,把已經出現過的元素通過下標形式寫入一個Object中,
//下標的引用要比數組的indexOf()方法搜索節省時間Array.prototype.clearRepeat = function() {var h = {}; //定義一個hash表var arr = []; //定義一個臨時數組for (var i = 0; i < this.length; i++) {if (!h[this[i]]) {h[this[i]] = true;arr.push(this[i]);}}return arr;};var test = [1, 6, 8, 8, 9, 9, 9, "1", "a"];console.log(test.clearRepeat()) //結果為[1, 6, 8, 9, "a"]
//此方法有缺陷,作為下標在轉換后會變成字符串,
//那么對于1和“1”這樣不同類型的值會對應到同一個下標而被去重。例如obj[1]和obj['1'],是相同的Array.prototype.clearRepeat = function() {var h = {}; //定義一個hash表var arr = []; //定義一個臨時數組for (var i = 0; i < this.length; i++) {var type = typeof this[i];if (!h[this[i] + type]) {h[this[i] + type] = true;arr.push(this[i]);}}return arr;};//不用hash表的解決辦法嗎,使用原始sort()排序后相鄰兩個數值進行比較Array.prototype.clearRepeat = function() {this.sort(); //數組排序var arr = [this[0]]; //定義結果數組for (var i = 1; i < this.length; i++) { //從第二項開始遍歷當前數組//判斷兩個相鄰元素是否相等,如果相等說明數據重復,否則將元素寫入結果數組if (this[i] !== arr[arr.length - 1]) {arr.push(this[i])}}return arr}var test = [1, 6, 8, 8, 9, 9, 9, "1", "a"];
5.寫一個返回閉包的函數
1.閉包函數是指有權訪問另一個函數作用域中的變量的函數
2.創建閉包函數最常見的方式是在一個函數內創建另一個函數,通過另一個函數訪問這個函數的局部變量
3.閉包的特點:1函數嵌套函數,2 函數內部可以引用外部的參數和變量 3 參數和變量不會被垃圾回收機制回收4.閉包的優點:1 希望一個變量長期駐扎在內存中 * 2 避免全局變量的污染 * 3 私有變量存在5.閉包的實現 1:函數嵌套函數 * 2 外層函數返回內層函數 * 3 外面有一全局變量接受外層函數6.缺點: 閉包使用不當,會造成內存污染,正常無法被垃圾回收機制清掉,IE低版本會造成內存泄漏function fun1() {var num = 1;return function () {num++;return num;}}var s = fun1()// console.log(s());// // 自執行函數 的閉包var fun3 = function () {var num = 1;return {b: 6,sum: function () {return num + this.b}}}();// console.log(fun3.sum());/** 把函數名當參數調用* 回調函數** */function a() {console.log('a');}function b() {console.log('b');}function c(fun) {fun()}// c(a), c(b)// 循環var num = 0;function a(fun) {console.log(123);fun(fun)}function a2(fun) {num++;console.log(num);if (num > 10) return;fun(a2)}a(a2)
6.使用遞歸完成1到100的累加
function add(num) {if (num == 1) {return num;} else {return num + add(num - 1)}}console.log(add(100));
7.Javascript有哪幾種數據類型
1,基本類型:字符串類型(string),數字類型(number),布爾類型(boolean)
2,復雜類型:數組類型(array),對象類型(object),函數類型(function),正則類型(regexp)
3,空類型:undefine 和 null
8.如何判斷數據類型
1、typeof
typeof 123, //"number"typeof 'dsfsf', //"string"typeof false, //"boolean"typeof [1,2,3], //"object"typeof {a:1,b:2,c:3}, //"object"typeof function(){console.log('aaa');}, //"function"typeof undefined, //"undefined"typeof null, //"object"typeof new Date(), //"object"typeof /^[a-zA-Z]{5,20}$/, //"object"typeof new Error() //"object"
Array,Object,null,Date,RegExp,Error這幾個類型都被typeof判斷為object,所以如果想要判斷這幾種類型,就不能使用typeof了。
Number,String,Boolean,Function,undefined,如果想判斷這幾種類型,那就可以使用typeof。
2、instanceof
123 instanceof Number, //false'dsfsf' instanceof String, //falsefalse instanceof Boolean, //false[1,2,3] instanceof Array, //true{a:1,b:2,c:3} instanceof Object, //truefunction(){console.log('aaa');} instanceof Function, //trueundefined instanceof Object, //falsenull instanceof Object, //falsenew Date() instanceof Date, //true/^[a-zA-Z]{5,20}$/ instanceof RegExp, //truenew Error() instanceof Error //true
instanceof運算符需要指定一個構造函數,或者說指定一個特定的類型,它用來判斷這個構造函數的原型是否在給定對象的原型鏈上
Number,String,Boolean沒有檢測出他們的類型,但是如果使用下面的寫法則可以檢測出來
var num = new Number(123);
var str = new String('dsfsf');
var boolean = new Boolean(false);
還需要注意null和undefined都返回了false,這是因為它們的類型就是自己本身,并不是Object創建出來它們,所以返回了false。
3、constructor
constructor是prototype對象上的屬性,指向構造函數。根據實例對象尋找屬性的順序,若實例對象上沒有實例屬性或方法時,就去原型鏈上尋找,因此,實例對象也是能使用constructor屬性的。
var num = 123;
var str = 'abcdef';
var bool = true;
var arr = [1, 2, 3, 4];
var json = {name:'wenzi', age:25};
var func = function(){ console.log('this is function'); }
var und = undefined;
var nul = null;
var date = new Date();
var reg = /^[a-zA-Z]{5,20}$/;
var error= new Error();function Person(){}
var tom = new Person();// undefined和null沒有constructor屬性
console.log(tom.constructor==Person,num.constructor==Number,str.constructor==String,bool.constructor==Boolean,arr.constructor==Array,json.constructor==Object,func.constructor==Function,date.constructor==Date,reg.constructor==RegExp,error.constructor==Error
);
//所有結果均為true
除了undefined和null之外,其他類型都可以通過constructor屬性來判斷類型。
4、toString()
可以通過toString() 來獲取每個對象的類型。為了每個對象都能通過 Object.prototype.toString() 來檢測,需要以 Function.prototype.call() 或者 Function.prototype.apply() 的形式來調用,傳遞要檢查的對象作為第一個參數,稱為thisArg。
var toString = Object.prototype.toString;toString.call(123); //"[object Number]"
toString.call('abcdef'); //"[object String]"
toString.call(true); //"[object Boolean]"
toString.call([1, 2, 3, 4]); //"[object Array]"
toString.call({name:'wenzi', age:25}); //"[object Object]"
toString.call(function(){ console.log('this is function'); }); //"[object Function]"
toString.call(undefined); //"[object Undefined]"
toString.call(null); //"[object Null]"
toString.call(new Date()); //"[object Date]"
toString.call(/^[a-zA-Z]{5,20}$/); //"[object RegExp]"
toString.call(new Error()); //"[object Error]"
總結以上的案例封裝個方法
function gettype(obj) {var type = typeof obj;if (type !== 'object') {return type;}//如果不是object類型的數據,直接用typeof就能判斷出來//如果是object類型數據,準確判斷類型必須使用Object.prototype.toString.call(obj)的方式才能判斷return Object.prototype.toString.call(obj).replace(/^[object (S+)]$/, '$1');
}
5、jQuery中判斷的方法
jQuery.isArray(object)
jQuery.isFunction(value)
jQuery.isNumeric(value)
jQuery.isEmptyObject(obj)
jQuery.isPlainObject(value)
9.console.log(1+'2')和console.log(1-'2')的打印結果
‘12’和-1,第一個是因為是字符串拼接,第二種減法運算將2強制轉化為數值類型進行的運算
10.Js的事件委托是什么,原理是什么
事件委托,通俗來說就是將元素的事件委托給它的父級或者更外級元素處理。
事件流包括三個階段:
- 事件捕獲:和冒泡類似,只不過事件的順序相反。即是從上級節點傳遞到下級節點
- 目標階段
- 事件冒泡:當下級節點觸發某個事件的時候,該事件會逐級向上觸發上級節點的同類事件

事件委托就是利用事件冒泡機制實現的
事件委托的優點:
- 只需要將同類元素的事件委托給父級或者更外級的元素,不需要給所有元素都綁定事件,減少內存空間占用,提升性能
- 使用事件委托可以自動綁定動態添加的元素,即新增的節點不需要主動添加也可以一樣具有和其他元素一樣的事件
11.如何改變函數內部的this指針的指向
常用變量取代: var _this = this
call()的用法
var obj = {text: '我的兩個愛好:'
}function getHobby(a, b) {console.log(this.text + a + '和' + b)
}getHobby.call(obj, '足球', '羽毛球')
// 我的兩個愛好:足球和羽毛球
apply()的用法
var obj = {text: '我的兩個愛好:'
}function getHobby(a, b) {console.log(this.text + a + '和' + b)
}getHobby.apply(obj, ['足球', '羽毛球'])
// 我的兩個愛好:足球和羽毛球
bind()的用法
var obj = {text: '我的兩個愛好:'
}function getHobby(a, b) {console.log(this.text + a + '和' + b)
}getHobby.bind(obj, '足球', '羽毛球')()
// 我的兩個愛好:足球和羽毛球
對比
getHobby.call(obj, '足球', '羽毛球')
getHobby.apply(obj, ['足球', '羽毛球'])
getHobby.bind(obj, '足球', '羽毛球')()
12.列舉幾種解決跨域問題的方式,且說明原理
1.什么是同源策略及其限制內容?
同源策略是一種約定,它是瀏覽器最核心也最基本的安全功能,如果缺少了同源策略,瀏覽器很容易受到XSS、CSRF等攻擊。所謂同源是指"協議+域名+端口"三者相同,即便兩個不同的域名指向同一個ip地址,也非同源。
當協議、子域名、主域名、端口號中任意一個不相同時,都算作不同域。不同域之間相互請求資源,就算作“跨域”。常見跨域場景如下圖所示:

特別說明兩點:
第一:如果是協議和端口造成的跨域問題“前臺”是無能為力的。
第二:在跨域問題上,僅僅是通過“URL的首部”來識別而不會根據域名對應的IP地址是否相同來判斷。“URL的首部”可以理解為“協議, 域名和端口必須匹配”。
這里你或許有個疑問:請求跨域了,那么請求到底發出去沒有?
跨域并不是請求發不出去,請求能發出去,服務端能收到請求并正常返回結果,只是結果被瀏覽器攔截了。你可能會疑問明明通過表單的方式可以發起跨域請求,為什么 Ajax 就不會?因為歸根結底,跨域是為了阻止用戶讀取到另一個域名下的內容,Ajax 可以獲取響應,瀏覽器認為這不安全,所以攔截了響應。但是表單并不會獲取新的內容,所以可以發起跨域請求。同時也說明了跨域并不能完全阻止 CSRF,因為請求畢竟是發出去了。
2、跨域解決方案
1.jsonp
2.cors
3.postMessage
4.websocket
5. Node中間件代理(兩次跨域)
6.nginx反向代理
7.window.name + iframe
8.location.hash + iframe
9.document.domain + iframe
webpack解決跨域
// 后端沒設置跨域時,可使用webpack進行配置// devServer: {// // 代理// proxy: {// // 只要請求地址有'api'都會匹配上// "/api": {// target: "http://132.232.94.151:3005",// ws: true,// // 允許跨域// changeOrigin: true,// pathRewrite: {// "^/api": "" //通過pathRewrite重寫地址,將前綴/api轉為/// }// }// }// }
13.談談垃圾回收機制的方式及內存管理
14.寫一個function ,清除字符串前后的空格
function trim(str) {if (str && typeof str === "string") {return str.replace(/(^s*)|(s*)$/g,""); //去除前后空白符}
}
15.js實現繼承的方法有哪些
16.判斷一個變量是否是數組,有哪些辦法
17.let ,const ,var 有什么區別
一)var聲明變量存在變量提升,let和const不存在變量提升
console.log(a); // undefined ===> a已聲明還沒賦值,默認得到undefined值
var a = 100;
console.log(b); // 報錯:b is not defined ===> 找不到b這個變量
let b = 10;
console.log(c); // 報錯:c is not defined ===> 找不到c這個變量
const c = 10;
二)let、const都是塊級局部變量
{let a = 1
}
console.log(a) // undefined
const 的特性和 let 完全一樣,不同的只是
1)聲明時候必須賦值
const a
控制臺報錯

2)只能進行一次賦值,即聲明后不能再修改
const a=1
a=2
控制臺報錯

3)如果聲明的是復合類型數據,可以修改其屬性

18.箭頭函數與普通函數有什么區別
那么箭頭函數有哪些特點?
- 更簡潔的語法
- 沒有this
- 不能使用new 構造函數
- 不綁定arguments,用rest參數...解決
- 使用call()和apply()調用
- 捕獲其所在上下文的 this 值,作為自己的 this 值
- 箭頭函數沒有原型屬性
- 不能簡單返回對象字面量
- 箭頭函數不能當做Generator函數,不能使用yield關鍵字
- 箭頭函數不能換行
19.隨機取1-10之間的整數
<script>
document.write(parseInt(10*Math.random())); //輸出0~10之間的隨機整數
document.write(Math.floor(Math.random()*10+1)); //輸出1~10之間的隨機整數
function RndNum(n){
var rnd="";
for(var i=0;i<n;i++)
rnd+=Math.floor(Math.random()*10);
return rnd;
}
document.write(RndNum(4)); //輸出指定位數的隨機數的隨機整數引用部分:
1. 從1開始 至 任意值parseInt(Math.random()*上限+1);
2. 從任意值開始 至 任意值parseInt(Math.random()*(上限-下限+1)+下限); function fRandomBy(under, over){
switch(arguments.length){
case 1: return parseInt(Math.random()*under+1);
case 2: return parseInt(Math.random()*(over-under+1) + under);
default: return 0;
}
}
document.write(fRandomBy(1,100)); //輸出指定范圍內的隨機數的隨機整數