1.闭包的内存泄露
function createFnArray(){
/**
* B KB MB GB TB 进制转换是1024
*/
var arr=new Array(1024*1024).fill(1); //这个空间占据了4KB
return function(){
console.log(arr.length);
}
}
var arrayFn=createFnArray();
20.png
执行完createFnArray函数后,FEC函数执行上下文栈会移除ECStack栈
21.png 22.png- 因为GC使用的算法是清除标记,只要是根对象能够访问到的对象或内存空间,就不会被销毁,那些不可达的对象就会被销毁
以上AO:0Xa002的arr指向的内存空间区域就会一直不会被销毁,所以就会产生内存泄露
以上是一个小的内存泄露,下面是更严重的内存泄露
function createFnArray(){
/**
* B KB MB GB TB 进制转换是1024
*/
var arr=new Array(1024*1024).fill(1); //这个空间占据了4KB
return function(){
console.log(arr.length);
}
}
var arrayFn=[];
for(let i=0;i<100;i++){
arrayFn.push(createFnArray())
}
function createFnArray(){
/**
* B KB MB GB TB 进制转换是1024
*/
var arr=new Array(1024*1024).fill(1); //这个空间占据了4KB
return function(){
console.log(arr.length);
}
}
// var arrayFn=createFnArray()
var arrayFn=[];
for(let i=0;i<100;i++){
setTimeout(()=>{
arrayFn.push(createFnArray());
console.log("push:",i);
},i*100)
}
setTimeout(()=>{
for(let i=0;i<50;i++){
setTimeout(()=>{
arrayFn.pop()
console.log("pop:",i);
},i*100)
}
},10000)
2.闭包引入的AO变量属性的销毁
function foo(){
var name="foo";
var age=20;
function bar(){
debugger
console.log(name);
}
return bar
}
var fn=foo()
fn()
/**
* ECMA规范:只有内层函数(作用域)使用了外层作用域的变量,那么外层作用域就不会被销毁
* V8引擎为了进一步优化 ,会将不使用的AO中的变量会销毁
*/
23.png
- ECMA规范:只要内层作用域引用了外层作用域的变量,那么那个AO对象就不会被销毁,AO中的所有变量也不会被销毁,
- V8引擎做了进一步优化,它会将AO中没有引用到的变量销毁,引用的变量保留。例如以上浏览器的截图,name被保留了,但是age被销毁了
3.为什么需要this
在常见的编程语言中,几乎都有this这个关键字,但是javascript的this和常见的面向对象有点不太一样
- 常见的面向对象的编程语言中,比如java、c++、Swift、Dart等等一系列语言中,this一般用在类方法中
- 也就是你需要创建一个类,类中的方法(特别是实例方法)中,this代表的是当前调用对象
- 但是javascript的this更加灵活,无论是它出现的位置还是它代表的含义
var obj={
name:"wjy",
eating:function(){ //方法
console.log(this.name+"在吃东西");
},
running:function(){//方法
console.log(this.name+"在跑");
},
study:function(){//方法
console.log(this.name+"在学习");
}
}
obj.eating();
obj.running();
obj.study()
但是其实不使用this也可以使用
var obj={
name:"wjy",
eating:function(){ //方法
console.log(obj.name+"在吃东西");
},
running:function(){//方法
console.log(obj.name+"在跑");
},
study:function(){//方法
console.log(obj.name+"在学习");
}
}
obj.eating();
obj.running();
obj.study()
但是如果发现obj这个变量名取名不太规范,想要去修改,但是你会发现,不仅是外面使用的obj的地方需要修改,就连对象内部定义的属性和方法调用的地方也要修改,会发现修改的工作量巨大,所以使用this的话会方便很多。
4.this在全局作用域指向什么
this在全局作用域中指向的是window(浏览器中)或空对象(node环境中)
- 在浏览器中,全局作用域的this指向window
-
在node环境中,全局作用域的this指向的是空对象
- node会将一个文件当作一个module——加载——编译——放入到一个函数中——执行这个函数.call
5.this在函数中指向
但是,在开发中很少直接在全局作用域下去使用this,通常都是在函数中使用
- 在函数执行时,会创建一个函数执行上下文
- 这个上下文会存储函数的调用栈、AO对象
- this也是其中的一个记录
- this是动态绑定的
- scope-chain是在编译的时候就已经确定的
function foo(){
console.log(this);
}
foo();//1. 直接调用
// 创建一个对象,对象中的函数调用指向foo
var obj={
name:"wjy",
foo:foo,
}
obj.foo();//2. 调用对象的foo函数
foo.apply("abc") //3. 使用apply绑定this
27.png
这个案例可以给我们一个什么样的启示呢?
- 函数在调用时,javascript会默认给一个this绑定一个值
- this的绑定和所处的位置是没有关系的
- this的绑定和调用方式及调用的位置有关
- this是在运行时被绑定的
那么this到底是怎样的一个绑定规则呢?
- 默认绑定
- 隐式绑定
- 显示绑定
- new 绑定
6.绑定规则
- 默认绑定
- 隐式绑定
- 显示绑定
- new 绑定
6.1默认绑定
什么情况下使用默认绑定呢?独立函数调用
- 独立的函数调用我们可以理解成函数没有被绑定到某个对象上进行调用
6.1.1 案例1
function foo(){
console.log(this);
}
foo();//独立函数调用
6.1.2 案例2
function foo1(){
console.log(this);
}
function foo2(){
console.log(this);
foo1();
}
function foo3(){
console.log(this );
foo2()
}
foo3()
6.1.3 案例3
var obj={
name:"wjy",
foo:function(){
console.log(this);
}
}
var bar=obj.foo;
bar();
6.1.4 案例4
function foo(){
console.log(this);
}
var obj={
name:"wjy",
foo:foo
}
var bar=obj.foo;
bar()
6.1.5 案例5
function foo(){
function bar(){
console.log(this);
}
return bar
}
var fn=foo();
fn()
看函数是如何调用的,只要是独立调用的,this指向的就是全局对象
6.2 隐式绑定
另外一种常见的调用方式就是通过某个对象进行调用的
- 也就是它的调用位置中,是通过某个对象发起的函数调用。
- 函数的隐式绑定:object.fn()
- object对象会被绑定到fn函数的this变量上
6.2.1 案例1
function foo(){
console.log(this);
}
var obj={
name:"wjy",
foo:foo
}
obj.foo()
6.2.2 案例2
var obj={
name:"wjy",
eating:function(){
console.log(this.name+"在吃东西");
},
running:function(){
console.log(this.name+"在跑步");
}
}
obj.eating()
obj.running()
6.2.3 案例3
这时候fn函数调用的时候this指向的是全局对象,只要函数独立调用,没有通过某个对象进行调用时,采用的就是默认绑定
var obj={
name:"wjy",
eating:function(){
console.log(this.name+"在吃东西");
},
running:function(){
console.log(this.name+"跑");
}
}
var fn=obj.eating;
fn();
6.2.4 案例4
var obj1={
name:"obj1",
foo:function(){
console.log(this);
}
}
var obj2={
name:"obj2",
bar:obj1.foo
}
obj2.bar()
6.3 显式绑定
-
隐式绑定有一个前提条件:
-
必须在调用的对象的内部有一个对函数的引用(比如一个属性)
-
如果没有这样的引用,在进行调用时,会报找不到该函数的错误
-
正是通过这个引用,间接的将this绑定到这个对象上。
-
如果我们不希望在对象内部包含这个函数的引用,同时又希望在这个对象上进行强制调用,该怎么做呢?
-
javascript的所有函数都可以使用call和apply方法(这个和prototype有关)
- 它们两个的区别在这里就不再展开
- 其实非常简单,第一个参数是相同的,后面的参数,apply为数组,call为参数列表
-
这两个函数的第一个参数要求是一个对象,这个对象的作用是什么呢?就是给this使用
-
在调用这个函数时,会将this绑定到这个传入的对象上。
function foo(){
console.log(this);
}
// foo(); //foo直接调用和foo.call或foo.apply调用是不同在于this的绑定是不同的
/**
* * 直接调用 this绑定的是全局对象
* foo.call()或foo.apply()调用:this指定的是第传入的对象
*/
var obj={
name:"wjy"
}
/**
* * call和apply是可以指定this的绑定
*/
foo.call(obj);
foo.apply(obj)
foo.apply("aabbcc")
6.3.1 call和apply有什么区别?
call和apply是可以明确绑定this,这个绑定规则称之为显式绑定
- call后面的参数 是参数列表,参数与参数之间用逗号分隔
- apply后面的参数 是数组,将要传入的参数放入到数组中
function sum(num1,num2){
console.log(num1+num2,this);
}
sum.call("call",20,30);//call后面的参数 是参数列表
sum.apply("apply",[20,30])// apply后面的参数 是数组
6.3.2 bind
function foo(){
console.log(this);
}
// foo.call("aaa");
// foo.call("aaa");
// foo.call("aaa");
// foo.call("aaa");
var newFoo=foo.bind("aaa");
newFoo()//this绑定的是"aaa"
foo()//this绑定的是window
默认绑定和显式绑定冲突时,显式绑定优先级别高一些
6.4 new绑定
- javascript中的函数可以当做一个类的构造函数来使用,也就是使用new 关键字
使用new关键字来调用函数是,会执行如下的操作:
- 创建一个全新的对象
- 这个新对象会被执行prototype连接
- 这个新对象会被绑定到函数调用的this上(this的绑定在这个步骤上完成)
- 如果函数没有返回其他对象,表达式会返回这个新对象
// 我们通过一个new关键字调用一个函数时(构造器),这个时候this是在调用这个构造器创建出来的新对象
function Person(name,age){
this.name=name;
this.age=age
}
var p1=new Person("wjy",22);
var p2=new Person("hyz",22);
console.log("p1:",p1);
console.log("p2:",p2);
网友评论