<!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>