牛客最新前端笔试题解析(一) this指向题目解析及扩展 #yyds干货盘点#
前言这篇文章主要来解析牛客笔试题部分的 this 指向题目。首先我们先从宏观上了解一下 JavaScript 中得 this 指向问题。
JavaScript 中 this 共有四种绑定(包括隐式绑定丢失)加 ES6 新增得箭头函数绑定,如果把这几种绑定全学会了,this 指向完全不成问题。 this 指向更详细的讲解,可以去看前面写过的一篇博文,里面还有 38 道题目,详细完整的讲解了 this 指向问题。
[*]默认绑定: 非严格模式下 this 指向全局对象,严格模式下 this 会绑定为 undefined
[*]隐式绑定: 满足 XXX.fn() 格式,fn 的 this 指向 XXX。如果存在链式调用, this 永远指向最后调用它的那个对象
[*]隐式绑定丢失:起函数别名,通过别名运行;函数作为参数会造成隐式绑定丢失。
[*]显式绑定: 通过 call/apply/bind 修改 this 指向
[*]new绑定: 通过 new 来调用构造函数,会生成一个新对象,并且把这个新对象绑定为调用函数的 this 。
[*]箭头函数绑定: 箭头函数没有 this ,它的 this 是通过作用域链查到外层作用域的 this ,且指向函数定义时的 this 而非执行时
题目一. 隐式绑定与隐式绑定丢失
var x = 1;
var obj = {
x: 3,
fun:function () {
var x = 5;
return this.x;
}
};
var fun = obj.fun;
console.log(obj.fun(), fun());解析
JavaScript 对于引用类型,其地址指针存放在栈内存中,真正的本体是存放在堆内存中的。fun = obj.fun 相当于将 obj.fun 指向得堆内存指针赋值给了 fun,此后 fun 执行与 obj 不会有任何关系,发生隐式绑定丢失。
[*]obj.fun(): 隐式绑定,fun 里面的 this 指向 obj,打印 3
[*]fun(): 隐式绑定丢失: fun 默认绑定,非严格模式下,this 指向 window,打印 1
答案
3 1
题目二: 隐式绑定丢失
var person = {
age: 18,
getAge: function() {
return this.age;
}
};
var getAge = person.getAge
console.log(getAge())简单的隐式绑定丢失问题
答案
undefined
题目三: 隐式绑定丢失
var obj = {
name:"zhangsan",
sayName:function(){
console.log(this.name);
}
}
var wfunc = obj.sayName;
obj.sayName();
wfunc();
var name = "lisi";
obj.sayName();
wfunc();简单的隐式绑定问题,不多做赘述了。
答案
zhangsan
undefined
zhangsan
lisi
题目四:new绑定
var a = 5;
function test() {
a = 0;
console.log(a);
console.log(this.a);
var a;
console.log(a);
}
new test();解析
使用new来构建函数,会执行如下四部操作:
[*]创建一个空的简单JavaScript对象(即{});
[*]为步骤1新创建的对象添加属性__proto__,将该属性链接至构造函数的原型对象 ;
[*]将步骤1新创建的对象作为this的上下文 ;
[*]如果该函数没有返回对象,则返回this。
通过 new 来调用构造函数,会生成一个新对象,并且把这个新对象绑定为调用函数的 this 。
[*]console.log(a): 打印变量 a 的值,当前 testAO 中存在 a 变量,打印 0
[*]console.log(this.a): new 绑定 this 指向新的实例对象,当前题目没有给实例对象添加 a 属性,打印 undefined
[*]console.log(a): 同第一个,打印 0
答案
0
undefined
0
题目五:箭头函数与显式绑定
function fun () {
return () => {
return () => {
return () => {
console.log(this.name)
}
}
}
}
var f = fun.call({name: 'foo'})
var t1 = f.call({name: 'bar'})()()
var t2 = f().call({name: 'baz'})()
var t3 = f()().call({name: 'qux'})
[*]箭头函数没有 this ,它的 this 是通过作用域链查到外层作用域的 this ,且指向函数定义时的 this 而非执行时。
[*]箭头函数,不能通过 call\apply\bind 来修改 this 指向,但可以通过修改外层作用域的 this 来达成间接修改。
[*]JavaScript 是静态作用域,即函数的作用域在函数定义的时候就决定了,而箭头函数的 this 是通过作用域链查到的,因此箭头函数定义后,它的作用域链就定死了。
[*]f = fun.call({name: 'foo'}): 将 fun 函数的 this 指向 {name: 'foo'},并返回一个箭头函数,因此箭头函数的 this 也指向 {name: 'foo'}
[*]t1 = f.call({name: 'bar'})()(): 对第一层箭头函数执行 call 操作,无效,当前 this 仍指向 {name: 'foo'},第二层、第三层都是箭头函数,第三层的 this 也指向 {name: 'foo'},打印 foo
[*]后续 t2 t3 分别对第二层、第三层箭头函数使用 call ,无效,最终都打印 foo 。
答案
foo
foo
foo
题目六:箭头函数
let obj1 = {
a: 1,
foo: () => {
console.log(this.a)
}
}
// log1
console.log(obj1.foo())
const obj2 = obj1.foo
// log2
console.log(obj2())解析
[*]obj1.foo 为箭头函数,obj1 为对象,无法提供外层作用域,因此 obj.foo 里面的 this 指向 window
[*]obj1.foo(): 箭头函数,this 指向 window,打印 undefined
[*]obj2 隐式绑定丢失: 打印 undefined
答案
undefined
undefined
题目七: 综合题(推荐看)
var name = 'global';
var obj = {
name: 'local',
foo: function(){
this.name = 'foo';
console.log(this.name);
}.bind(window)
};
var bar = new obj.foo();
setTimeout(function() {
console.log(window.name);
}, 0);
console.log(bar.name);
var bar3 = bar2 = bar;
bar2.name = 'foo2';
console.log(bar3.name);解析
这个题的整体出题质量还是挺高的,首先咱们来把涉及到的知识罗列一下:
[*]bind 是显式绑定,会修改 this 指向,但 bind() 函数不会立即执行函数,会返回一个新函数
[*]setTimeout 是异步任务,同步任务执行完毕后才会执行异步任务
[*]绑定优先级: new绑定 > 显式绑定 > 隐式绑定 > 默认绑定
解析
[*]obj.foo 将它的 this 通过 bind 显式的绑定为 window,但 bind 不会立即执行
[*]var bar = new obj.foo(): new 绑定优先级大于 bind ,因此 bind 失效了,此时 this 指向 new 实例,因此 obj.foo 内部的 console 打印 foo
[*]bar 是 new obj.foo() 的实例,console.log(bar.name) 打印 foo
[*]setTimeout 异步任务,等到同步执行完毕再来调用它的回调
[*]bar3 = bar2 = bar,将 bar2,bar3,bar 的地址都指向 bar 所指向的空间。
[*]bar2.name = 'foo2',修改地址指向堆内存的值
[*]console.log(bar3.name): 由于三个变量指向同一块地址,bar3 修改了 name ,bar3 也随之改变,打印 foo2
[*]setTimeout 的回调执行,打印 global
答案
foo
foo
foo2
global
题目八: 综合题目七修改
题目六虽然出的很有质量,但是我感觉有几个地方考察的让人不满足,咱们来改一下题目。
改法一: 去除 new 绑定
var name = 'global';
var obj = {
name: 'local',
foo: function(){
this.name = 'foo';
console.log(this.name);
}.bind(window)
};
obj.foo();
setTimeout(function() {
console.log(this.name);
}, 0);
console.log(name);
[*]obj.foo 没有做修改,它的 this 通过 bind 显式的绑定为 window,但 bind 不会立即执行
[*]obj.foo() 执行,显式绑定优先级大于隐式绑定,因此此时 foo 函数 this -> window
[*]this.name 相当于 window.name,修改了全局的 name 变量值,打印 foo
[*]setTimeout 回调等同步代码执行完毕
[*]全局的 name 已经修改为 foo ,打印 foo
[*]执行 setTimeout 的回调,默认绑定,打印 foo
答案
foo
foo
foo改法一: 修改 bind 为 call
var name = 'global';
var obj = {
name: 'local',
foo: function(){
this.name = 'foo';
console.log(this.name);
}
};
obj.foo.call(window);
var bar = new obj.foo();
setTimeout(function() {
console.log(window.name);
}, 0);
console.log(bar.name);
var bar3 = bar2 = bar;
bar2.name = 'foo2';
console.log(bar3.name);call 与 bind 的区别就在于 call 函数会立即执行
因此 obj.foo.call(window) 会立即执行函数,同时也修改了全局的 name 值。其他部分与上面所讲类似,不多做赘述了。
答案
foo
foo
foo
foo2
foo
https://blog.51cto.com/u_11724598/4841981
页:
[1]