this含义
执行上下文有且只有三类,全局执行上下文,函数上下文,与eval上下文;由于eval一般不会使用,这里不做讨论
全局执行上下文只有一个,在客户端中一般由浏览器创建,也就是我们熟知的window对象,
我们能通过this直接访问到它。
同时window对象还是var声明的全局变量的载体。我们通过var创建的全局对象,都可以通过window直接访问
函数执行上下文可存在无数个,每当一个函数被调用时都会创建一个函数上下文;需要注意的是,同一个函数被多次调用,都会创建一个新的上下文。
执行上下文栈(下文简称执行栈)也叫调用栈,执行栈用于存储代码执行期间创建的所有上下文,具有LIFO(Last In First Out后进先出,也就是先进后出)的特性。
JS代码首次运行,都会先创建一个全局执行上下文并压入到执行栈中,之后每当有函数被调用,都会创建一个新的函数执行上下文并压入栈内;由于执行栈LIFO的特性,所以可以理解为,JS代码执行完毕前在执行栈底部永远有个全局执行上下文。
1.全局执行上下文一般由浏览器创建,代码执行时就会创建;函数执行上下文只有函数被调用时才会创建,调用多少次函数就会创建多少上下文。
2.调用栈用于存放所有执行上下文,满足FILO规则。
3.执行上下文创建阶段分为绑定this,创建词法环境,变量环境三步,两者区别在于词法环境存放函数声明与const let声明的变量,而变量环境只存储var声明的变量。
4.词法环境主要由环境记录与外部环境引入记录两个部分组成,全局上下文与函数上下文的外部环境引入记录不一样,全局为null,函数为全局环境或者其它函数环境。环境记录也不一样,全局叫对象环境记录,函数叫声明性环境记录。
5.你应该明白了为什么会存在变量提升,函数提升,而let const没有。
6.ES3之前的变量对象与活动对象的概念在ES5之后由词法环境,变量环境来解释,两者概念不冲突,后者理解更为通俗易懂。
1.在全局环境中的this——window
"use strict"console.log(this); //windowconsole.log(this===window);//true
2.在函数内部,this的值取决于函数被调用的方式。
函数内部中的 this 指向谁,不是在函数定义时决定的,而是在函数第一次调用并执行的时候决定的<script>function mycall(){console.log(54,this) //window}mycall()</script>
2.1在函数中的this——window
在函数内部,this的值取决于函数被调用的方式。在函数中的this——windowfunction f() {console.log(this); //windowconsole.log(this===window);//true}f()console.log(f()===window.f()); //true
2.2函数在严格模式下——undefined
function f() {"use strict"console.log(this); //undefinedconsole.log(this === window); //false}f()
3.对象中的this——指向调用者
function fun() {console.log(this.name);}let obj = {name: 'objName',fn: fun}var name = "windowName"obj.fn() //objNamefun() //windowNamefunction foo() {console.log(this.a);}var obj2 = {a: 2,fn: foo}var obj1 = {a: 1,o1: obj2}obj1.o1.fn() //2
4.构造函数中的this——当前实例化对象
var obj = new a();通过这条语句新创建了一个对象,把函数中的this指向了obj。
function Animal(name) {this.type="动物"this.name = namethis.say = function () {console.log(this);}}var cat = new Animal('Cat')cat.say() //{name:'Cat',type:'动物',say:f()}
5.定时器中的this——window
II.改变this方法
A.bind方法
bind() 方法创建一个新的函数,在 bind() 被调用时,这个新函数的 this 被指定为 bind()的第一个参数,而其余参数将作为新函数的参数,供调用时使用。function.bind(thisArg[, arg1[, arg2[, ...]]])const module = {x: 42,getX: function () {return this.x;}};const unboundGetX = module.getX;console.log(unboundGetX());//unboundGetX普通函数this指向windonw 环境不存在x undefinedconst boundGetX = unboundGetX.bind(module); //把unboundGetX的this指向moduleconsole.log(boundGetX()); //42
B.call方法
call() 方法使用一个指定的 this 值和单独给出的一个或多个参数来调用一个函数。function.call(thisArg, arg1, arg2, ...)var person = {fullName: function (city, country) {console.log( this.firstName + " " + this.lastName + "," + city + "," + country)return this.firstName + " " + this.lastName + "," + city + "," + country;}}var person1 = {firstName: "Bill",lastName: "Gates"}person.fullName.call(person1);//Bill Gates,undefined,undefinedperson.fullName.call(person1, "Seattle", "USA");//Bill Gates,Seattle,USA
B.1 手写call
/*** 首先需要处理this指向(先临时存储指向当前函数的this)* 再需要考虑数据类型的关系* 执行函数* 清除绑定关系this*/Function.prototype.callFun = function (_argObj, ...args) {const _that = this;// 判断_argObj是否存在并且是否属于Object_argObj = (_argObj && _argObj instanceof Object) ? _argObj : window;_argObj.fun = _that;// 执行_argObj下的fun函数_argObj.fun(...args);// 执行完再清除_argObj下的fun属性delete _argObj.fun;}function bar() {console.log('bar_this', this)console.log('bar(): ', ...arguments); // 打印参数}var obj = {name: 'cky',age: 24}bar.callFun(obj, obj.name, obj.age);
C.apply方法
apply() 方法调用一个具有给定 this 值的函数,以及以一个数组(或类数组对象)的形式提供的参数。func.apply(thisArg, [argsArray])var person = {fullName: function (city, country) {console.log( this.firstName + " " + this.lastName + "," + city + "," + country)return this.firstName + " " + this.lastName + "," + city + "," + country;}}var person1 = {firstName: "Bill",lastName: "Gates"}person.fullName.apply(person1);//Bill Gates,undefined,undefinedperson.fullName.apply(person1, ["深圳", "中国"]);//Bill Gates,深圳,中国
C.1手写apply
D.call和apply区别
方法定义:apply:调用一个对象的一个方法,用另一个对象替换当前对象。例如:B.apply(A, arguments);即A对象应用B对象的方法。call:调用一个对象的一个方法,用另一个对象替换当前对象。例如:B.call(A, args1,args2);在JavaScript中,call和apply作用是一样的,都是为了改变某个函数运行时的上下文(context)而存在的,换句话说,就是为了改变函数体内部this的指向即A对象调用B对象的方法。
call 与 apply 的相同点:方法的含义是一样的,即方法功能是一样的;第一个参数的作用是一样的;call 与 apply 的不同点:两者传入的列表形式不一样call可以传入多个参数;apply只能传入两个参数,所以其第二个参数往往是作为数组形式传入
传递参数的方式。用法上不同,主要是参数不完全同注意call方法中的参数arg:a) arg的个数为零或多个;b) arg可以是任何类型,包括Array。call在这里译为“调用”,在JS中可以理解为“A对象调用B对象的某个方法”;注意apply方法中的参数args:a) args是Array对象的一个实例,也就是一个数组;b) args的个数为零(就是说不起作用)或1个;。apply在这里译为“应用”,即“A对象应用B对象的某个方法”。
E.总结
apply 、 call 、bind 三者都是用来改变函数的this对象的指向的;apply 、 call 、bind 三者第一个参数都是this要指向的对象,也就是想指定的上下文;apply 、 call 、bind 三者都可以利用后续参数传参;bind 是返回对应函数,便于稍后调用;apply 、call 则是立即调用
