/**
* bind 函式在js中的應用
*/
this.name = "test";
let testObj = {
	name:'zhangsan',
	introduce:function(){
		return this.name;
	}
}
let test = {
	name:"lisi"
}
let test1 = {
	name:"wangwu"
}
let fn = testObj.introduce;
console.log(fn());//test   
console.log(fn.bind(test)());//lisi
console.log(fn.bind(test1)());//王五
console.log(fn());//test 注:不存在永久改變this指向的說法  


/**
	*bind()的另一個最簡單的用法是使一個函式擁有預設的初始引數。這些引數(如果有的話)作為bind()的第二個引數             跟在this(或其他物件)後面,之後它們會被插入到
	*目標函式的引數列表的開始位置,傳遞給繫結函式的引數會跟在它們的後面。
*/
function list() {
  return Array.prototype.slice.call(arguments);
}


var list1 = list(1, 2, 3); // [1, 2, 3]


var leadingThirtysevenList = list.bind(undefined, 37,21);


var list2 = leadingThirtysevenList(); // [37]
var list3 = leadingThirtysevenList(1, 2, 3); // [37,21, 1, 2, 3]
var list4 = leadingThirtysevenList(1, 2, 3, 4); // [37,21, 1, 2, 3,4]

/**
* 來看es6中的函式初始值 
*/
function EsArguments(name='zhangsan'){
	console.log(name);
}
EsArguments();//zhangsan
EsArguments("lisi");//lisi


/**
* 在自定義物件中的應用  demo 
* 跟es6中函式的初始值的區別在於不會覆蓋原始值 
*/
function testBind(){
	this.cacheArgs = this.transformArgs.bind(undefined,...arguments);
};
testBind.prototype.transformArgs = function(){
	return Array.prototype.slice.call(arguments);
}
testBind.prototype.showArgList = function(){
	return this.cacheArgs(...arguments);
}
 let Test = new testBind(1,2,3); 
 console.log(Test.showArgList(13,14));
 console.log(Test.showArgList(5,21));


/* bind和call apply的區別在於不會立即呼叫 原因在於 bind() 函式會建立一個新函式(稱為繫結函式),
* 新函式與被調函式(繫結函式的目標函式)具有相同的函式體。
* bind一般用在非同步呼叫和事件
* ->下面看bind函式的原生實現方式 
*/
Function.prototype.bind = function() {
        var self = this, // 儲存原函式執行上下文中的this指向 
        context = Array.prototype.shift.call(arguments), // 需要繫結的this上下文
        args = Array.prototype.slice.call(arguments); // 剩餘的引數轉成陣列
        return function() { //返回一個新函式
        // 執行新函式時,將傳入的上下文context作為新函式的this
        // 並且組合兩次分別傳入的引數,作為新函式的引數
		return self.apply(context, Array.prototype.concat.call(args, Array.prototype.slice.call(arguments))); 
      }
};
/**bind 官方文件解釋
* 語法
* fun.bind(thisArg[, arg1[, arg2[, ...]]])
* 引數thisArg

* 當繫結函式被呼叫時,該引數會作為原函式執行時的 this 指向。當使用new 操作符呼叫繫結函式時,該參

                    數 無 效。 (注:原因在於new操作會建立一個空物件)

* arg1, arg2, ...
* 當繫結函式被呼叫時,這些引數將置於實參之前傳遞給被繫結的方法。

* 返回值

                 * 返回由指定的this值和初始化引數改造的原函式拷貝
*/

/**
  * bind函式在setTimeout中的應用
*/
let testBingMethod = {
	name:'123',
	test:function(){
		console.log(this.name);
},
showList:function(){
	setTimeout(this.test.bind(this),1000);
//第一個this為當前物件執行上文的this 
//第二個this為setTimeout函式的this指向即window物件
	}
}
testBingMethod.showList()
/**
  * bind作為建構函式使用的繫結函式

  * 自然而然地,繫結函式適用於用new操作符 new 去構造一個由目標函式建立的新的例項。當一個繫結函式是用來

                    構建一個值的,原來提供的 this 就會被忽略。然而, 

  * 原先提供的那些引數仍然會被前置到建構函式呼叫的前面。 <---原因在於new操作會建立一個空物件-->
  */
function Point(x, y) {
  this.x = x;
  this.y = y;
}


Point.prototype.toString = function() { 
  return this.x + ',' + this.y; 
};


var p = new Point(1, 2);
p.toString(); // '1,2'


// 以下這行程式碼在 polyfill 不支援,polyfill下必須指向一個新的物件 
// 在原生的bind方法執行沒問題: 
var YAxisPoint = Point.bind(null, 0/*x*/);
var axisPoint = new YAxisPoint(5);
axisPoint.toString(); // '0,5'


/**
*bind 的快捷呼叫

*你可以用 Array.prototype.slice 來將一個類似於陣列的物件(array-like object)轉換成一個真正的陣列,就

                            拿它來舉例子吧。你可以建立這樣一個捷徑:

*/
function testFn(){
	var slice = Array.prototype.slice;
	slice.apply(arguments);

	/* 用 bind()可以使這個過程變得簡單。在下面這段程式碼裡面,slice 是 Function.prototype 的 apply() 方

	法的繫結函式,並且將 Array.prototype 的 

	*slice() 方法作為 this 的值。這意味著我們壓根兒用不著上面那個 apply()呼叫了。
	*/ 
	var unboundSlice = Array.prototype.slice;
	var slice = Function.prototype.apply.bind(unboundSlice);
	slice(arguments);
}