面试-js作用域,this指向

64 阅读3分钟

作用域&作用域链

面试题

var num = 123; 
// f1 函数写好了,作用域就定下来了,也就是作用域链定下来了 
// f1 函数作用域链: f1 函数作用域 ==> 全局作用域 
function f1() { 
    console.log(num); // 123 
} 
function f2() { 
    var num = 456; 
    f1(); 
} 
f2(); //打印啥?undefined
var num = 123 
function f1(num) { 
    // 形参:理解成函数内声明的局部变量 
    // var num = 实参; 456 
    console.log(num) // 456 
} 
function f2() { 
    var num = 456; 
    f1(num); // num 实参 f1(456) 
} 
f2() //456
var num = 123
function f1(num) {
    // var num = 实参; // undefined
    console.log(num); // undefined
}
function f2() {
    var num = 456;
    f1();
}
f2()
var num = 123
function f1() {
    console.log(num); // 123
}
function f2() {
    var num = 456;
    f1(num);
}
f2();
var num1 = 10;
var num2 = 20;
function fn(num1) {
    // var num3; // 预解析
    // var num1 = 实参; // undefined // 形参的理解
    num1 = 100; // 修改的局部 num1 为 100;
    num2 = 200; // 修改的全局 num2 为 200;
    num3 = 300; // 修改的局部 num3 为 300;
    console.log(num1); // 100
    console.log(num2); // 200
    console.log(num3); // 300
    var num3;
}
fn();
console.log(num1); // 10
console.log(num2); // 200
console.log(num3); // error*/ 全局没有定义num3 
var num = 10;
fn1();
function fn1() {
    console.log(num); // undefined 原因是下面局部定义了var num提升到局部顶部接着打印并没有值,这种情况是不会取全局上面的值,因为局部已经定义了自己的变量
    var num = 20;
    console.log(num); //20
}
console.log(num); // 10

预解析经典面试题

function fn1() {
// 函数内的代码在执行之前也要经过预解析
var num; // 预解析
    console.log(num); // undefined
    num = 20; // 修改局部 num 为 20
    console.log(num); // 20
}
var num;
num = 10; // 修改全局 num 为 10 fn1();
console.log(num); // 10

var num = 10;
fn1();
function fn1() {
    console.log(num); // 10
    num = 20; // 修改全局的 num 为 20
    console.log(num); // 20
}
console.log(num); // 20
var a = 1
function fn() {
    console.log(a)
}
function fn2(f) {
    var a = 2
    f()
}
fn2(fn)
var a=123
function fun(){
    console.log(a) //undefined
    var a=456
}
fun()
console.log(a) //123

作用域总结:

// 1. 预解析(函数内的代码执行之前也要预解析)

// 2. 函数内的形参理解: 理解成函数内声明的局部变量

// 3. 函数作用域在函数写好的时候就已经确定下来了,作用域链就已经定下来了。

作用域链:

当查找变量的时候,会先从当前上下文的变量对象中查找,如果没有找到,就会从父级(词法层面上的父级)执行上下文的变量对象中查找,一直找到全局上下文的变量对象,也就是全局对象。这样由多个执行上下文的变量对象构成的链表就叫做作用域链。

this的指向规则

  1. 默认绑定
function fn (){ 
    console.log(this) //this -> window 
} 
fn() // fn()独立调用函数,this指向window
  1. 隐式绑定
var obj = { 
    test:1, 
    fn:function(){ 
        console.log(this) //this -> obj 
    } 
} 
obj.fn() // obj.fn() //指向 obj
  1. 显示绑定
apply(),call(),bind() apply,call,bind当传入null,undefind时,this指向window
  1. new()绑定
function Person(){
    console.log(this) //代表的就是new出来的实例
}
let person=new Person

4>3>2>1 优先级

面试题

手写简单的call

第一步实现this

Function.prototype.call2=function(context){
    context.fn=this
    context.fn()
    delete context.fn
}
let foo={
    value:1
}
function bar(){
    console.log(this.value)
}
bar.call2(foo)

第二步可以传参数

Function.prototype.call2=function(context){
    context.fn=this //foo.fn=bar
    var args=[...arguments].slice(1)
    context.fn(...args)
    delete context.fn
}
let foo={
    value:1
}
function bar(name,age){
    console.log(name)
    console.log(age)
    console.log(this.value)
}
bar.call2(foo,'ludan',18)

第三步 传入null

var value=1
function bar(){
    console.log(this.value) //1
}
bar.call(null) 
Function.prototype.call2=function(context){
    var context=context||window
    context.fn=this //foo.fn=bar
    var args=[...arguments].slice(1)
    context.fn(...args)
    delete context.fn
}
var value=1
function bar(){
    console.log(this.value)
}
bar.call2(null)

ES6写法

Function.prototype.call2=function(context,...args){
    if(typeof context==='undefined'||context===null){
        context=window
    }
    let fnSymbol=Symbol()
    context[fnSymbol]=this //foo.fn=bar
    var fn=context[fnSymbol](...args)
    delete context[fnSymbol]
    return fn
}
var value=1
function bar(){
    console.log(this.value)
}
bar.call2(null)

手写bind

Function.prototype.bind2=function(context){
    var _this=this
    var args=Array.prototype.slice.call(arguments,1)
    return function(){
        var bindArgs=Array.prototype.slice.call(arguments)
    return _this.apply(context,args.concat(bindArgs))
    }
}

自己实现new objectFactory

objectFactory 代表new
function objectFactory(){
    var obj={}
    var Constructor=[].shift.call(arguments)
    obj.__proto__=Constructor.prototype
    //Constructor.apply(obj,arguments)
    var ret=Constructor.apply(obj,arguments)
    return typeof ret==='object'?ret:obj;
}

[].shift.call(arguments)

原因arguments是一个类数组,并没有shift方法

image.png

我们打印下Array.prototype

image.png

可以看到数组的原型对象上是有shift方法的。

所以通过Array.prototype.shift.call(arguments)来获取第一个参数。

使用call函数的作用是将this指向arguments,也就是让arguments调用了shift方法