this的指向
较为容易理解的
- 箭头函数
箭头函数不会创建自己的
this,它只会从自己的作用域链的上一层继承this。 这句话很好理解了,另外,箭头函数不能作为构造函数,call bind apply 无效
var adder = {
base : 1,
add : function(a) {
var f = v => v + this.base;
return f(a);
},
addThruCall: function(a) {
var f = v => v + this.base;
var b = {
base : 2
};
return f.call(b, a);
}
};
console.log(adder.add(1)); // 输出 2
console.log(adder.addThruCall(1)); // 仍然输出 2
- new 绑定 硬绑定 来看 new 做了什么,手动实现的new
function myNew(){
// 第一步 默认返回一个 空对象;
let obj = {};
// 第二步 获取构造函数
let Fn = Array.prototype.shift.call(arguments);
// 第三步 obj的__proto__ 指向 Fn
obj = Object.create(Fn.prototype); // 1
// 第四步 执行构造函数,获取构造函数的返回值,改变构造函数中的this指向
// 来指向创建的对象
let args = Array.prototype.slice.call(arguments)
let x = Fn.call(obj, ...args);
// 第五步 返回值
// 如果x是引用类型就直接返回
if((typeof x == 'object' || typeof x == 'function') && x !== null){
return x
} else {
return obj;
}
}
看关键的一步,let x = Fn.call(obj, ...args);, 使用call来改变构造函数this的指向,所以本质上来说,new 和 call bind apply 归为一类。
- 隐式绑定 函数上下文
let obj = {
name: '401',
func: function(){
console.log(this.name); // 401
}
};
obj.func();
- 默认绑定 全局上下文
var name = '点点';
let obj = {
name: '401',
func: function(){
console.log(this.name); // 点点
}
};
let outFunc = obj.func;
outFunc();
这里为什么是 '点点' ,这样理解,outFunc === obj.func 成立吗?当然成立,这个时候是全局的上下文,所以就是点点。
- call bind apply 显式绑定 我们一般这样用 例子1: Object.prototype.toString.call(obj);
手动实现个call,来看下怎么改变this的指向:
Function.prototype.myCall = function(thisArg){
// 调用myCall的必须是函数
if(typeof this !== 'function'){
throw new TypeError('调用myCall必须是个函数!')
}
// 获取参数
let args = [...arguments].slice(1);
// 结合 例子1 这里 this == toString
let fn = Symbol(this);
thisArg[fn] = this;
// thisArg 就是call第一个参数 结合例子1 就是obj
/*
obj = {
name:'401',
func: ...,
fn: toString,
}
*/
// 在这一步,运用了隐式绑定,把this指向了obj;
// obj.toString(...args);
let result = thisArg[fn](...args);
delete thisArg[fn];
return result;
}
一个简单的call实现,运用了this隐式绑定。
优先级:new > call bind apply > 隐式绑定 > 默认绑定
我的理解就是:谁能调用函数?或window,或对象,有些语法糖,比如:call bind apply ,对开发很友好。
闭包
红宝书(p178)对于闭包的定义:闭包是指有权访问另一个函数作用域的中的变量的函数
MDN 一个函数和对其周围状态(lexical environment,词法环境)的引用捆绑在一起(或者说函数被引用包围),这样的组合就是闭包(closure)。也就是说,闭包让你可以在一个内层函数中访问到其外层函数的作用域。在 JavaScript 中,每当创建一个函数,闭包就会在函数创建的同时被创建出来。
- 闭包产生的原因 ES5中只存在两种作用域,全局作用域和函数作用域,当访问一个变量时,解释器现在当前作用域中查找,如果没找到,就去父作用域中查找,如果找不到就返回错误,这就是作用域链。(有点类似原型链);
闭包其实就是js保存变量的一种机制,闭包在函数被定义的时候创建;
闭包的表现形式:
-
返回一个函数 防抖和节流 比较经典的,当然我们也可以自己写;
-
作为函数参数传递 实际运用中的例子话?
-
在定时器、事件监听、Ajax请求、跨窗口通信、Web Worker或者任何异步中,只要使用了回调函数,就是在使用闭包,
以下闭包仅仅保存了当前作用域和全局作用域
// 定时器
setTimeout(function timeHandler(){
console.log('111');
},100)
// 事件监听
$('#app').click(function(){
console.log('DOM Listener');
})
- IIFE(立即执行函数表达式),保存了全局作用域和当前作用域
var a = 2;
(function(){
// a 2
console.log(a);
})()
如何打印出1,2,3,4,5,
for(var i = 1;i<=5;i++){
setTimeout(function timer(){
console.log(i);
},0)
}
- setTimeout 第三个参数
for(var i = 1;i<=5;i++){
setTimeout(function timer(j){
console.log(j);
},0,i)
}
- 利用 IIFE
for(var i = 1;i<=5;i++){
(function(i){
setTimeout(function timer(){
console.log(i);
},0)
})(i)
}
- 使用 let
for(let i = 1;i<=5;i++){
setTimeout(function timer(){
console.log(i);
},0)
}
- 闭包关于内存泄露 闭包使用不当会造成内存泄漏,
function bar(){
var b = [1,2,3];
function barz(){
this.a = b;
}
return barz
}
let func = bar();
func();
console.log(window.a); // [1,2,3]
类似这样就属于闭包使用不当引起的内存泄露,这段代码有两个内存泄露的地方,
- 对 b 的引用
- a 定义到了全局变量上边 引起内存泄露的原因不止闭包,希望我能用新的记录来说明,下一章,记录垃圾回收和内存泄露,毕竟这和我们平时写代码息息相关。