前端知识体系(函数原型链,函数作用域)

认识原型链首先要认识原型的对象

JS中的对象有两个,一个普遍对象(Object),另一个是函数对(Function)。
而普通对象是通过对象字面量和使用new来创建对象的
一般来说,通过 new Function 产生的对象是函数对象,其他的都是普通对象。举例 说明:

function f1() {  //函数声明
    // todo
}
var f2 = function () {  //函数表达式
    // todo  
};
var f3 = new Function('x',  'console.log(x)');  //new一个函数
 
var o1 = {};  //对象字面量创建对象
var o2 = new Object();  //new创建
var o3 = new f1(); 
 
console.log(
    typeof f1,  //function
    typeof f2,  //function
    typeof f3,  //function
    typeof o1,  //object
    typeof o2,  //object
    typeof o3  //object
);

f1,f2再创建的时候,js会自动通过new function的方式创建他们,所以f123都是函数对象。
o12都是正常的创建对象。
但o3是new 一个函数对象来创建的对象,但是只有new function产生的对象才是函数对象,所以o3部署函数对象,它是普通对象。

认识原型链
function f(){}
var obj = new f()

在 JS 中,每当创建一个函数对象 f 时,该对象中都会内置一些属性,其中包括 prototype 和 proto, prototype 即原型对象,它记录着f的一些属性和方法。
需要注意的是,prototype 对 f 是不可见的,也就是说,f 不会查找 prototype 中的属性和方法。
比如

function f(){}
f.prototype.foo = "abc";
console.log(f.foo); // undefined

那么,prototype 有什么用呢? 其实 prototype 的主要作用就是继承。 通俗一点讲,prototype 中定义的属性和方法都是留给自己的 “后代” 用的,因此,子类完全可以访问prototype中的属性和方法。

function f() {}
f.prototype.foo = "abc";
var obj = new f();
console.log(obj.foo); // abc

其实呀,在new创造一个对象的时候,如上面,就会把父类的prototype赋值给新对象的—proto—属性,这样就是上面说的传承,因此子类才可以访问父类的prototype中的属性。
那父类的—proto—属性存的是谁的prototype呢,其实是object的属性
那这个传参的原理是什么呢,看下面原型链
在这里插入图片描述
其实在new一个新的对象的时候,会创建两条原型链,一条是上面这条obj的原型链,还有一条是 f 的原型链
f => Function.prototype => Obj.prototy
obj 对象拥有这样一个原型链以后,当 obj.foo 执行时,obj 会先查找自身是否有该属性,但不会查找自己的 prototype,当找不到foo时,obj 就沿着原型链依次去查找.
原型链的形成靠的是—proto—,而部不是prototype

认识函数作用域
function scope(){
    var foo = "global";
    if(window.getComputedStyle){
        var a = "I'm if";
        console.log("if:"+foo); //if:global
    }
    while(1){
        var b = "I'm while";
        console.log("while:"+foo);//while:global
        break;
    }
    !function (){
        var c = "I'm function";
        console.log("function:"+foo);//function:global
    }();
    console.log(
         foo,//global
         a, // I'm if
         b, // I'm while
         c  // c is not defined ,因为c在闭包里面,它有自己的作用域
    );
}
scope();

scope函数中定义的foo变量,除过自身可以访问以外,还可以在if语句、while语句和内嵌的匿名函数中访问。 因此,foo的作用域就是scope函数体。
在javascript中,if、while、for 等代码块不能形成独立的作用域。因此,javascript中没有块级作用域,只有函数作用域和全局作用域。
但是,在JS中有一种特殊情况:
如果一个变量没有使用var声明,window便拥有了该属性,因此这个变量的作用域不属于某一个函数体,而是window对象。

作用域链

所谓作用域链就是:一个函数体中嵌套了多层函数体,并在不同的函数体中定义了同一变量, 当其中一个函数访问这个变量时,便会形成一条作用域链,如代码

 foo = "window";
function first(){
    var foo = "first";
    function second(){
       var foo = "second";
       console.log(foo);
    }
    function third(){
       console.log(foo);
    }
    second(); //second  second->first->window,
    third();  //first   third->first->window
}
first();

当执行second时,JS引擎会将second的作用域放置链表的头部,其次是first的作用域,最后是window对象,于是会形成如下作用域链:

second->first->window, 此时,JS引擎沿着该作用域链查找变量foo, 查到的是 second

当执行third时,third形成的作用域链:third->first->window, 因此查到的是:frist

作用域链延长(with/catch)

with 和 catch 语句主要用来临时扩展作用域链, 将语句中传递的变量对象添加到作用域的头部。语句结束后,原作用域链恢复正常
//with语句

foo ="window";

function first(){

    var foo ="first";

    function second(){

       var foo ="second";

       console.log(foo);

    }

    function third(obj){

       console.log(foo);//first

       with (obj){

           console.log(foo);//obj

       }

       console.log(foo);//first

    }

    var obj = {foo:'obj'};

    third(obj);

}

first();

 

//catch语句

var e =new Error('a');

try {

    throw new Error('b');

}catch (e) {

    console.log(e.message);//b

} 

在执行third()时,传递了一个obj对象,obj 中有属性foo, 在执行with语句时,JS引擎将obj放置在了原链表的头部,于是形成的作用域链如下:
obj->third->first->window, 此时查找到的foo就是obj中的foo,因此输出的是 obj
而在with之前和之后,都是沿着原来的链表进行查找,从而说明在with语句结束后,作用域链已恢复正常。看下面例子

var foo = "window";
var obj = {
    foo : "obj",
    getFoo : function() {
        return function() {
            return this.foo;
        };
    }
};
var f = obj.getFoo();
f(); //window   因为相当于只是执行了最里面那个函数,没有谁来调用
如果能把getFoo里面的第一层this存起来,放到第二层里面用,就是object
执行var  f = obj.getFoo()返回的是一个匿名函数,相当于:
var f = function(){
     return this.foo;
}
// f() 相当于window.f(), 因此f中的this指向的是window对象,this.foo相当于window.foo, 所以f()返回"window"

这里加一个关于js函数作用域和全局作用域有关的东西
下面代码就展示了js会有一个预解析。解析之后就会有一个语法树,然后再执行项执行之前把变量提升到作用域,这样也就是说只是声明了一下变量,没有赋值。

if (false) {
    var log = "能否输出?"
}
console.log(log); // undefined 这里会有一个预解析(解析后会有一个语法树,再进入执行阶段)的过程,var 声明的变量会在执行前将变量提升到作用域上

if (false) {
    let log1 = "能否输出?" // let 声明的变量只存在离他最近的大括号内
}
console.log(log1); // Uncaught ReferenceError: log1 is not defined,即使是 true log1 这行还是会报错

再如
使用 var 声明 for 循环的初始变量 j,最终 j 点击按钮的时候会输出计算后的最终值
使用 let 声明 for 循环的初始变量 j,由于存在块级别作用域,会将 j 的值锁死在当前作用域内,因此点击会输出对应的 j 值

for (let j = 0; j < 2; j++) {
	document.querySelector('#let').addEventListener('click', () => console.log(j))
}

let 的写法明显优雅与闭包,但 let 声明变量性能会稍逊于 var,因为编译时会自动生成闭包

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值