js_this_arguments_callbindapply 详解

/**
 * THIS 函数的执行主体,跟执行上下文不是一个概念
 *全局的this是window
 *this跟函数在哪执行和在哪定义没有必然联系
 *
 * 可以按照一下规律确实执行主体是谁:
 *   + 给当前元素的某个事件行为绑定方法,事件触发,方法中的this是当前元素本身
 *       例如给body的click绑定方法,则方法中的this就是body,(特殊情况 ie678下基于attachEvent实现的DOM2事件绑定,绑定方法中的this不是元素本身,而是window)
 *   + 函数执行,先看函数名之前是否有‘.’,有‘.’,‘.’前是谁this就是谁,没有‘.’则this就是window(在js的严格模式下,没有'.',方法中的this是undefined)
 *       + 匿名函数(自执行函数、回调函数)一般this也是window、undefined,除非做了特殊处理
 *       + 括号表达式中有多项只取最后一项作,但是this为window
 *       + use strict;//在当前上下文起作用,只对当前和下级上下文起作用
 *         匿名函数中的this,一般都是window、undefined
 *         回调函数中的this一般都是window、undefined,除非特殊处理了
 *   + new构造函数中的this是当前实例
 *   + 箭头函数没有自己的this,用到的this是上下文中的this,以及{ }块级上下文里面没有this,如果代码中遇到this也不是函数自己的,而是它所在的上级上下文的this
 *   + 基于call\appply\bind可以强制改变this指向,对箭头函数没用
 *
 */

function fn() {
    console.log(this);
}
let obj = {"name":"xxxx", fn:fn};
fn();//this=> window
obj.fn();//this =》obj;
//..........................................
"use strict";
function fn() {
    console.log(this);
}
let obj = {"name":"xxxx", fn:fn};
fn();//this=> undefined
obj.fn();//this =》obj;

//.................................................
var num =10;
var obj = { num:20 };
obj.fn = (function (num) {
    this.num = num * 3;
    num++;
    return function (n) {
        //console.log(this);
        this.num +=n;
        num++;
        console.log(num);
    }
})(obj.num);
var fn = obj.fn;
fn(5);// 输出22   //此时的this是window,没有‘.’则this就是window
obj.fn(10);//23
console.log(num,obj.num);//65 30

[1,2].sort(function (a, b) {
    console.log(this); //window
});
[1,2].forEach(function (item,index) {
    console.log(this); //window
});
[1,2].forEach(function (item,index) { //foreach做了特殊处理,传递的第二个参数是为了改变会调函数中的this指向
    console.log(this); //obj
}, obj);

(obj.fn)(); //  this -> obj;
(10,20,obj.fn)(); //  this -> window;
//
let obj = {
    name: "obj",
    fn(){
        console.log(this); // obj
        setTimeout(function () {
            this.name = "aa"; //window.name
            console.log(this); // window 这个回调函数
        },1000);
    }
};
//
let obj = {
    name: "obj",
    fn(){
        let _this = this; // obj
        setTimeout(() => {
            _this.name = "aaa"; // _this -> obj;
            console.log(this); //window
        },1000);
    }
};
//
let obj = {
    name: "obj",
    fn(){
        let _this = this; // obj
        setTimeout(function () {
            _this.name = "aa";// obj.name
            console.log(this); // window 这个回调函数
        },1000);
    }
};
//
(function () {
    var val = 1;
    var json ={
        val:10,//属性
        dbl:function () {
          val *=2; //val是变量,不是自己的私有变量,是自执行函数创建出来的上下文中的变量
        }
    };
    json.dbl();
    alert(json.val + val);  //"12";alert输出的都是字符串
})();


//.........arguments............
var a = 4;
function b(x,y,a) {
    /**
     *  EC(b)
     *    x = 1, y=2, a =3
     *   作用域链:EC(B). EC(G)
     *   初始化this:window
     *   初始化arguments=>{0:1,1:2,3:3,length:3}
     *       在非严格模式下:初始化的arguments和行参建立一个‘映射’机制,并且映射机制只能在这个阶段建立
     *                       arguments跟传值有关,传值的才有映射关系,默认值不形成映射关系,并且只要传递实参,arguments中就有值,不传值就是一个空的数组集合
     *       严格模式下不存在映射机制
     *
     */
    console.log(a); //3
    arguments[2] = 10;
    console.log(a);// 10

}
a = b(1,2,3); //b没有返回值就是undefined
console.log(a); // undefined

//.........arguments............
var a = 4;
function b(x,y,a) {
   a = 3;
   console.log(arguments[2]); //undefined
}
a = b(1,2);

//....................call\appply\bind................................
/**
 * fun.call(obj,...args)  立即执行
 *    首先fun基于原型链找到Function.prototype.call方法,并且把call方法执行
 *    call中的this就是fn,传递给call方法的第一个实参就是未来改变fn中的this,...args是传给fun的实参信息
 *    并且接受fn执行的返回结果,作为返回值给外部
 *    fun.call(10,20);//  this-> 10 Number
 *    fun.call();fun.call(null);//如果不传参数或者传递的是null/undefinded,在js非严格模式下this-》window。在严格模式下this->undefined
 *
 * fun.apply(obj,[args])
 *    跟call没有区别,唯一区别参数只能是[10,20]数组,表现形式不一样call需要把参数一个一个传递给call,call在一个一个传递给fun
 *    apply需要把参数放到数组中传递给apply,但是apply内部也是把数组每一项一个一个传给fun
 *
 * fun.bind(obj,...args)
 *    call、apply在执行函数时会立即把函数执行,并改变this
 *    bind是预先处理,执行bind只会预先把函数需要改变的this等信息存储起来,但是此时函数并不会执行并返回一个匿名函数,
 *    当后期执行匿名函数时才会把需要执行的函数执行,并且改变this的预设值
 *
 */
const  fn = function fn(x, y) {
    console.log(this.name);
    console.log(x + y);
    return this;
}
window.name = "window";
let obj = {
    name: "obj"
}
fn(10, 20); // window  this -> window
let res = fn.call(obj, 10, 20); //obj  this-> obj
res == obj ;// true
fn.bind(obj, 10 ,20)();
let resapp = fn.apply(obj, [10,20]);
resapp == res;// true
obj.fn(); //this->obj   obj.fn is not a function

setTimeout(fn.call(obj, 10, 20), 1000);// 错误处理,这种在设置是fn.call已经执行,相当于把fn.call返回的结果绑定给定时器,会报Uncaught SyntaxError: Unexpected identifier
setTimeout(fn.bind(obj, 10 ,20), 1000);
setTimeout(function () {
    fn.call(obj, 10, 20)
},1000);

const  fn = function fn(x, y, ev) {
    console.log(this.name);
    console.log(x + y);
    return this;
};
document.body.click = fn.bind(obj, 10, 20);
document.body.click = function (ev) {
    fn.call(obj,10 ,20, ev);
};

//................fun.bind(obj,...args)自定义实现..........................

Function.prototype.bind = function bind(context, ...params) { //运行时参数是10,20
    //此时this是.bind之前的对象fn
    //context 为需要改变this成context
    // return function anonyous(...args) {
    //     //此处的this是body
    // }
    return (...args) => { //运行时 参数是 ev
        //此时this 上级上下文 fn
        this.apply(context, params.concat(args));
        // this.call(context, ...params.concat(args));
    }
};
const  fn = function fn(x, y, ev) {
    console.log(this.name);
    console.log(x + y);
    return this;
};
let obj = {
    name: "obj"
}
document.body.click = fn.bind(obj, 10, 20);


//................fun.call(obj,...args)自定义实现..........................
const  fn = function fn(x, y, ev) {
    console.log(this.name);
    console.log(x + y);
    return this;
};
let obj = {
    name: "obj"
}
//fn 和obj 本来没关系
obj.fn = fn;
obj.fn(10, 20);//此时中this->obj;
delete obj.fn;
call实现同理
Function.prototype.call = function call(context, ...params) {
    //细节点:对于context类型处理,基本数据类型无法设置键值对
    context == null ? window : null;
    if(!/^(object|function)$/i.test(typeof context)){
        //基本数据类型
        // context = new context.constructor(context); //但是对于Symbol\BigInt不友好,没有构造函数
        context = Object(context);//直接变成引用类型值
    }

    let res;
    //this -> fn
    //context->obj 为需要改变this成context
    // context['my_' + this.name] = this; // this-> fn //可能产生命名冲突
    let key = Symbol("key");
    context[key] = this;
    res = context[key](...params);//函数中this->obj
    delete context[key];
    return res;
};
const  fn = function fn(x, y) {
    console.log(this.name);
    console.log(x + y);
    return this;
};
let obj = {
    name: "obj"
}
let res = fn.call(obj, 10, 20);


//......call 深层理解..................基于Function.prototype.call = function call(context, ...params)分析即可
var name = "zhuzhu";
function A(x, y) {
    var res = x + y;
    console.log(res, this.name);
}
function B(x, y) {
    var res = x - y;
    console.log(res, this.name);
}
B.call(A, 40 , 30); // 10 "A"
B.call.call.call(A, 20, 10); // NaN, undefined
//B.call.call.call(A, 20, 10);
//开始 this->B.call.call   context->A
// A.key = call
//相当于把call.call(A,20, 10)执行  A.key= call 相当于 A.call(10, 20) 即CALL第二次执行
// this->A  context-> new Number(20)   params-> 10
//结果 NaN, undefined
Function.prototype.call(A, 60, 50);  //没有任何输出
//Function.prototype.call(A, 60, 50);
// 开始 this->Function.prototype context->A parmas->60,50
//A.key = Function.prototype  相当于 Function.prototype(60,50)
//Function.prototype 为匿名空函数 即啥都不处理即没有任何输出
Function.prototype.call.call.call(A, 80, 70);// NaN, undefined
//Function.prototype.call.call.call(A, 80, 70);
// 开始 this->Function.prototype.call.call context->A parmas->80, 70
//A.key = Function.prototype.call.call  相当于 call.call(A, 80, 70) 即CALL第二次执行
// this->A  context-> new Number(80)   params-> 70
//结果 NaN, undefined

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值