作用域&作用域链
面试题
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的指向规则
- 默认绑定
function fn (){
console.log(this) //this -> window
}
fn() // fn()独立调用函数,this指向window
- 隐式绑定
var obj = {
test:1,
fn:function(){
console.log(this) //this -> obj
}
}
obj.fn() // obj.fn() //指向 obj
- 显示绑定
apply(),call(),bind() apply,call,bind当传入null,undefind时,this指向window
- 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方法
我们打印下Array.prototype
可以看到数组的原型对象上是有shift方法的。
所以通过Array.prototype.shift.call(arguments)来获取第一个参数。
使用call函数的作用是将this指向arguments,也就是让arguments调用了shift方法