javascript 面向对象学习(三)——this,bind、apply 和 call
this 是 js 里绕不开的话题,也是非常容易混淆的概念,今天试着把它理一理。
this 在非严格模式下,总是指向一个对象,在严格模式下可以是任意值,本文仅考虑非严格模式。记住它总是指向一个对象对于理解它的意义很重要。this 在实际使用中,大致分为以下几种情况:
1. 函数作为对象的方法调用时,this 指向调用该函数的对象
var obj = {
name: 'jack',
getName: function() {
console.log(this === obj) // true
console.log(this.name) // jack
}
}
obj.getName()
这个应该很好理解,不多说了。
2. 函数作为普通函数被调用时,this 指向全局对象。在浏览器中,全局对象是window。
var name = 'global'
function getName() {
console.log(this === window) // true
console.log(this.name) // global
}
getName()
我的理解是上面的代码可以改写为
window.name = 'global'
window.getName = function() {
console.log(this === window) // true
console.log(this.name) // global
}
window.getName()
这样其实与情况1是一样的,相当于函数作为对象的方法调用,只不过这里的对象是全局对象。
《Javascript 设计模式与开发实践》一书中有个例子如下:
window.name = 'globalName';
var myObject = {
name: 'seven',
getName: function(){
return this.name
}
} var getName = myObject.getName
console.log(getName()) // globalName
getName 是定义在myObject 对象中的方法,在调用getName 方法时,打印出的却是全局对象的name,而不是myObject对象的name,这再次证明了this并非指向函数被声明时的环境对象,而是指向函数被调用时的环境对象。
3. 函数作为构造函数调用时
指向构造出的新对象
function Person(name) {
this.name = name
} var jack = new Person('Jack')
console.log(jack.name) // Jack
var rose = new Person('Rose')
console.log(rose.name) // Rose
这里创建了两个不同名字的对象,打印出的name也是不一样的,说明构造函数的 this 会根据创建对象的不同而变化。需要注意的是,如果构造函数里返回了一个Object类型的对象,那么this会指向这个对象,而不是利用构造函数创建出的对象。我们在构造函数一章里也提到过,new 操作符所做的最后一步就是返回新对象,而如果我们显式地返回一个对象,就会覆盖这步操作,this也就不再指向新对象。
4. 函数作为事件处理函数调用时,指向触发事件的元素
document.getElementById("myBtn").addEventListener("click", function(e){
console.log(this === e.currentTarget) // true
});
5. 箭头函数
原因箭头函数没有this,箭头函数的 this 是继承父执行上下文里面的 this。执行上下文后面再讨论,现在只要知道简单对象(非函数)是没有执行上下文的。
var obj = {
name: 'obj',
getName: function() {
console.log(this) // 执行上下文里的 this
return (()=>{
console.log(this.name)
})
}
} var fn = obj.getName()
fn() // obj
按照情况2来处理的话,this 指向全局对象,应该输出 undefined,结果并不是。与普通函数不同,箭头函数的 this 是在函数被声明时决定的,而不是函数被调用时。在这里,父执行上下文是 getName 函数,也就继承了 getName 的 this,即 obj。
利用 bind、apply、call 改变 this 指向
bind、apply、call 都是定义在 Function 原型对象上的方法,所有函数对象都能继承这个方法,三者都能用来改变 this 指向,我们来看看它们的联系与区别。
function fn() {
console.log(this.name)
} // bind
var bindfn = fn.bind({name: 'bind'})
bindfn() // bind // apply
fn.apply({name: 'apply'}) // apply // call
fn.call({name: 'call'}) // call
我们定义了一个函数fn,然后分别调用了它的 bind、apply、call 方法,并传入一个对象参数,通过打印出的内容可以看到 this 被绑定到了参数对象上。bind 似乎有些不同,多了一步 bindfn() 调用,这是因为 bind 方法返回的是一个函数,不会立即执行,而调用 apply 和 call 方法会立即执行。
下面再来看一下 fn 函数存在参数的情况:
function fn(a, b, c) {
console.log(a, b, c)
} var bindfn = fn.bind(null, 'bind');
bindfn('A', 'B', 'C'); // bind A B fn.apply(null, ['apply', 'A']) // apply A undefined fn.call(null, 'call', 'A'); // bind A undefined
bindfn 打印出的结果是fn调用bind方法时的传递的参数加上bindfn传递的参数,参数 'C' 被舍弃掉了。调用 apply 和 call 方法打印出的则是传递给它们的参数,不一样的是,apply 的参数是一个数组(或类数组),call 则是把参数依次传入函数。这时候再看它们的定义应该会好理解很多:
bind() 方法创建一个新的函数,在 bind() 被调用时,这个新函数的 this 被指定为 bind() 的第一个参数,而其余参数将作为新函数的参数,供调用时使用。
apply() 方法调用一个具有给定 this 值的函数,以及作为一个数组(或类数组对象)提供的参数。
call() 方法使用一个指定的 this 值和单独给出的一个或多个参数来调用一个函数。
我们可以利用它们来借用其他对象的方法。已知函数的参数列表 arguments 是一个类数组对象,比如上例中函数 fn 的参数 a, b, c,因为它不是一个真正的数组,不能调用数组方法,这时将 this 指向 arguments 就能借用数组方法:
(function(){
Array.prototype.push.call(arguments, 'c')
console.log(arguments) // ['a', 'b', 'c']
})('a','b')
值得一提的是,push 方法并不是只有数组才能调用,一个对象只要满足1.可读写 length 属性;2.对象本身可存取属性. 就可以利用 call / apply 调用 push 方法。
参考:
《Javascript 设计模式与开发实践》
https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Operators/this
http://www.imooc.com/article/80117
https://blog.csdn.net/weixin_42519137/article/details/88053339
javascript 面向对象学习(三)——this,bind、apply 和 call的更多相关文章
- JavaScript面向对象学习笔记
JavaScript 常被描述为一种基于原型的语言 (prototype-based language)--每个对象拥有一个原型对象,对象以其原型为模板.从原型继承方法和属性.原型对象也可能拥有原型, ...
- 前端笔记之JavaScript面向对象(三)初识ES6&underscore.js&EChart.js&设计模式&贪吃蛇开发
一.ES6语法 ES6中对数组新增了几个函数:map().filter().reduce() ES5新增的forEach(). 都是一些语法糖. 1.1 forEach()遍历数组 forEach() ...
- JavaScript基础学习(三)—数组
一.数组简介 JavaScript数组的每一项都可以保存任何类型的数据,也就是说数组的第一个位置保存字符串,第二个位置可以保存数值,第三个位置可以保存对象,而且数组的大小是可以动态调整的,即可 ...
- javascript面向对象学习(一)
面向对向的初体验 创建一个标签 // 传统 var p = document.createElement('p'); var txt = document.createTextNode('我是传统js ...
- javascript 面向对象学习(一)——构造函数
最近在学习设计模式,找了很多资料也没有看懂,看到怀疑智商,怀疑人生,思来想去还是把锅甩到基础不够扎实上.虽然原型继承.闭包.构造函数也都有学习过,但理解得不够透彻,影响到后续提高.这次重新开始学习,一 ...
- Javascript设计模式学习三(策略模式)
定义:定义一系列的算法,把它们一个个封装起来,并且使它们可以互相替换.目的:将算法的使用和算法的实现分离开来.比如: if(input == 'A'){ return 1; } if(input == ...
- Javascript面向对象(三):非构造函数的继承
这个系列的第一部分介绍了"封装",第二部分介绍了使用构造函数实现"继承". 今天是最后一个部分,介绍不使用构造函数实现"继承". 一.什么是 ...
- javascript 面向对象学习(二)——原型与继承
什么是原型? 首先我们创建一个简单的空对象,再把它打印出来 var example = {} console.log(example) 结果如下: { __proto__: { constructor ...
- PHP面向对象学习三 类的抽象方法和类
一个类中至少有一个方法是抽象的,我们称之为抽象类. 所以如果定义抽象类首先定义抽象方法. 1.类中至少有一个抽象方法 2.抽象方法不允许有{ } 3.抽象方法前面必须要加abstract 抽象类的几个 ...
随机推荐
- 学习Echarts:(二)异步加载更新
这部分比较简单,对图表的异步加载和更新,其实只是异步获取数据然后通过setOption传入数据和配置而已. $.get('data.json').done(function (data) { myCh ...
- 你真的了解负载均衡中间件nginx吗?
前言 nginx可所谓是如今最好用的软件级别的负载均衡了.通过nginx的高性能,并发能力强,占用内存下的特点,可以搭建高性能的代理服务.同时nginx还能作为web服务器,反向代理,动静分离服务器. ...
- Centos慢慢长大(一)
1.写在前面 这将是一个系列性的文章.可能更多的是记录我在学习的过程中的一些感悟吧.我想强调的是在这一系列文章里我会从最小化的安装开始,然后逐渐的增加需要安装的软件.就象一个婴儿的诞生,慢慢的学走路. ...
- 关于mobileSelect.js日期数据获取封装,动态时间,封装
传入起始年份和起始月份 得到 插件的标准格式放到 效果 let getAllDatas = (stime, etime) => { //接收传进来的参数月份 var data_M = etime ...
- Docker虚拟机配置手札(centos)
一.Docker只支持CentOS7及以上系统,不支持6.x系统 二.yum安装Docker 1.安装相关环境和设置仓库 yum install -y yum-utils device-mapper- ...
- NO.5 CCS运行demo(云端)
我们在demo的README中发现如果程序在云端运行会有很酷的界面而且功能会多一些. 首先我们在CCS开始界面点击Resourse Explorer 然后在浏览器中找到对应的demo 打开GUI界面, ...
- spring-kafka之KafkaListener注解深入解读
简介 Kafka目前主要作为一个分布式的发布订阅式的消息系统使用,也是目前最流行的消息队列系统之一.因此,也越来越多的框架对kafka做了集成,比如本文将要说到的spring-kafka. Kafka ...
- pandas的loc与iloc
1. loc是用标签(也就是行名和列名)来查找,标签默认是数字,但也可以通过index参数指定为字符型等其他的类型. 格式是df.loc[行名,列名],如果列标签没有给出,则默认为查找指定行标签的所有 ...
- 关于Dev-C++一种引用头文件<iostream>问题(暴力解决)
问题情况如下,因个人水平有限,不知道具体原因是啥,当引用头文件<iostream>时会出现如下问题,经排查,并不是头文件本身的问题,有可能是Dev哪一个文件被改动了,或者设置出了问题(前者 ...
- win服务器管理软件巧利用——如何让服务器管理事半功倍
那些服务器管理大牛估计看到这个标题会笑了,服务器怎么管理,靠自带的远程桌面肯定是远远不够的,要实现上千台服务器同时登陆,没有一个好程序管理,估计得三餐不食为其颠倒. 那么,有什么好的服务器推荐呢?站长 ...