<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>call apply</title>
</head>
<body>
<div id="div1">div1</div>
<!----------------------------------------------------------------------------------------------------------------------
它們各自的定義:
apply:應用某一對象的一個方法,用另外一個對象替換當前對象。例如:B.apply(A, arguments);即A對象應用B對象的方法。
call:調用一個對象的一個方法,以另外一個對象替換當前對象。例如:B.call(A, args1,args2);即A對象調用B對象的方法。
它們的共同之處:
都「能夠用來代替另外一個對象調用一個方法,將一個函數的對象上下文從初始的上下文改變爲由thisObj指定的新對象」。
它們的不一樣之處:
apply:最多隻能有兩個參數——新this對象和,第二個參數爲一個帶下 標的集合,這個集合能夠爲數組,也能夠爲類數組。
若是給該方法傳遞多個參數,則把參數都寫進這個數組裏面,
固然,即便只有一個參數,也要寫進數組裏。若是argArray不是一個有效的數組或arguments對象,那麼將致使一個TypeError。
若是沒有提供argArray和thisObj任何一個參數,那麼Global對象將被用做thisObj,而且沒法被傳遞任何參數。
call:它能夠接受多個參數,第一個參數與apply同樣,後面則是一串參數列表。這個方法主要用在js對象各方法相互調用的時候,
使當前this實例指針保持一致,或者在特殊狀況下須要改變this指針。若是沒有提供thisObj參數,那麼 Global 對象被用做thisObj。
實際上,apply和call的功能是同樣的,只是傳入的參數列表形式不一樣。
---------------------------------------------------------------------------------------------------------------------------->
<!--實現繼承-->
<script>
function Animal(name){
this.name = name;
this.showName = function () {
console.log(this.name);
};
}
Animal.prototype = {
publicProperty:function (arg1) {
console.log(arg1);
}
};
function Dog(speak) {
Animal.apply(this,[speak]);
}
Dog.prototype = new Animal();
var dog1 = new Dog("旺旺");
console.log(dog1);
dog1.showName();
dog1.publicProperty("dog1 publicProperty");
var dog2 = new Animal("animal");
console.log(dog2);
dog2.publicProperty("dog2");
</script>
<!--實現多重繼承-->
<script>
function Add() {
this.showAdd = function (a,b) {
console.log(a + b);
}
}
function Sub() {
this.showSub = function (c, d) {
console.log(c - d);
}
}
function Add_Sub(a,b) {
Add.apply(this,[a,b]);
Sub.apply(this,[a,b]);
}
var aa = new Add_Sub();
aa.showAdd(43,67);
aa.showSub(66,66);
</script>
<!--apply的一些其餘巧妙用法-->
<script>
//獲得數組中最大(小)的一項;這塊在調用的時候第一個參數給了null,這是由於沒有對象去調用這個方法,我只須要用這個方法幫我運算,獲得返回的結果就行,因此直接傳遞了一個null過去。
//當使用 call 或者 apply 的時候,若是咱們傳入的第一個參數爲 null,函數體內的 this 會指 向默認的宿主對象,在瀏覽器中則是 window
//有時候咱們使用 call 或者 apply 的目的不在於指定 this 指向,而是另有用途,好比借用其 他對象的方法。那麼咱們能夠傳入 null 來代替某個具體的對象
var arr = [1,2,3,4,5];
Math.min.apply(null,arr);
Math.max.apply(null,arr);
//Array.prototype.push能夠實現兩個數組的合併
var arr1 = [1,2,3];
var arr2 = [4,5,6,7,8,9];
Array.prototype.push.apply(arr1,arr2);
console.log(arr1);
</script>
<script>
//四。原型式繼承
//這種繼承藉助原型 *****並基於已有的對象建立新對象*****,
//同時還沒必要所以建立自定義類型
function obj(o) { //傳遞一個字面量函數
function F() {} //建立一個構造函數
F.prototype = o; //把字面量函數賦值給構造函數的原型
return new F(); //最終返回出實例化的構造函數
}
var box = { //字面量對象
name : 'Lee',
arr : ['哥哥','妹妹','姐姐']
};
var box1 = obj(box); //傳遞
console.log(box1);
console.log(box1.name); // =>"Lee"
box1.name = 'Jack';
console.log(box1.name); // =>"Jack"
console.log(box1.arr); // =>['哥哥','妹妹','姐姐']
box1.arr.push('父母');
console.log(box1.arr); // =>['哥哥','妹妹','姐姐','父母']
var box2 = obj(box); //傳遞
console.log(box);
console.log(box2.name); // =>"Lee"
console.log(box2.arr); //引用類型共享了 =>['哥哥','妹妹','姐姐','父母']
</script>
<!--------------------------------------------------------------------------------------------------------------------->
<script>
/*********** Array.prototype.slice.call(arguments) *********************************/
//Array.prototype.slice.call(arguments)能將具備length屬性的對象轉成數組,
// 除了IE下的節點集合(由於ie下的dom對象是以com對象的形式實現的,js對象與com對象不能進行轉換)
var a={length:2,0:'first',1:'second'};
Array.prototype.slice.call(a);// ["first", "second"]
var a={length:2};
Array.prototype.slice.call(a);// [undefined, undefined]
//slice有兩個用法,一個是String.slice,一個是Array.slice,第一個返回的是字符串,第二個返回的是數組,
//Array.prototype.slice.call(arguments)可以將arguments轉成數組,那麼就是arguments.toArray().slice();
//是否是就能夠說Array.prototype.slice.call(arguments)的過程就是先將傳入進來的第一個參數轉爲數組,再調用slice?
var b = function () {
console.log(this); // 'littledu'
console.log(typeof this); // Object
console.log(this instanceof String); // true
};
b.call('littledu');
//咱們能夠大膽猜一下slice的內部實現,以下:
Array.prototype.slice = function (start, end) {
var result = new Array();
start = start || 0;
end = end || this.length; //this指向調用的對象,當用了call後,可以改變this的指向,也就是指向傳進來的對象,這是關鍵
for (var i = start; i < end; i++) {
result.push(this[i]);
}
return result;
};
//轉成數組的通用函數
var toArray = function (s) {
try {
return Array.prototype.slice.call(s);
} catch (e) {
var arr = [];
for (var i = 0, len = s.length; i < len; i++) {
//arr.push(s[i]);
arr[i] = s[i]; //聽說這樣比push快
}
return arr;
}
};
</script>
<script>
/*********************************** Function.prototype.bind**************************************************/
var id = "window";
document.getElementById( 'div1' ).onclick = function(){
console.log( this.id ); // 輸出:div1
var func = function(){
console.log( this.id ); // 輸出:window,此時的this指向window
};
func();
func.call(this); //輸出div1
};
//使用 call 來修正 this 的場景,好比修正 document.getElementById 函數內部「丟失」的 this,代碼以下:
document.getElementById = (function( func ){
return function(){
return func.apply( document, arguments );
}
})( document.getElementById );
var getId = document.getElementById;
var div = getId( 'div1' );
console.log ( div.id ); // 輸出: div1
//大部分高級瀏覽器都實現了內置的 Function.prototype.bind,用來指定函數內部的 this指向,
// 即便沒有原生的 Function.prototype.bind 實現,咱們來模擬一個也不是難事
Function.prototype.bind = function( context ){
var self = this; // 保存原函數
return function(){ // 返回一個新的函數
return self.apply( context, arguments ); // 執行新的函數的時候,會把以前傳入的 context
// 看成新函數體內的 this
}
};
var obj = {
name: 'sven'
};
var func = function(){
console.log( this.name ); // 輸出:sven
}.bind(obj);
func();
var log = console.log.bind(console);
log(6666);
//咱們經過 Function.prototype.bind 來「包裝」func 函數,而且傳入一個對象 context 看成參數,這個 context 對象就是咱們想修正的 this 對象。
//在 Function.prototype.bind 的內部實現中,咱們先把 func 函數的引用保存起來,而後返回一 個新的函數。當咱們在未來執行 func 函數時,
// 實際上先執行的是這個剛剛返回的新函數。在新 函數內部,self.apply( context, arguments )這句代碼纔是執行原來的 func 函數,
// 而且指定 context 對象爲 func 函數體內的 this。這是一個簡化版的 Function.prototype.bind 實現,
// 一般咱們還會把它實現得稍微複雜一點, 使得能夠往 func 函數中預先填入一些參數:
Function.prototype.bind = function(){
var self = this, // 保存原函數
context = [].shift.call( arguments ), //獲取傳入的第一個參數即arguments[0],亦即 須要綁定的 this 上下文,即下面例子中的func中傳入的obj
args = [].slice.call( arguments ); // 剩餘的參數轉成數組,即將下面func例子中的參數obj後面的1,2參數轉爲數組[1,2]
return function(){ // 返回一個新的函數
return self.apply( context, [].concat.call( args, [].slice.call( arguments ) ) );
// 執行新的函數的時候,會把以前傳入的 context 看成新函數體內的 this
// 而且組合兩次分別傳入的參數,做爲新函數的參數,即下面的func例子中組合bind中傳入的1,2和func中傳入的3,4
}
};
var obj = {
name: 'sven'
};
var func = function( a, b, c, d ){
console.log( this.name ); // 輸出:sven
console.log( [ a, b, c, d ] ) // 輸出:[ 1, 2, 3, 4 ]
}.bind( obj, 1, 2 );
func( 3, 4 );
/********************************借用其餘對象的方法**************************************************/
//借用方法的第一種場景是「借用構造函數」,經過這種技術,能夠實現一些相似繼承的效果:
var A = function( name ){
this.name = name;
};
var B = function(){
A.apply( this, arguments );
};
B.prototype.getName = function(){
return this.name;
};
var b = new B( 'sven' );
console.log( b.getName() ); // 輸出: 'sven'
//借用方法的第二種運用場景跟咱們的關係更加密切。函數的參數列表 arguments 是一個類數組對象,雖然它也有「下標」,
// 但它並不是真正的數組,因此也不能像數組同樣,進行排序操做或者往集合裏添加一個新的元素。
// 這種狀況下,咱們經常會借用 Array.prototype 對象上的方法。好比想往 arguments 中添加一個新的元素,一般會借用 Array.prototype.push:
(function(){
Array.prototype.push.call( arguments, 3 );
console.log ( arguments ); // 輸出[1,2,3]
})( 1, 2 );
//在操做 arguments 的時候,咱們常常很是頻繁地找 Array.prototype 對象借用方法。想把 arguments 轉成真正的數組的時候,
// 能夠借用 Array.prototype.slice 方法;想截去 arguments 列表中的頭一個元素時,又能夠借用 Array.prototype.shift 方法。
// 那麼這種機制的內部實現原理是什麼呢?咱們不妨翻開 V8 的引擎源碼,以 Array.prototype.push 爲例,看看 V8 引 擎中的具體實現:
function ArrayPush() {
var n = TO_UINT32( this.length ); // 被 push 的對象的 length
var m = %_ArgumentsLength(); // push 的參數個數
for (var i = 0; i < m; i++) {
this[ i + n ] = %_Arguments( i ); // 複製元素 (1)
}
this.length = n + m; // 修正 length 屬性的值 (2)
return this.length;
}
//經過這段代碼能夠看到,Array.prototype.push 其實是一個屬性複製的過程,把參數按照 下標依次添加到被 push 的對象上面,
// 順便修改了這個對象的 length 屬性。至於被修改的對象是誰,究竟是數組仍是類數組對象,這一點並不重要。
// 由此能夠推斷,咱們能夠把「任意」對象傳入 Array.prototype.push:
var a = {};
Array.prototype.push.call( a, 'first' );
console.log ( a.length ); // 輸出:1
console.log(a); //輸出{0:"first",length:1}
console.log ( a[ 0 ] ); // first
//這段代碼在絕大部分瀏覽器裏都能順利執行,但因爲引擎的內部實現存在差別,若是在低版 本的 IE 瀏覽器中執行,必須顯式地給對象 a 設置 length 屬性:
var a = {
length: 0
};
//前面咱們之因此把「任意」兩字加了雙引號,是由於能夠借用 Array.prototype.push 方法的對象還要知足如下兩個條件,從 ArrayPush 函數的(1)處和(2)處也能夠猜到,這個對象至少還要知足:
//對象自己要能夠存取屬性
//對象的 length 屬性可讀寫
//對於第一個條件,對象自己存取屬性並無問題,但若是借用 Array.prototype.push 方法的不是一個 object 類型的數據,而是一個 number 類型的數據呢? 咱們沒法在 number 身上存取其餘數據,那麼從下面的測試代碼能夠發現,一個 number 類型的數據不可能借用到 Array.prototype. push 方法:
var a = 1;
Array.prototype.push.call( a, 'first' );
console.log ( a.length ); // 輸出:undefined
console.log ( a[ 0 ] ); // 輸出:undefined
//對於第二個條件,函數的 length 屬性就是一個只讀的屬性,表示形參的個數,咱們嘗試把 一個函數看成 this 傳入 Array.prototype.push:
var func = function(){};
Array.prototype.push.call( func, 'first' );
console.log ( func.length );
// 報錯:cannot assign to read only property ‘length’ of function(){}
</script>
<script>
/*************************** call() apply() bind() 區別 ***************************************************************/
// 相同點:都是改變函數內部的this指向
// 區別一、
// call和apply傳參形式不一樣:
//用call和apply方法,this指向他們的第一個參數,apply的第二個參數是一個參數數組,call的第二個及之後的參數都是數組裏面的元素,須要所有列舉。例如:
var numbers = [5,458,120,-215];
var maxNumbers = Math.max.apply(Math,numbers); // 458
var maxInNumbers = Math.max.call(Math,5,458,120,-215); // 458
//獲取數組中的最大值和最小值,利用他們擴充做用域擁有Math的min和max方法;因爲沒有什麼對象調用這個方法,因此第一個參數能夠傳null或者自己;
// 區別二、
//bind不會當即調用:
//bind與apply、call最大的區別就是bind不會當即調用,其餘兩個會當即調用;bind是返回對應函數,便於稍後調用,apply、call是當即調用;
// bind是新建立一個函數,而後把它的上下文綁定到bind()括號中的參數上,而後將它返回。例如:
var button = document.getElementById("btn");
var text = document.getElementById("text");
button.onclick = function(){
console.log(this.id); // text
}.bind(text);
// bind是隻有點擊button的時候,纔會調用函數,而call和apply是當即調用,頁面刷新就調用。
// 注意若是call和apply的第一個參數寫的是null,那麼this指向的是window對象
// 固然還會有不少變體,像用apply實現bind,用原生js封裝bind和apply方法,下面附上代碼:
/***** 利用apply實現bind *************************************************************/
Function.prototype.testBind = function(that){
var _this = this,
slice = Array.prototype.slice,
args = slice.apply(arguments,[1]);
return function () {
return _this.apply(that,args.concat(Array.prototype.slice.apply(arguments,[0])))
}
};
/******* 原生js實現call ????????************************************************************/
Function.prototype.newCall = function(){
var ctx = arguments[0] || window; // 第一個參數是改變this的對象
ctx.fn = this; // 將該函數賦給傳入的對象
var args = [];
for(var i =0 ;i<arguments.length;i++){
args.push("arguments[" + i + "]");
}
var result = eval("ctx.fn(" + args.join(",") + ")");
delete ctx.fn;
return result;
};
/********** 原生js實現apply *************************************************************/
Function.prototype.newApply = function (ctx,arr) {
var ctx = arguments[0] || window;
ctx.fn = this;
var result;
if(!arr){
result = ctx.fn();
}else{
var args = [];
for(var i =0 ;i<arguments[1].length;i++){
args.push("arguments[1][" + i + "]");
}
result = eval("ctx.fn(" + args.join(",") + ")");
}
delete ctx.fn;
return result;
}
</script>
</body></html>