这是我在segmentfault的第一篇文章,欢迎你们指正
思考 + 导图 + 示例代码 => 船新版本
记得当时找实习的时候,老是会在简历上加上一句——熟悉Js,例如this指向、call、apply等...前端
而每次投递简历时我都会经历以下步骤git
下面几道题是我在网上搜索出来的热度较高的问题,若是大佬们能够轻松的回答上,并有清晰的思路,不妨直接点个赞吧(毕竟也消耗了很多脑细胞),若是大佬们能在评论处指点一二,就更好了!!!github
填空题:web
【 】
函数会建立一个新函数,新函数与被调函数具备相同的函数体,当目标函数被调用时 this 值指向第一个参数。问答题:面试
代码分析题:segmentfault
var name = 'window' var person1 = { name: 'person1', show1: function () { console.log(this.name) }, show2: () => console.log(this.name), show3: function () { return function () { console.log(this.name) } }, show4: function () { return () => console.log(this.name) } } var person2 = { name: 'person2' } person1.show1() person1.show1.call(person2) person1.show2() person1.show2.call(person2) person1.show3()() person1.show3().call(person2) person1.show3.call(person2)() person1.show4()() person1.show4().call(person2) person1.show4.call(person2)()
百度、谷歌上输入“this的指向”关键字,大几千条文章确定是有的,总不至于为了全方面、无死角的掌握它就要将全部的文章都看一遍吧?因此不如梳理出一个稳固的框架,顺着咱们的思路来填充它。数组
执行环境
动态绑定的,而非函数被声明时的环境;除了不经常使用的with和eval的状况,具体到实际应用中,this指向大概能够分为四种:浏览器
被调用的方式
;一、做为对象的方法调用 app
当函数做为对象的方法被调用时,this指向该对象
框架
var obj = { a: 'yuguang', getName: function(){ console.log(this === obj); console.log(this.a); } }; obj.getName(); // true yuguang
二、做为普通函数调用
当函数不做为对象的属性被调用,而是以普通函数的方式,this老是指向全局对象(在浏览器中,一般是Window对象)
window.name = 'yuguang'; var getName = function(){ console.log(this.name); }; getName(); // yuguang
或者下面这段迷惑性的代码:
window.name = '老王' var obj = { name: 'yuguang', getName: function(){ console.log(this.name); } }; var getNew = obj.getName; getNew(); // 老王
而在ES5的严格模式下,this被规定为不会指向全局对象,而是undefined
三、构造器调用
除了一些内置函数,大部分Js中的函数均可以成为构造器,它们与普通函数没什么不一样
构造器和普通函数的区别在于被调用的方式
:
当new运算符调用函数时,老是返回一个对象,this一般也指向这个对象
var MyClass = function(){ this.name = 'yuguang'; } var obj = new MyClass(); obj.name; // yuguang
可是,若是显式的返回了一个object对象,那么这次运算结果最终会返回这个对象。
var MyClass = function () { this.name = 1; return { name: 2 } } var myClass = new MyClass(); console.log('myClass:', myClass); // { name: 2}
只要构造器不显示的返回任何数据,或者返回非对象类型的数据,就不会形成上述问题。
四、call或apply调用
跟普通的函数调用相比,用call和apply能够动态的改变函数的this
var obj1 = { name: 1, getName: function (num = '') { return this.name + num; } }; var obj2 = { name: 2, }; // 能够理解成在 obj2的做用域下调用了 obj1.getName()函数 console.log(obj1.getName()); // 1 console.log(obj1.getName.call(obj2, 2)); // 2 + 2 = 4 console.log(obj1.getName.apply(obj2, [2])); // 2 + 2 = 4
5.箭头函数
箭头函数不会建立本身的this,它只会从本身的做用域链的上一层继承this。
所以,在下面的代码中,传递给setInterval的函数内的this与封闭函数中的this值相同:
this.val = 2; var obj = { val: 1, getVal: () => { console.log(this.val); } } obj.getVal(); // 2
就像标题同样,有的时候this
会指向undefined
状况一
var obj = { name: '1', getName: function (params) { console.log(this.name) } }; obj.getName(); var getName2 = obj.getName; getName2();
这个时候,getName2()
做为普通函数被调用时,this指向全局对象——window。
状况二
当咱们但愿本身封装Dom方法,来精简代码时:
var getDomById = function (id) { return document.getElementById(id); }; getDomById('div1') //dom节点
那么咱们看看这么写行不行?
var getDomById = document.getElementById getDomById('div1') // Uncaught TypeError: Illegal invocation(非法调用)
这是由于:
document
对象的方法时,方法内的this指向document
。getId
应用document
内的方法,再以普通函数的方式调用,函数内容的this
就指向了全局对象。利用call和apply修正状况二
document.getElementById = (function (func) { return function(){ return func.call(document, ...arguments) } })(document.getElementById) // 利用当即执行函数将document保存在做用域中
不要由于它的“强大”而对它产生抗拒,了解并熟悉它是咱们必需要作的,共勉!
先来看区别,是由于它们几乎没有区别,下文代码实例call和apply均可以轻易的切换。
当它们被设计出来时要作到的事情一摸同样,惟一的区别就在于传参的格式不同
apply接受两个参数
call接受的参数不固定
由于在全部(非箭头)函数中均可以经过arguments
对象在函数中引用函数的参数。此对象包含传递给函数的每一个参数,它自己就是一个类数组,咱们apply在实际使用中更常见一些。
call是包装在apply上面的语法糖,若是咱们明确的知道参数数量,而且但愿展现它们,可使用call。
当使用call或者apply的时候,若是咱们传入的第一个参数为null,函数体内的this会默认指向宿主对象,在浏览器中则是window
。
借用其余对象的方法
咱们能够直接传null来代替任意对象
Math.max.apply(null, [1, 2, 3, 4, 5])
使用一个指定的 this 值和单独给出的一个或多个参数来调用一个函数——来时MDN
实现继承
;this
;1.调用构造函数来实现继承
经过“借用”的方式来达到继承的效果:
function Product(name, price) { this.name = name; this.price = price; } function Food(name, price) { Product.call(this, name, price); // this.category = food; } var hotDog = new Food('hotDog', 20);
2.调用函数而且指定上下文的 this
此时this被指向了obj
function showName() { console.log(this.id + ':' + this.name); }; var obj = { id: 1, name: 'yuguang' }; showName.call(obj)
3.使用call单纯的调用某个函数
Math.max.apply(null, [1,2,3,10,4,5]); // 10
先来看一下call帮咱们须要作什么?
var foo = { value: 1 }; function show() { console.log(this.value); }; show.call(foo); //1
就像解方程,要在已知条件中寻找突破哦口:
call
使得this的指向变了,指向了foo;show
函数被执行了;this
+ 参数列表;初版代码
上面提到的3点,仅仅完成了一点,且传入的参数
var foo = { value: 1 }; function show() { console.log(this.value); }; Function.prototype.setCall = function (obj) { console.log(this); // 此时this指向show obj.func = this; // 将函数变成对象的内部属性 obj.func(obj.value); // 指定函数 delete obj.func // 删除函数,当作什么都没发生~ } show.setCall(foo);
第二版代码
为了解决参数的问题,咱们要能获取到参数,而且正确的传入:
var foo = { value: 1 }; function show(a, b) { console.log(this.value); console.log(a + b); }; Function.prototype.setCall = function (obj) { obj.fn = this; // 将函数变成对象的内部属性 var args = []; for(let i = 1; i < arguments.length; i++){ args.push('arguments[' + i + ']'); } eval('obj.fn(' + args + ')'); // 传入参数 delete obj.fn; // 删除函数,当作什么都没发生~ } show.setCall(foo, 1, 2); // 1 3
此时,咱们就能够作到,传入多个参数的状况下使用call了,可是若是你仅想用某个方法呢?
第三版代码
Function.prototype.setCall = function (obj) { var obj = obj || window; obj.fn = this; var args = []; for(var i = 1, len = arguments.length; i < len; i++) { args.push('arguments[' + i + ']'); } var result = eval('obj.fn(' + args +')'); delete obj.fn; return result; }; // 测试一下 var value = 2; var obj = { value: 1 }; function bar(name, age) { console.log(this.value); return { value: this.value, name: name, age: age } } bar.setCall(null); // 2 console.log(bar.setCall(obj, 'yuguang', 18));
bind() 方法建立一个新的函数,在 bind() 被调用时,这个新函数的 this 被指定为 bind() 的第一个参数,而其他参数将做为新函数的参数,供调用时使用 —— MDN
提到了call和apply,就绕不开bind。咱们试着来模拟一个bind方法,以便加深咱们的认识:
Function.prototype.bind = function (obj) { var _this = this; // 保存调用bind的函数 var obj = obj || window; // 肯定被指向的this,若是obj为空,执行做用域的this就须要顶上喽 return function(){ return _this.apply(obj, arguments); // 修正this的指向 } }; var obj = { name: 1, getName: function(){ console.log(this.name) } }; var func = function(){ console.log(this.name); }.bind(obj); func(); // 1
这样看上去,返回一个原函数的拷贝,并拥有指定的 this 值,仍是挺靠谱的哦~
JavaScript内功基础部分第一篇,总结这个系列是受到了冴羽大大的鼓励和启发,本系列总章数待定,保证都是咱们在面试最高频的,但在工做中经常被忽略的。
JavaScript内功系列:
关于我
其余沉淀
若是您看到了最后,不妨收藏、点赞、评论一下吧!!!
持续更新,您的三连就是我最大的动力,虚心接受大佬们的批评和指点,共勉!