this、原型、原型链、作用域、作用域链、闭包
this
this
指的是函数运行时所在的环境, 随着执行环境的改变而改变。
var obj = {
foo: function () { console.log(this.bar) },
bar: 1
};
var foo = obj.foo;
var bar = 2;
obj.foo() // 1
foo() // 2
构造器的this
与return
- 如果 return 返回的是一个对象,则返回这个对象,而不是 this。
- 如果 return 返回的是一个原始类型,则忽略。
function Fn()
{
this.user = 'qwe';
return {};
}
var a = new Fn;
console.log(a.user); //undefined
function Fn1()
{
this.user = 'zxc';
return function(){};
}
var b = new Fn1;
console.log(b.user); //undefined
function Fn()
{
this.user = 'abc';
return 1;
}
var a = new Fn;
console.log(a.user); //abc
function Fn()
{
this.user = '123';
return undefined;
}
var a = new Fn;
console.log(a); // {user: "123"}
function Fn1()
{
this.user = '456';
return null;
}
var b = new Fn1;
console.log(b.user); //456
箭头函数没有自己的this,this指向父级
原型,原型链
所有 JavaScript 对象都从原型继承属性和方法
Object.prototype 位于原型继承链的顶端
设置原型对象
- obj.proto=prototypeObj.
- Object.setPrototypeOf(obj, prototypeObj)。
- Object.create(prototypeObj)。
在原型链上查找属性比较耗时,对性能有副作用,这在性能要求苛刻的情况下很重要。另外,试图访问不存在的属性时会遍历整个原型链。
遍历对象的属性时,原型链上的每个可枚举属性都会被枚举出来。要检查对象是否具有自己定义的属性,而不是其原型链上的某个属性,则必须使用所有对象从Object.prototype
继承的hasOwnProperty
方法。
function Person(name,age) {
this.name = name;
this.age = age;
}
Person.prototype.getInfo = function() {
return this.name + " is " + this.age;
}
var person1 = new Person('abc',17); //abc is 17
var person2 = new Person('def',18); //def is 18
console.log(person1.getInfo());
console.log(person1.__proto__ == Person.prototype); // true
console.log(Person.prototype.constructor == Person);// true
作用域,作用域链
作用域
当前的执行上下文。作用域也可以根据代码层次分层,以便子作用域可以访问父作用域,通常是指沿着链式的作用域链查找,而不能从父作用域引用子作用域中的变量和引用。
全局作用域(全局上下文) 局部作用域(函数上下文) 块作用域(代码块,es6中let const)
当前作用域不存在的变量和引用,就沿着作用域链继续寻找
作用域链
作用域链保证对执行环境有权访问的所有变量和函数的有序访问
当前执行环境搜索不到所需变量或函数会搜索上一级作用域链
function compare(value1,value2) {
if(value1>value2) {
return -1;
} else if(value1<value2) {
return 1;
} else {
return 0;
}
}
var result = compare(5,10);
每个函数都有自己的执行环境。
闭包
闭包
一个函数和对其周围状态(lexical environment,词法环境)的引用捆绑在一起(或者说函数被引用包围),这样的组合就是闭包(closure)。(MDN)
闭包是指有权访问另一个函数作用域中的变量的函数。(javascript高级程序设计)
function afun() {
var a = 'a';
function bfun() {
console.log(a);
}
console.log([bfun]);
console.dir(bfun);
}
afun();
内层函数引用外层函数的变量(词法环境,自由变量)就形成了闭包,return 这个内部函数只是为了便于使用内部函数,从而达到隐藏变量,不直接修改处理“私有变量”的目的。
function makeAdder(x) {
return function(y) {
return x + y;
};
}
var add5 = makeAdder(5);
var add10 = makeAdder(10);
console.log(add5(2)); // 7
console.log(add10(2)); // 12
词法环境 (所引用变量)为三个函数所共享
var Counter = (function() {
var privateCounter = 0;
function changeBy(val) {
privateCounter += val;
}
return {
increment: function() {
changeBy(1);
},
decrement: function() {
changeBy(-1);
},
value: function() {
return privateCounter;
}
}
})();
console.log(Counter.value()); /* logs 0 */
Counter.increment();
Counter.increment();
console.log(Counter.value()); /* logs 2 */
Counter.decrement();
console.log(Counter.value()); /* logs 1 */
每个闭包都是引用自己词法作用域内的变量。
var makeCounter = function() {
var privateCounter = 0;
function changeBy(val) {
privateCounter += val;
}
return {
increment: function() {
changeBy(1);
},
decrement: function() {
changeBy(-1);
},
value: function() {
return privateCounter;
}
}
};
var Counter1 = makeCounter();
var Counter2 = makeCounter();
console.log(Counter1.value()); /* logs 0 */
Counter1.increment();
Counter1.increment();
console.log(Counter1.value()); /* logs 2 */
Counter1.decrement();
console.log(Counter1.value()); /* logs 1 */
console.log(Counter2.value()); /* logs 0 */
容易引发的问题
该问题并非由闭包引起。
function funFor() {
var result = new Array();
for(var i=0;i<10;i++) {
result[i] = function() {
return i;
}
}
return result;
}
funFor();
result返回函数数组,且[[Scopes]]
中作用域链的第一位Closure(闭包)中i为10
函数中 i首先查找作用域链第一位中的i,当循环执行完后,i为10![image.png](https://img-blog.csdnimg.cn/img_convert/fb611690e2efb66a56ee80ca58f9934f.png#clientId=ue7cf8e92-6a30-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=619&id=NArO3&margin=[object Object]&name=image.png&originHeight=619&originWidth=737&originalType=binary&ratio=1&rotation=0&showTitle=false&size=38235&status=done&style=none&taskId=uea36c54f-2e51-4d47-a198-85499a74a85&title=&width=737)
这种影响可通过es6中let关键字来消除,或是通过使用更多的闭包来消除。
补充
构造器
function User(name) {
this.name = name;
this.isAdmin = false;
}
let user = new User("Jack");
alert(user.name); // Jack
alert(user.isAdmin); // false
当一个函数被使用 new 操作符执行时,它按照以下步骤:
- 一个新的空对象被创建并分配给 this。
- 函数体执行。通常它会修改 this,为其添加新的属性。
- 返回 this 的值。
如果没有参数,可以省略 new 后的括号
new.target
function User(name) {
if (!new.target) { //没有通过 new 运行
return new User(name); //
}
this.name = name;
}
let john = User("John"); // 将调用重定向到新用户
alert(john.name); // John
构造器的return
通常,构造器没有 return 语句。它们的任务是将所有必要的东西写入 this,并自动转换为结果。
但是,如果这有一个 return 语句,那么规则就简单了:
- 如果 return 返回的是一个对象,则返回这个对象,而不是 this。
- 如果 return 返回的是一个原始类型,则忽略。
执行环境(执行上下文)
执行环境
执行环境定义了变量或函数有权访问的其他数据,决定了它们各自的行为。
每个执行环境都有一个与之关联的变量对象,环境中定义的所有变量和函数都保存在这个对象中。(无法直接访问)
每个函数都有自己的执行环境。当执行流进入一个函数时,函数的环境就会被推入一个环境栈中。而在函数执行之后,栈将其环境弹出,把控制权返回给之前的执行环境。
全局执行上下文
全局执行上下文只有一个,在客户端中一般由浏览器创建=>window对象
window对象还是var声明的全局变量的载体
函数上下文
每当一个函数被调用时都会创建一个函数上下文
同一个函数被多次调用,都会创建一个新的上下文
执行上下文栈用于存储代码执行期间创建的所有上下文
执行环境和作用域链
当代码在一个环境中执行时,会创建变量对象的一个作用域链。作用域链保证对执行环境有权访问的所有变量和函数的有序访问。作用域链的前端,始终都是当前执行的代码所在环境的变量对象。如果这个环境是函数,则将其活动对象作为变量对象。活动对象在最开始时只包含一个变量,即arguments对象(这个对象在全局环境中时不存在的)。作用域链中的下一个变量对象来自包含(外部)环境,而再下一个变量对象则来自下一个包含环境。这样一直延续到全局执行环境;全局执行环境的变量对象始终都是作用域链中的最后一个对象。
循环中的函数表达式
利用循环,函数表达式来创建多个函数时,函数中的循环变量指向函数本身的父级作用域。
var result = new Array();
for(var i=0;i<10;i++) {
result[i] = function() {
return i;
}
}
console.log(result)