温故而知新,重新记录学习js执行环境和this,以及bind,apply,call手动实现,方便以后巩固。
如何理解JS中得执行环境(上下文)?
- 执行环境(Execution context,EC)执行上下文,定义了变量或函数有权访问的数据集合,并决定他们各自的行为。js为每个执行环境关联一个变量对象。环境中定义的所有变量和函数都保存在这个对象中。虽然我们编写的代码无法访问这个对象,但解析器在处理数据时会在后台使用它。
注意:【在函数执行环境中,VO 是不能直接访问的,则将其活动对象AO(activation object)作为变量对象。AO 是在进入函数的执行环境时创建的,并为该对象初始化一个 arguments 对象(这个对象在全局环境是不存在的!)】。
- 执行环境分为三种:全局执行环境,函数执行环境,evel()执行环境。
- 执行环境由变量对象,[[Scope]]属性(执行作用域链),this指针(用来指向一个环境对象)
- 活动对象AO = 变量对象VO + 函数的parameters + arguments特殊对象。只有全局变量的变量对象允许通过 VO 的属性名称间接访问(全局对象:
- Web 浏览器中,全局执行环境就是 window 对象
- nodejs 的中,全局执行环境就是global 对象。
- 执行环境分析:
- 全局执行环境是最外围的执行环境,全局执行环境被认为是window对象,因此所有的全局变量和函数都作为window对象的属性和方法创建的【代码载入浏览器时,全局环境被创建,当我们关闭网页或者浏览器时全局执行环境才被销毁】。
- js的执行顺序是根据函数的调用来决定的,当一个函数被调用时,该函数环境的变量对象就被压入一个执行栈中。而在函数执行之后,栈将该函数的变量对象弹出,该环境就被销毁,保存在其中的所有变量和函数定义也随之销毁,再把控制权交给之前的执行环境变量对象。
说说JS中的作用域?
-
[[Scope]]作用域: 变量的作用域分为两种: 全局变量:最外层函数定义的变量,拥有全局作用域,任何内部函数都可以访问。 局部变量:局部作用域一般只在固定代码片段内可以访问到,而对于函数外部是无法访问。函数内部声明变量要用var,否则会被认为是全局变量
-
作用域链[[Scopr Chain]]:根据内部函数可以访问外部函数变量的这种机制,用链式查找可以决定哪些数据内别内部函数访问。
-
分析: 当某个函数第一次被调用时,就会创建一个执行环境EC以及相应的作用域链,并把作用域链赋值给以一个特殊的内部属性[Scope]。然后使用this,arguments和其他命名的参数来初始化函数的活动对象AO,当前执行环境的变量对象始终在作用域链的第0位。
JS中什么情况下会出现变量提升?
- JavaScript引擎的工作方式是,先解析代码,获取所有被声明的变量,然后再一行一行地运行。这造成的结果,就是所有的变量的声明语句,都会被提升到代码的头部,这就叫做变量提升(hoisting)
- 注意事项:
- 如果函数和变量声明时同一个变量,最终会为函数
function a() {
console.log(1);
}
var a;
console.log(a);//答案为函数。
//如果把var a 变成 var a=10,最终输出10;
复制代码
- 函数和变量相比,声明函数才会被优先提升
console.log(a);//输出函数
function a() {
console.log(1);
}
var a=10;
console.log(a);
复制代码
- 声明式函数才能整个函数提升,赋值式函数是不能整个函数提升
function a(){
b();//可以执行,整个函数提升了
c();//不可以执行,因为c提升了,但是值为undefined。
function b(){
console.log('b');
}
var c=function(){
console.log('c');
}
}
a();
复制代码
- ES5中的var 和 function 的声明都存在变量提升,ES6中的 let 、 const 则不存在有变量提升。
实战:以下代码输出什么?为什么?
var name = '梦回';
(function () {
//相当于把var name=undefined提升到这里
if (typeof name === 'undefined') {
var name = '前端';
console.log('hello' + name);
} else {
console.log('World' + name);
}
})()
//输出'hello前端'
//因为在匿名函数的作用域中,name被提升到匿名函数内部的顶端,
//在函数作用域中已经有name,就不会去获取全局的name
//但是此时的值为undefined。
复制代码
如何理解this,简述JS中的this指向?
- this就是函数运行时所在的环境对象。this的指向在函数定义的时候是确定不了的,只有函数执行的时候才能确定this到底指向谁,实际上this的最终指向的是那个调用它的对象
- 纯粹的函数调用。在js的严格版中this指向不是window,是undefined。
var x = 1; function test() { console.log(this.x); } test(); // 1 this指向window 复制代码
- 作为对象方法的调用。如果一个函数中有this,这个函数有被上一级的对象所调用,那么this指向的就是上一级的对象
function test() { console.log(this.x); } var obj = {}; obj.x = 1; obj.m = test; obj.m(); // 1 this指向obj 复制代码
- 作为构造函数调用,this表示new生成的对象,会进行如下操作:
- 创建一个空的简单JavaScript对象(即{})
- 链接该对象(即设置该对象的构造函数)到另一个对象
- 将步骤1新创建的对象作为this的上下文
- 如果该函数没有返回对象,则返回this
function test() { this.x = 1; } var obj = new test(); obj.x // 1 this指向obj //特殊的例子: function test1() { this.x = 1; return {}; } var a = new test1(); console.log(a.x); //undefined //如果返回值不是一个对象那么this还是指向函数的实例。 复制代码
- apply 调用。是函数的一个方法,作用是改变函数的调用对象。它的第一个参数就表示改变后的调用这个函数的对象。因此,这时this指的就是这第一个参数。为空表示window
var x = 0; function test() { console.log(this.x); } var obj = {}; obj.x = 1; obj.m = test; obj.m.apply() // 0 this指向window 复制代码
参考 参考
JS中如何改变this指向?
- new关键字改变this指向
- 使用bind,call,apply。call和apply与bind区别,call和apply改变了函数的this上下文后便执行该函数,而bind则是返回改变了上下文后的一个函数。而call和apply的区别是第二个参数的区别,apply是第二个必须是数组,call是除了第一个参数以外还可以添加多个参数
实现call,apply,bind函数?
Function.prototype.myBind = function() {
var _this = this;
var context = [].shift.call(arguments);// 保存需要绑定的this上下文
var args = [].slice.call(arguments); //剩下参数转为数组
console.log(_this, context, args);
return function() {
return _this.apply(context, [].concat.call(args, [].slice.call(arguments)));
}
};
/**
* 每个函数都可以调用call方法,来改变当前这个函数执行的this关键字,并且支持传入参数
*/
Function.prototype.myCall = function(context) {
//第一个参数为调用call方法的函数中的this指向
var context = context || global;
//将this赋给context的fn属性
context.fn = this;//此处this是指调用myCall的function
var arr = [];
for (var i=1,len=arguments.length;i<len;i++) {
arr.push("arguments[" + i + "]");
}
//执行这个函数,并返回结果
var result = eval("context.fn(" + arr.toString() + ")");
//将this指向销毁
delete context.fn;
return result;
}
/**
* apply函数传入的是this指向和参数数组
*/
Function.prototype.myApply = function(context, arr) {
var context = context || global;
context.fn = this;
var result;
if (!arr) {
result = context.fn(); //直接执行
} else {
var args = [];
for (var i=0,len=arr.length;i<len;i++) {
args.push("arr[" + i + "]");
}
result = eval("context.fn([" + args.toString() + "])");
}
//将this指向销毁
delete context.fn;
return result;
}
复制代码
参考 参考 参考
ES6新增的箭头函数能改变this指向么?箭头函数使用场合?
-
不能。函数体内的this对象,就是定义时所在的对象,而不是使用时所在的对象。绑定定义时所在的作用域,而不是指向运行时所在的作用域。对象中使用箭头函数,该this为window(最近的作用域对象),对象不构成单独的作用域
-
使用场合:箭头函数适合于无复杂逻辑或者无副作用的纯函数场景下,例如:用在 map、reduce、filter 的回调函数定义中。
-
不使用场景:
- 箭头函数不适合定义对象的方法(对象字面量方法、对象原型方法、构造器方法),因为箭头函数没有自己的 this,其内部的 this 指向的是外层作用域的 this。
- 箭头函数不适合定义结合动态上下文的回调函数(事件绑定函数),因为箭头函数在声明的时候会绑定静态上下文
练习:
var name ="梦回";
var obj={
name:"前端",
sayName:function(){
console.log(this.name);
},
callback:function(){
var that=this;
return function(){
var sayName=that.sayName;
that.sayName();//前端,这边调用了局部变量的that,而that保存obj对象。这个方法就是输出obj对象的name
sayName()//梦回,运行时该this为window,
};//
}
}
obj.callback()();//一个括号调用,生成了闭包。第二个括号调用,闭包的调用,就会读取函数内部的变量。
//使用let,声明name,是就不是window的属性。所以输出得时候结果会变成sayName(),会输出空字符。原因是name是window自带属性且为空字符。换成其他变量就会变成undefined
复制代码