javascript作用域、执行上下文、原型和原型链

一、作用域
js中作用域是指可访问变量,对象,函数的集合,也就是调用它们能生效的代码区块。在js中没有块级作用域,只有全局作用域和函数作用域

成都创新互联是一家集网站建设,阿拉尔企业网站建设,阿拉尔品牌网站建设,网站定制,阿拉尔网站建设报价,网络营销,网络优化,阿拉尔网站推广为一体的创新建站企业,帮助传统企业提升企业形象加强企业竞争力。可充分满足这一群体相比中小企业更为丰富、高端、多元的互联网需求。同时我们时刻保持专业、时尚、前沿,时刻以成就客户成长自我,坚持不断学习、思考、沉淀、净化自己,让我们为更多的企业打造出实用型网站。

1、全局,函数作用域

var a = 10
function f1(){
        var b = c = 20;
        console.log(a);     //10
        console.log(c);     //20
        function f2() {
             console.log(b); //20
        }f2();
}
f1();
console.log(a);     //10
console.log(c);     //20
console.log(b);     //error

var b = c = 20 是指 var b = c; c = 20
在f1函数中c没使用var声明,所以c为全局变量,b为局部变量,绑定在f1函数下,外部访问不到。

2、模仿块级作用域
没有块级作用域,但是有if(),for()等块语句,在块语句内部定义的变量会保留在它们已经存在的作用域内,举个栗子:

if(true) {
        var word = 'hello';
        console.log(word);  //hello
}
console.log(word);      //hello

if()语句存在全局作用域下,所以内部定义的变量存在于全局作用域中,无论在哪都可以访问。

function add(num) {
        if(num > 10) {
                var num = 10;
                console.log(num);   //10
        }
        console.log(num);       //10
}
add(11);
console.log(num);   //Uncaught ReferenceError: num is not defined

此时if()在add函数中,内部定义的变量存在于add函数的作用域中,只有在add函数和块语句中才可以访问到,外部无法访问。

3、使用自执行的匿名函数包裹块语句构建块作用域,也叫私有作用域

function add(num) {
        for(var i = 0; i < num; i++) {
            console.log(i);     //0,1,2,3,4,5,6,7,8,9
        }
        console.log(i);     //10
    }
add(10);

将代码改为

function add(num) {
        (function () {
            for(var i = 0; i < num; i++) {
                     console.log(i);    //0,1,2,3,4,5,6,7,8,9
                }
        })()
    console.log(i);     //Uncaught ReferenceError: i is not defined
}
add(10);

此时变量i只能在for()循环中访问到,在add函数和外部都无法访问,并且在匿名函数中定义的任何变量都会在执行结束时被销毁,所以变量i只能在for()循环中使用。

二、执行上下文
javascript运行的代码环境有三种:

  • 全局代码:代码默认运行的环境,最先会进入到全局环境中
  • 函数代码:在函数的局部环境中运行的代码
  • Eval代码:在Eval()函数中运行的代码
  • 全局上下文:是最外围的一个执行环境,web浏览器中被认为是window对象。在初始化代码时会,先进入全局上下文中
  • 执行上下文:每当一个函数被调用时就会为该函数创建一个执行上下文,每个函数都有自己的执行上下文
function f1() {
        var f1Context = 'f1 context';
        function f2() {
                var f2Context = 'f2 context';
                function f3() {
                        var f3Context = 'f3 context';
                        console.log(f3Context);
                }
                f3();
                console.log(f2Context);
        }
        f2();
        console.log(f1Context);
}
f1();
//结果:
//f3 context
//f2 context
//f1 context

全局上下文:拥有f1()
f1()的执行上下文:有变量f1Context和f2()
f2()的执行上下文:有变量f2Context和f3()
f3()的执行上下文:有变量f3Context

ECS:执行环境栈,可以理解为代码执行的土壤,即代码执行的地方
js是单线程,任务都为同步任务的情况下某一时间只能执行一个任务

执行一段代码首先会进入全局上下文中,并将其压入ECS中栈顶
首先执行f1(),为其创建执行上下文,进入到栈顶位置,全局上下文被往下压
f1()中有f2(),再为f2()创建f2()的执行上下文,f2()进入到栈顶位置,f1()被往下压,
依次,最终全局上下文被压入到栈底,f3()的执行上下文在栈顶
f3()执行完后,ECS就会弹出其执行上下文(内部变量随之被销毁),f3()上下文弹出后,f2()上下文来到栈顶,开始执行f2(),依次,最后ECS中只剩下全局上下文,它等到应用程序退出,例如浏览器关闭时销毁。
javascript作用域、执行上下文、原型和原型链

function foo(i) {
        if(i  == 3) {
                return;
        }
        foo(i+1);
        console.log(i);
}
foo(0);

ECS栈顶为foo(3)的的上下文,直接return弹出后,栈顶变成foo(2)的上下文,执行foo(2),输出2并弹出,执行foo(1),输出1并弹出,执行foo(0),输出0并弹出,关闭浏览器后全局EC弹出,所以结果为2,1,0。

三、原型和原型链
1、对象(普通对象、函数对象)

  • 所有引用类型(函数,数组,对象)都拥有proto属性(隐式原型)
  • 所有函数拥有prototype属性(显式原型)(仅限函数)
  • 原型对象:拥有prototype属性的对象,在定义函数时就被创建

2、构造函数
//创建构造函数

function Word(words){
        this.words = words;
}
Word.prototype = {
        alert(){
                alert(this.words);
        }
}
//创建实例
var w = new Word("hello world");
w.print = function(){
        console.log(this.words);
        console.log(this);  //Person对象
}
w.print();  //hello world
w.alert();  //hello world

print()方法是w实例本身具有的方法,所以w.print()打印hello world;alert()不属于w实例的方法,属于构造函数的方法,w.alert()也会打印hello world,因为实例继承,构造函数原型上的方法。
实例w的隐式原型指向它构造函数的显式原型,指向的意思是恒等于
w.proto === Word.prototype

当调用某种方法或查找某种属性时,首先会在自身调用和查找,如果自身并没有该属性或方法,则会去它的proto属性中调用查找,也就是它构造函数的prototype中调用查找。所以很好理解实例继承构造函数的方法和属性:
w本身没有alert()方法,所以会去Word()的显式原型Word.prototype中调用alert(),即实例继承构造函数的方法。  

3、原型和原型链

Function.prototype.a = "a";
Object.prototype.b = "b";
function Person(){}
console.log(Person);    //function Person()
let p = new Person();
console.log(p);         //Person {} 对象
console.log(p.a);       //undefined
console.log(p.b);       //b

实例p上面并没有a属性,那么会通过proto向上查找,根据:
p.proto== Person.prototype,然后Person.prototype上也没有a属性,Person.prototype仍然是一个对象,上面仍然具有proto属性,根据:
Person.prototype.proto== Function.prototype //false

Person.prototype.proto== Object.prototype //true

Object.prototype.proto== null //再上一级就是null了

此时,Object.prototype.b = "b",所以p.a是undefined,而p.b是"b",
因为没有定义Object.prototype.a,只定义了Function.prototype.a

总结:

1.查找属性,如果本身没有,则会去proto中查找,也就是构造函数的显式原型中查找,如果构造函数中也没有该属性,因为构造函数也是对象,也有proto,那么会去它的显式原型中查找,一直到null,如果没有则返回undefined
2.p.proto.constructor  == function Person(){}
3.p._proto.proto_== Object.prototype
4.p.
proto.proto.proto== Object.prototype.proto == null         
5.通过
proto__形成原型链而非protrotype

javascript作用域、执行上下文、原型和原型链


文章标题:javascript作用域、执行上下文、原型和原型链
标题来源:http://myzitong.com/article/jhohge.html