文章加密

;

2019年7月31日 星期三

跨域請求 Cors

https://developer.mozilla.org/zh-TW/docs/Web/HTTP/CORS   跨來源資源共用(CORS) MDN

https://www.ithome.com.tw/voice/129558  深入認識跨域請求

前端工程師 面試 問題

https://www.google.com/search?ei=AGpCXbnyKMzywQOy6ZyoCg&q=%E5%89%8D%E7%AB%AF%E5%B7%A5%E7%A8%8B%E5%B8%AB+%E9%9D%A2%E8%A9%A6+%E5%95%8F%E9%A1%8C&oq=%E5%89%8D%E7%AB%AF%E5%B7%A5%E7%A8%8B%E5%B8%AB+%E9%9D%A2%E8%A9%A6+%E5%95%8F%E9%A1%8C&gs_l=psy-ab.3...3886.5262..5773...0.0..0.133.647.6j1......0....1..gws-wiz.......0i30j33i160.GC-4v-D2Hpg&ved=0ahUKEwi58uKv6uDjAhVMeXAKHbI0B6UQ4dUDCAo&uact=5

問考官 和PM的溝通方式

es6 的 function, this 指向

es6 的 function
var fn = () => 'Hello';
即為
function fn () {
   return  'Hello';
}

如果只帶一個參數,可以拿掉parentheses(),如下
var fn = a => a+5;


this 指向
es5
function doSth(){
    console.log(this)
}

1.直接console.log function,結果是印出window物件
console.log(doSth())  //  [object Window] { ... }

2.直接在HTML的body內添加button tag,接著加上下面這行程式,結果是印出呼叫此function的物件→即object HTMLButtonElement
button.addEvenetListener('click', fn)//  [object HTMLButtonElement] { ... }

結論:this always refers to 呼叫此function的物件

es6

var fn2 = () => console.log(this);

button .addEventListener('click',fn2);  //  [object Window] { ... }

結論:this always refers to what it refers when you define the function no matter how or where you call the function

1. setTimeout 第一個參數除了function還這已直接將程式寫在""裡。js是單一執行續,那同步跟非同步是怎麼進行的呢? 2. let、const

https://kuro.tw/posts/2019/02/23/%E8%AB%87%E8%AB%87-JavaScript-%E7%9A%84-setTimeout-%E8%88%87-setInterval/



setTimeout 第一個參數除了function外,還可以直接將程式寫在""裡,這是因為有eval轉換,但是效能會比function要差一些




變數scope影響settimeout的結果
// 假設想透過迴圈 + setTimeout 來做到
// 每秒鐘將 i 的值 console 出來

for( var i = 0; i < 5; i++ ) {
  window.setTimeout(function() {
    console.log(i);
  }, 1000);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
但是上面這段 code 執行的結果是, console.log() 會在「一秒鐘之後」同時印出「五次 5」。
這是為什麼呢? 由於 JavaScript var切分變數有效範圍 (scope) 的最小單位為 function,所以當 1000ms 過去, 變數 i 的值早已是 for 迴圈更新完畢的 i ,而不是迴圈內當下的那個 i 。



接下來,我們簡單的來了解一下let、const的應用吧!
let、const是ES6之後加入的新成員,其作用的範圍跟var有些差異。
  • let與const是區塊作用域(block scope),如果const定義的變數是array,它屬於object是reference type,將可以被改變!因為by reference表示他並不是真的紀錄那些值,而是記錄那個記憶體位置!
  • var是函式作用域(function scope)
由上面的案例,我們得知「使用var宣告變數,可用範圍以function為界,function外讀不到值」,但如果使用區塊語句像if, else, for, while等等區塊語句時,宣告的區域變數仍然可在整段程式碼做存取,這並不是我們希望的結果。這時候我們就會用到let。
以上面的程式碼為例,我們在if(block scope)中宣告的變數 b在if的作用域外仍然可以被存取。
假使這項變數只有在if判斷時需要被使用,我們會希望變數 b在if作用範圍外不要被保留,所以我們改使用let變數吧。
改用let宣告變數 b後,就會發現b的值無法順利被取得,這表示變數 b在離開if區域後,是無法作用的。
這時候如果打開console就可以看到b is not defined的錯誤訊息喔!



所以我們會改用這樣的寫法來隔離變數作用域:
for( var i = 0; i < 5; i++ ) {

  // 為了凸顯差異,我們將傳入後的參數改名為 x
  // 當然由於 scope 的不同,要繼續在內部沿用 i 這個變數名也是可以的。
  (function(x){
    window.setTimeout(function() {
      console.log(x);
    }, 1000 * x);
  })(i);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
像這樣的做法,通常稱它為 IIFE (Immediately Invoked Function Expression)
一用就丟 立刻被呼叫、執行的 function 表達式。
或者,也可以改用 let 提供的 Block Scope 特性:
for( let i = 0; i < 5; i++ ) {
  window.setTimeout(function() {
    console.log(i);
  }, 1000 * i);
}
  • 1
  • 2
  • 3
  • 4
  • 5
就可以避開這個問題。
P.S.如果寫成下面這樣
for(let i = 0; i < 5; i++ ) {
window.setTimeout("console.log(i)"
, 1000);
}
會出現error
Uncaught ReferenceError: i is not defined


討論一下let

1.
age=27;
let age;
console.log(age)

結果:Uncaught ReferenceError: Cannot access 'age' before initialization

2.
age=27;
console.log(age)

結果:Uncaught ReferenceError: age is not defined

3.
function doSth(){
age=27;
}
let age;
doSth()
console.log(age)

結果:27
→you have to declare things before you actually using them

js是單一執行續,那同步跟非同步是怎麼進行的呢?

主要執行緒指的是正在排隊等待要執行的任務,也就是圖中的 Stack:
Stack 裡面的任務有同步、也有些非同步事件,像是本文不斷提到的 setTimeout() 或者 Ajax等等非同步的事件。
JavaScript 的執行緒會逐一執行 Stack 內的任務,當碰上了非同步事件時,為了不讓程式被這些需要等待的任務卡著,就會繼續執行後續的動作。 而當這些非同步事件的 callback function 被呼叫時,就會將 callback function 的任務丟到 Event Queue 當中,並等待目前 Stack 的任務都已經完成後,再繼續逐一執行 Event Queue 的任務。
所以再次回到範例:
console.log('start');

(function () {
  console.log('call function')

  window.setTimeout(function () {
    console.log('setTimeout');
  }, 0);
})();

console.log('end');
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
即便我們在 setTimeout() 的等待時間設定為 0, 因為 JavaScript 會先將其擱置到 Queue 當中, 等待 Stack 的任務完成後,再回來執行 setTimeout() 內的 callback function。 這也就是為什麼範例中 setTimeout 總是會比 end 還要晚印出的原因了。

原型, 模組, 閉包, 拉升, typeof, 不等性(Inequality)

https://cythilya.github.io/2018/10/10/intro-2/  看完後在緊急補一下ES6

原型(Prototype)( https://blog.techbridge.cc/2017/04/22/javascript-prototype/ )

原型可說是物件的一種 fallback 機制,當在此物件找不到指定屬性時,就會透過原型鏈結(prototype link / prototype reference)追溯到其父物件上。範例如下,若想存取 bar.a 但由於 bar 並無 a 屬性,因此就會透過原型鏈結找到 foo,並得到 100 這個值。
var foo = { a: 100 };

var bar = Object.create(foo); // 建立 bar 物件,並連結到 foo
bar.b = 'hi';

bar.a // 100,委派給 foo
bar.b // 'hi'
原型
另外,原型最常應用於「行為委派」(behavior delegation),如上例所示,將物件 bar 的行為委派給 foo,這也是常聽到很類似於其他語言的類別的繼承功能,但其實完全不同。


https://blog.techbridge.cc/2017/04/22/javascript-prototype/  該來理解 JavaScript 的原型鍊了

new运算符的缺点

用构造函数生成实例对象,有一个缺点,那就是无法共享属性和方法。

比如,在DOG对象的构造函数中,设置一个实例对象的共有属性species。

  function DOG(name){

    this.name = name;

    this.species = '犬科';

  }

然后,生成两个实例对象

  var dogA = new DOG('大毛');

  var dogB = new DOG('二毛');

这两个对象的species属性是独立的,修改其中一个,不会影响到另一个。

  dogA.species = '猫科';

  alert(dogB.species); // 显示"犬科",不受dogA的影响

每一个实例对象,都有自己的属性和方法的副本。这不仅无法做到数据共享,也是极大的资源浪费(站了兩個空間)。



prototype属性的引入

考虑到这一点,Brendan Eich决定为构造函数设置一个prototype属性

这个属性包含一个对象(以下简称"prototype对象"),所有实例对象需要共享的属性和方法,都放在这个对象里面;那些不需要共享的属性和方法,就放在构造函数里面。

实例对象一旦创建,将自动引用prototype对象的属性和方法。也就是说,实例对象的属性和方法,分成两种,一种是本地的,另一种是引用的。

还是以DOG构造函数为例,现在用prototype属性进行改写:

  function DOG(name){

    this.name = name;

  }

  DOG.prototype = { species : '犬科' };


  var dogA = new DOG('大毛');

  var dogB = new DOG('二毛');


  alert(dogA.species); // 犬科

  alert(dogB.species); // 犬科

现在,species属性放在prototype对象里,是两个实例对象共享的。只要修改了prototype对象,就会同时影响到两个实例对象。

  DOG.prototype.species = '猫科';


  alert(dogA.species); // 猫科

  alert(dogB.species); // 猫科
console.log(dogA.species === dogB.species) // false

P.S. 有些人會直接在 Array.prototype 上面加一些函式,讓自己可以更方便地做一些操作,原理也是這樣。可是一般來說,不推薦直接去修改不屬於你的 Object。
1
2
3
4
5
Array.prototype.last = function () {
    return this[this.length - 1];
};
  
console.log([1,2,3].last()) // 3

__proto__ (這個是要深入研究運行原理的,較複雜,而且不實用,可視情況跳過)

JavaScript 怎麼知道要到prototype去找?

function Person(name, age) {
  this.name = name;
  this.age = age;
}
  
Person.prototype.log = function () {
  console.log(this.name + ', age:' + this.age);
}
  
var nick = new Person('nick', 18);
  
console.log(nick.__proto__ === Person.prototype === Function.prototype) // true
//子.__proto__ === 父.prototype  (不能越級!!!)

// 那 Person.prototype.__proto__ 會指向誰呢?會指向 Object.prototype
console.log(Person.prototype.__proto__ === Object.prototype) // true
  
// 那 Object.prototype.__proto__ 又會指向誰呢?會指向 null,這就是原型鍊的頂端了
console.log(Object.prototype.__proto__) // null
nick 的__proto__會指向Person.prototype,所以在發現 nick 沒有 log 這個 method 的時候,JavaScript 就會試著透過__proto__找到Person.prototype,去看Person.prototype裡面有沒有 log 這個 method。
那假如Person.prototype還是沒有呢?那就繼續依照這個規則,去看Person.prototype.__proto__裡面有沒有 log 這個 method,就這樣一直不斷找下去。找到時候時候為止?找到某個東西的__proto__是 null 為止。意思就是這邊是最上層了。
而上面這一條透過__proto__不斷串起來的鍊,就叫做原型鍊。透過這一條原型鍊,就可以達成類似繼承的功能,可以呼叫自己 parent 的 method。

如果想知道一個屬性是存在 instance 身上,還是存在於它屬於的原型鍊當中,可以用hasOwnProperty這個方法:
1
2
3
4
5
6
7
8
9
10
11
12
function Person(name, age) {
  this.name = name;
  this.age = age;
}
  
Person.prototype.log = function () {
  console.log(this.name + ', age:' + this.age);
}
  
var nick = new Person('nick', 18);
console.log(nick.hasOwnProperty('log')); // false
console.log(nick.__proto__.hasOwnProperty('log')); // true
有了hasOwnProperty之後,我們就可以自己來模擬這段往上找的過程:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
function Person(name, age) {
  this.name = name;
  this.age = age;
}
  
Person.prototype.log = function () {
  console.log(this.name + ', age:' + this.age);
}
  
var nick = new Person('nick', 18);
  
function call(obj, methodName) {
  var realMethodOwner = obj;
  
  // 不斷往上找,直到 null 或者是找到真的擁有這個 method 的人為止
  while(realMethodOwner && !realMethodOwner.hasOwnProperty(methodName)) {
    realMethodOwner = realMethodOwner.__proto__;
  }
  
  // 找不到就丟一個 error,否則執行這個 method
  if (!realMethodOwner) {
    throw 'method not found.';
  } else {
    realMethodOwner[methodName].apply(obj);
  }
}
  
call(nick, 'log'); // nick, age:18
call(nick, 'not_exist'); // Uncaught method not found.

閉包(Closure)

閉包是指變數的生命週期只存在於該函式內,一旦離開了函式,該變數就會被回收而不可再利用,且必須在函式內事先宣告。
範例如下,在函式 closure 內可以存取 a 的值,但離開了函式 closure 走到全域範疇之下,就取不到 a 的值了,因此會被報錯「Uncaught ReferenceError: a is not defined」。
function closure() {
  var a = 1;
  console.log(a); // 1
}

closure();
a // Uncaught ReferenceError: a is not defined


模組(Module)

模組模式(Module Pattern)又稱為揭露模組(Revealing Module),經由建立一個模組實體(Module Instance,如下範例的 foo),來調用內層函式。而內層函式由於具有閉包的特性,因此可存取外層包含函式(Outer Enclosing Function)之內的變數和函式。透過模組模式,可隱藏私密的資訊,並對外公開 API。
範例如下,CoolModule 對外公開 API doSomething 和 doAnother,CoolModule 之外是無法取得其私有的 something 和 another 兩個變數的值。
function CoolModule() {
  var something = "cool";
  var another = [1, 2, 3];

  function doSomething() {
    console.log(something);
  }

  function doAnother() {
    console.log(another.join(" ! "));
  }

  return {
    doSomething: doSomething,
    doAnother: doAnother
  };
}

var foo = CoolModule();

foo.doSomething(); // cool
foo.doAnother(); // 1 ! 2 ! 3

什麼是Module Pattern?解決什麼問題?

Module Pattern 利用函數的「閉包(closure)」特性來避免汙染全域的問題 - 使用閉包(closure)來提供封裝的功能,將方法和變數限制在一個範圍內存取與使用。這樣的好處除了避免汙染全域外,也將實作隱藏起來,只提供公開的介面(public API)供其他地方使用,簡單易懂。

物件實字(object literals)

第一個先來看物件實字(object literals)的觀念。
在object literal notation中,物件中的屬性和方法用這樣的方式被描述 - name/value pairs,即key/value的方式,並用分號(:)區隔 - 每個name/value pairs用逗號(,)區隔,而最後一個name/value pair後不需使用逗號結尾 - 使用大括號({})包裝起來
範例如下。
var myObjectLiteral = {
    variableKey: variableValue,
    functionKey: function(){
        // ...
    }
};
使用object literal的好處是將程式碼封裝和組織起來。
如果用new來建構也是可以的,但會發生一些非預期的結果,並不建議使用。
[Note] JavaScript並沒有如同我們熟知的其他語言(例如:Java、C#等)有private、protected和public這些語法可用,而要靠函數的作用域,即「閉包(closure)」來實作。

閉包(closure)

再來看閉包(closure)的觀念。
Closure是指變數的生命週期只存在於該function中,一旦離開了function,該變數就會被回收而不可再利用,且必須在function內事先宣告。
function closure() {
    var a = 1;
    console.log(a); //1
}
closure();
console.log(a); //Uncaught ReferenceError: a is not defined
對於closure進一步的探討可參考這篇文章 Closures

Module Pattern的範例

來看一個Module Pattern的實際範例。
var testModule = (function(){
    var counter = 0;
    return {
        incrementCounter: function(){
            return counter++;
        },
        resetCounter: function(){
            console.log('counter value prior to reset: ' + counter);
            counter = 0;
        }
    };
}());

//test
testModule.incrementCounter();
testModule.resetCounter(); //counter value prior to reset: 1
在這裡,變數counter是個private變數,無法被function外的其他地方任意存取,只能經由公開方法incrementCounter 和 resetCounter取用。 function最後會return一個物件,這個物件即是公開出去的API,讓程式的其他區域可以與之互動。 而這就是利用函數的閉包特性來達成的。
我們再看一個更複雜的例子...
var basketModel = (function(){
    //private
    var basket = [];
    function doSomethingPrivate(){
        //...
    }
    function doSomethingElsePrivate(){
        //...
    }

    //public
    return {
        addItem: function(value){
            basket.push(value);
        },
        getItemCount: function(){
            return basket.length;
        },
        doSomething: doSomethingPrivate(),
        getTotal: function(){
            var q = this.getItemCount(),
                p = 0;

            while(q--){
                p = p + basket[q].price;
            }
            return p;
        }
    }
}());

basketModel.addItem({
    item: 'bread',
    price: 0.5
});

basketModel.addItem({
    item: 'butter',
    price: 0.3
});

console.log(basketModel.getItemCount()); //2
console.log(basketModel.getTotal()); //0.8
我們這樣的存取是不行的...
console.log(basketModule.basket); //"basket"是private variable,不提供函數外部存取
console.log(basket); //"basket"在函數內部的時候才可以這樣呼叫
所以我們就可以為Module Pattern做一個範本,如下。
var myNamespace = (function(){
    //private members
    var myPrivateVariable = 0;
    var myPrivateMethod = function(someText){
        console.log(someText);
    };
    //public members
    return {
        myPublicVariable: 'foo',
        myPublicFunction: function(bar){
            myPrivateVariable++;
            myPrivateMethod(bar);
        }
    };
}());

console.log(myNamespace.myPublicVariable); //foo
myNamespace.myPublicFunction('hi'); //hi

優缺點?

優點是清楚的物件導向、封裝概念。
缺點是
  • 如果要變數或方法的public/private狀態,我們必須要去用到的每一個地方手動修改使用方式
  • 在debug時,對於private members較難偵測
  • private members難以擴充,彈性不高




嚴格模式(Strict Mode)

嚴格模式簡單說就是為了預防開發者的一些不小心或錯誤的行為,JavaScript 引擎協助做了一些檢測的工作,當開發者誤用時就把錯誤丟出來。可參考-MDN
範例如下,在未宣告變數而賦值的狀況下,會無預警的產生一個全域變數,但若使用嚴格模式('use strict')則會禁止這行為外,還會報錯,告知開發者變數尚未被定義。
'use strict';

a = 1; // Uncaught ReferenceError: a is not defined


巢狀範疇(Nested Scope)

若在目前執行的範疇找不到這個變數的時候,就會往外層的範疇搜尋,持續搜尋直到找到為止,或直到最外層的全域範疇(global scope,在瀏覽器底下就是指 window)。
如下,console.log(a + b) 中,b 無法在 foo 中找到,但可從全域範疇中追出來。
const foo = (a) => {
  console.log(a + b);
}

const b = 2;

foo(2); // 4


拉升(Hoisting)

在程式執行前,編譯器(compiler)會先由上到下逐行將程式碼轉為電腦可懂的命令,然後再執行編譯後的指令。在這個編譯的階段,編譯器找出所有的變數並繫結所屬範疇,但不賦值,所以此刻變數所帶的值是 undefined;而在執行階段,JavaScript 引擎才會處理給值的事情。
我們可以把這個過程想像成是將這些變數「提升」到程式碼的最頂端,如下範例所示,因此當印出 a 的值的時候,會是已宣告但還沒賦值的狀態,也就是有這個變數,但其值是 undefined,一直到程式執行了,才給值。因此,我們可以在程式碼任何地方呼叫運用這個變數,但只有在正式宣告之後才能有正確的值可用,在宣告之前使用都會得到 undefined。
var a; // 編譯時期的工作

console.log(a); // undefined
a = 2; // 執行時期的工作

不等性(Inequality)

關於不等性的比較運算子有 ><>=<= 共四種,在這裡有幾種狀況需要注意
  • 若比較的值都是字串,則以字典的字母順序為主。
'ab' < 'cd' // true
  • 若比較的值型別不同,由於值的不等性比較沒有嚴格不相等這種情況,因此,無論什麼樣的比較都會被強制轉型為數字,無法轉為數字的就會變成 NaN。
'99' > 98 // true,字串 '99' 被強制轉型為數字 99

'Hello World' > 1 // false,字串 'Hello World' 無法轉為數字,變成 NaN
'Hello World' < 1 // false
'Hello World' = 1 // false
  • NaN 不大於、不小於、不等於任何值,當然也不等於自己。
NaN > NaN // false
NaN < NaN // false
NaN === NaN // false
NaN == NaN // false

typeof

typeof 可用於檢測值的型別是什麼。
typeof 'Hello World!' // 'string'
typeof true // 'boolean'
typeof 1234567 // 'number'
typeof null // 'object'
typeof undefined // 'undefined'
typeof { name: 'Jack' } // 'object'
typeof Symbol() // 'symbol'
typeof function() {} // 'function'
typeof [1, 2, 3] // 'object'
typeof NaN // 'number'

不懂得都趕緊看一看
https://cythilya.github.io/archieve/