this、apply/call、bind、闭包、函数、变量复制
一、实际场景中抽象出的一个问题
下面this各指向什么?
var a = {
b: function() {
console.log(this);
}, f: function() {
var c = this.b;
c();
}
}; a.b();
a.f();
第一个this指向a,第二个this指向window。(做对了吗)
二、JavaScript中变量复制的问题
变量拷贝分为值拷贝和引用类型数据拷贝
一个变量向另一个变量复制基本类型数据值时,另一个变量会在自己所占的内存中保存一份属于自己的数据值。
一个变量向另一个变量复制引用类型数据值时,实质上复制的是一个指针变量,这个指针指向堆内存中的对象,复制之后这两个变量指向同一个对象。
Es5中基本数据类型包括string、number、null、undefined、boolean
引用类型包括Array、RegExp、Date、Function等
( 基本包装类型:String、Boolean、Number; 单体内置对象:Global、Math)
三、函数
创建一个函数的方式: 函数声明、函数表达式、通过Function构造函数创建,它接受任意数量的参数,最后一个参数始终被看成是函数体。
(例如:new Function(“num1”, “num2”, “return num1+num2”))
函数声明有声明提升的过程,解析器在向执行环境中加载数据时会优先读取函数声明,保证它在执行任何代码之前都可用,而函数表达式则是在解析器
执行到它所在的代码行时才被解析执行。
var condition = false;
console.log( afun(100) );//undefined
if (condition) {
function afun(num) {
return num + 100;
}
}
else {
function afun(num) {
return num + 200;
}
}
上面的代码,函数调用之前还没有做判断,所以还没有afun函数
var condition = false;
if (condition) {
function afun(num) {
return num + 100;
}
}
else {
function afun(num) {
return num + 200;
}
}
console.log( afun(100) );//300
js中没有块级作用域的概念,上面的代码可以正常调用
js中函数也是一个对象,函数名是一个指针,所以在将一个函数的函数名复制给另一个变量时,这个变量也指向了这个函数
if (condition) {
var bfun = function(num) {
return num + 100;
}
}
else {
var bfun = function(num) {
return num + 200;
}
} var cfun = bfun;
bfun = null;
// console.log( bfun(100) );//报错
console.log( cfun(200) );//400
验证函数拷贝之后,被赋值的变量和赋值的变量指向的是同一个函数,函数表达式和函数声明定义的函数在复制时是一样的,复制之后两个变量指向同一函数。
复制之后,添加新的bfun,这个新的bfun覆盖原来的bfun,cfun同样也改变了,说明被赋值的变量和赋值的变量指向的是同一个函数。
function bfun(num) {
return num + 200;
}
var cfun = bfun;
function bfun(num) {
return num*1000;
} ;
console.log( bfun(100) );//100000
console.log( cfun(200) );//200000
但是当采用函数表达式的形式(在用函数表达式定义的bfun函数,bfun也是一个全局变量,这里没有用var定义变量,规范写法应该加上var) 再次定义一个同名的函数时,
如下:
function bfun(num) {
return num + 200;
}
var cfun = bfun;
bfun = function(num) {
return num*1000;
} ;
console.log( bfun(100) );//100000
console.log( cfun(200) );//300
函数表达式定义的函数没有覆盖函数声明定义的同名函数,在JavaScript中函数也是一个对象,函数名是一个指向函数的的变量指针,函数名也是一个变量,
在JavaScript中有一条规则是,函数声明会覆盖和其函数名同名的变量的声明,但是不会覆盖同名的变量的赋值,不管它们定义的顺序如何。在用函数表达式定
义的bfun函数中,bfun也是一个变量,只不过它被赋的值是一个匿名函数,一个变量的值可以是任何类型(string、number、boolean、null、undefined、一个
函数、一个复杂类型数据(对象)等)。
验证:函数声明会覆盖和其函数名同名的变量的声明,但是不会覆盖同名变量的赋值
bfun = function(num) {
return num*1000;
} ; var cfun = bfun; function bfun(num) {
return num + 200;
} console.log( bfun(100) );//100000
console.log( cfun(200) );//200000
再看一段代码,最后结果为多少?
bfun = function(num) {
return num*1000;
} ; var cfun = bfun; bfun = function(num) {
return num + 200;
}
console.log( bfun(100) );
console.log( cfun(200) );
最后结果为300、2000,后面的bfun覆盖了前面的bfun,在后面调用bfun时调用的是第二个,而cfun仍然为第一个bfun的值,why?
不是说函数是一个对象,函数名相当于一个指针,指向的是函数,在复制之后两个变量会指向同一个函数吗?
当bfun是采用函数声明的形式定义的时候,后面函数声明定义一个相同的同名函数之后,bfun改变,cfun会随之改变。而这里的bfun是采用函数表达式定义的函数,
又会有什么不同?
我的理解是,函数表达式定义的函数是没有函数名的,在复制的时候复制的是一个具体值,这里复制的就是一个匿名函数,而不是像函数声明复制一样复制的是一个
内存地址(指针),
函数表达式拷贝,就等同于基本数据类型变量拷贝,变量与变量之间各保存了一份属于自己的值,其中一个变量的值改变不会影响另外一个。
函数的内存分配(无论是函数声明还是函数表达式定义的函数):
函数表达式与函数声明不同的是,bfun再重新被赋值一个匿名函数之后,此时bfun指向一个新的对象(有点类似于原型上采用字面量的方式定义属性和方法一样,
此时指向的是一个新对象),cfun还是指向原来的匿名函数。也就是函数声明方式定义的函数,定义同名的函数,同名函数会覆盖原来的函数;而函数表达式定义
的函数不会被同名函数覆盖,定义同名函数之后原来的函数仍然存在。
函数是一个对象,所以可以在函数名上定义属性和方法:
bfun = function(num) {
return num*1000;
} ; var cfun = bfun;
bfun.a = 7777;
console.log(bfun.a);//777
bfun.b = function(num) {
return num + 200;
}
console.log(typeof bfun);//function console.log(bfun.a);//777
console.log( bfun(100) ); //10000
console.log( bfun.b(100) ); //300
验证:函数表达式定义的函数不会被同名函数覆盖,定义同名函数之后原来的函数仍然存在。
bfun = function(num) {
return num*1000;
} ; var cfun = bfun;
bfun.a = 7777;
console.log(bfun.a);//777
bfun = function(num) {
return num + 200;
}
console.log(typeof bfun);//function console.log(bfun.a);//undefined
console.log( bfun(100) ); //300
console.log( cfun(200) );//200000
console.log(cfun.a);//777
函数是一个对象,所以可以在函数对象上添加属性和方法,这样看似乎对象不一定就是存储在堆内存中了?函数表达式中的匿名函数这样的函数对象就是存在栈中啊
函数名是一个指针,指向函数,函数是一个对象,存在堆内存中。
四、闭包
var a = {
b: function() {
console.log(this);
}, f: function() {
var c = this.b;
c();
}
};
a.b();
a.f();
通过上面的分析,c这里和b指向的是同一个函数,但是为什么this的指向不同?问题在于调用(直接调用b时this为a,在f中将b复制给c之后,在调用c,this指向window)
函数的方式不一样,结果执行环境也不同。
这个例子实质上类似于(this===window):
var object = {
_name : "my object",
getNameFunc : function() { var that = this;
var a = function() {
console.log("value:"+this._name);//undefined
};
a();
}
};
console.log(object.getNameFunc());
同样也类似于(this===window):
var object = {
_name : "my object",
getNameFunc : function() { return function() {
// this._name = "yyy";
console.log(this);//window
return this._name;//undefined
}; }
};
console.log(object.getNameFunc()());
上面三个例子都是在函数中创建了一个闭包函数
闭包: 一个有权访问另一个函数中的变量的函数就是闭包,常见的闭包就是一个函数中包含另一个函数
函数没有利用对象调用时,this指向window;以上 闭包中的this指向window,这又引申到this指向的问题。
五、this的指向问题 (见:https://www.zhihu.com/question/19636194)
调用函数的几种方式以及当前this的指向问题:
直接通过函数名调用(无论是在哪里调用),此时this指向window
通过对象点函数名的形式调用,this指向函数的直接调用者
new关键字调用构造函数,this指向new出来的这个对象
通过apply或call方法调用,this指向第一个参数中的对象,如果第一个参数为null,this指向window
还可以通过bind()方法来改变函数的作用域,它返回的是一个函数的实例,最终需要通过这个实例来调用函数(apply和call方法是直接调用函数)
通过apply和call方法用来改变函数的作用域,这是每个函数中都包含的两个非继承的方法。
function a(num, num2) { }
当通过函数名直接调用一个函数时a(1,2),这个相当于a.apply(null, arguments),第一个参数是为null,所以this指向window
var b = {
function a(num, num2) { }
}
当通过对象调用一个函数时b.a(1,2),这个相当于a.apply(b, arguments),第一个参数是为b,所以this指向b
当把调用函数的方法换写成apply或call的形式就很好理解this的指向了
关于直接调用函数和通过new关键调用构造函数的区别,如下:
考察不同调用方式this的指向问题和变量提升的问题
var a=10;
function test(){
a=5;
alert(a);
alert(this.a);
var a;
alert(this.a);
alert(a);
} test();
new test();
// 5 10 10 5
//5 undefined undefined 5
test()方法,this指向window,test中用var定义了一个a,它将被提升到执行环境的最前端,a=5
new test(),此时test是个构造函数,this.a没有被定义所以为undefined
总结:
上面的问题的实际场景如下:
实际项目中抽象出来的一部分,用来统一给dom元素添加事件的写法,大大提升了代码的可维护性
var util = {
maps: {
'click #btn': 'clickBtn'
}, stop: function() {
console.log("stop....");
}, clickBtn: function(event) {
var e = event;
var target = e.target || e.srcElement;
console.log(target.id);
this.stop();
}, _scanEventsMap: function(maps, isOn) { var delegateEventSplitter = /^(\S+)\s*(.*)$/;
var bind = isOn ? this._delegate.bind(this) : this._undelegate.bind(this);
// this._delegate('click', "#btn", this["clickBtn"]);
// bind('click', "#btn", "clickBtn");
for (var keys in maps) {
if (maps.hasOwnProperty(keys)) {
var matchs = keys.match(delegateEventSplitter);
bind(matchs[1], matchs[2], maps[keys]);
}
}
}, _delegate: function(name, selector, func) {
var ele = document.querySelector(selector),
that = this,
func = this[func];
console.log(func);
ele.addEventListener(name, function(event) {
var e = event || window.event;
func.apply(that, arguments);
}, false);
}, _undelegate: function(name, selector, func) {
var ele = $(selector);
ele.removeEventListener(name, func, false);
}, _init: function() {
this._scanEventsMap(this.maps, true);
}
}
util._init();
this、apply/call、bind、闭包、函数、变量复制的更多相关文章
- 前端总结·基础篇·JS(三)arguments、callee、call、apply、bind及函数封装和构造函数
前端总结系列 前端总结·基础篇·CSS(一)布局 前端总结·基础篇·CSS(二)视觉 前端总结·基础篇·CSS(三)补充 前端总结·基础篇·JS(一)原型.原型链.构造函数和字符串(String) 前 ...
- 手写系列:call、apply、bind、函数柯里化
少废话,show my code call 原理都在注释里了 // 不覆盖原生call方法,起个别名叫myCall,接收this上下文context和参数params Function.prototy ...
- 使用call、apply和bind解决js中烦人的this,事件绑定时的this和传参问题
1.什么是this 在JavaScript中this可以是全局对象.当前对象或者任意对象,这完全取决于函数的调用方式,this 绑定的对象即函数执行的上下文环境(context). 为了帮助理解,让我 ...
- 第10天:apply和call、bind、函数作为参数、返回值使用、闭包、递归的样例
apply和call apply和call都可以改变this的指向 函数的调用,改变this的指向 函数名字.apply(对象,[参数1,参数2,.....]) 方法名字.apply(对象,[参数1, ...
- 函数的属性和方法之call、apply 及bind
一.前言 ECMAScript中的函数是对象,因此函数也有属性和方法.每个函数都包含两个属性:length和prototype.每个函数也包含两个非继承来的方法:apply()和call(),还有一些 ...
- 高频重要前端API手写整理(call,apply,bind,instanceof,flat,filter,new,防抖,节流,深浅拷贝,数组乱序,数组去重,继承, lazyman,jsonp的实现,函数的柯里化 )
Function.prototype.call = function(context,...args){ var context = context || window; context.fn = t ...
- 【JavaScript】[bind,call,apply] (function cal(){}());声明函数立即执行
---恢复内容开始--- 1.js 里函数调用有 4 种模式:方法调用.正常函数调用.构造器函数调用.apply/call 调用.同时,无论哪种函数调用除了你声明时定义的形参外,还会自动添加 2 个形 ...
- Javascript中call、apply、bind函数
javascript在函数创建的时候除了自己定义的参数外还会自动新增this和arguments两个参数 javascript中函数也是对象,call.apply.bind函数就是函数中的三个函数,这 ...
- 改变函数中的 this 指向——神奇的call,apply和bind及其应用
在JavaScript 中,call.apply 和 bind 是 Function 对象自带的三个方法,这三个方法的主要作用是改变函数中的 this 指向,从而可以达到`接花移木`的效果.本文将对这 ...
随机推荐
- Codeforces 766C:Mahmoud and a Message(DP)
题目链接:http://codeforces.com/problemset/problem/766/C 题意 有一个长度为n的字符串,第二行有26个数字,位置1~26对应为a~z的字母,数值表示该字母 ...
- PTA——判断胖瘦
PTA 7-40 到底是不是太胖了 #include<stdio.h> #include<math.h> int main() { ,N; scanf("%d&quo ...
- python性能分析——insert()
我们在list中插入数据时,经常使用这两个函数: append():在列表的末尾增加一个数据 insert():在某个特定位置前加一个数据 Python内的list实现是通过数组实现的,而不是链表的形 ...
- java-接口的成员特点
1.成员变量: - 只能是常量,并且是静态的.公共的. - 默认修饰符:public static final - 建议:自己手动给出. 2.构造方法:接口没有构造方法. 3.成员方法: - 只能是抽 ...
- 在学习linux基础入门时的一些问题总结(1)
本周在实验楼完成了<linux基础入门>的21个实验,虽然之前已经学习过linux的相关课程,对linux下的命令也有一些了解和实践,但完成这21个实验以及35个练习题仍然遇到了许多的问题 ...
- (13)自定意义标签和过滤器 (templatetags)
过滤器分内置和自定意义 PS:过滤器可以用在for循环和if判断后,但是标签不能使用在for循环和if判断后 内置过滤器: add(在模板中实现加减法) default(就是当传入的变量是False的 ...
- How_Require_Extensions_Work
Why Doing require extensions correctly is essential, because: Users should be able to install multip ...
- JavaScript跨浏览器绑定事件函数的优化
JavaScript作为一门基于事件驱动的语言(特别是用在DOM操作的时候),我们常常需要为DOM绑定各种各样的事件.然而,由于低版本的IE的不给力,在绑定事件和移除事件监听上都与众不同,我们常常需要 ...
- spring中afterPropertiesSet方法与init-method配置描述
1. InitializingBean.afterPropertiesSet()Spring中InitializingBean接口类为bean提供了定义初始化方法的方式,它仅仅包含一个方法:after ...
- js 判断滚动条是否停止滚动
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title> ...