图示---作用域链和原型链的区分

在了解作用域链之前,我们应该需要先了解执行上下文,和执行上下文栈。

一、作用域和作用域链

1.什么是作用域。

针对变量,比如说创建一个函数,函数里面又包含了一个函数,也是函数的嵌套,那么就有了四个个作用域:

<script>
    var a = 1
    function f() {
        var a = 2
        var b = 3
        function f1() {
            var c = 5
            function f2() {
                //f2逐级向上一级作用域找 最终都可以得到a,b,c
                console.log(a, b, c);   //2 3 5
            }
            f2()
        }
        f1()
        //在f的内部作用域中能找到  a  b 
        console.log(a, b);   //2 3
        //因为c在f1作用域中,从执行上下文角度分析,得不到c,
        //f内部作用域中找不到c,并且外部其上级作用域也没有c,所以会报错
        console.log(c);   //c is not defined
    }
    f()
</script>

作用域的特点: 先在自己的变量范围查找,若找不到,就沿着作用域往上找
这几个作用域也就形成了一条作用域链。【全局作用域==>函数f作用域==>函数f1作用域 ==>函数f2作用域。
在这里插入图片描述

2.总结:何为作用域链

JavaScript上每一个函数执行时,会先在自己创建的作用域上找对应属性值。若找不到则往父级的作用域上找,再找不到则再上一层找,直到找到大boss:window(全局作用域)。像这种逐级查找的作用域 ----就是JavaScript中的作用域链。

二、原型和原型链

抛出几个问题:
什么是原型?为什么需要原型?使用它又有什么作用?原型链又是什么东西?
你可以带着几个问题阅读下面文章:
【首先先坐下铺垫,了解下与原型相关的知识】

1.什么是对象

对象的定义:
对象被定义为“无序属性的集合,其属性可以包含基本值,对象或者函数”。

<script>
    //创建一个father对象  字面量方式创建
    let father = {
        name: 'xiaoming',
        age: 25,
        sex: "men",
        getName: function () {return this.name },
        parent: {},
    }
    //也可以new一个obj对象 
    let obj=new Object()
</script>

我们可以通过一下来访问对象里面的属性:
当访问一个属性时:

person.name
// 或者
person['name']

当同时访问两个属性时:

['name', 'age'].forEach(function(item) {
    console.log(person[item]);
})
2.工厂模式

通过上面的方式创建对象很简单,但是当我们在实际中需要很多对象,那么还需要重复定义对象的。

    let father = {
        name: 'xiaoming',
        age: 25,
        sex: "men",
        getName: function () {return this.name },
        parent: {},
    }
    let father = {
        name: '小强',
        age: 15,
        sex: "men",
        getName: function () {return this.name },
        parent: {},
    }

很明显这种方式不合理,代码过于繁琐累赘。所以可以用工厂模式。
这个工厂就像是一个函数。我们可以通过这个工厂创建很多的对象。

<script>
let createfather = function(){
    //声明一个中间对象
    let o=new Object();
    //将对象属性  挂载到对象上
    o.name=name,
    o.age=age,
    o.getName=function(){
        return this.name
    }
    return ;
}
//创建两个实例对象
let father1=createfather('xiaoming',20);
let father2=createfather('qiang',23);
</script>

这样可以节省了一些代码。就能创建很多个father对象,但是这里仍然有问题。就是没有办法识别对象实例的类型。使用instanceof可以识别对象的类型。如下:

var 0 = {};
var createfather = function() {};
console.log(o instanceof Object);  // true
console.log(createfather instanceof Function); // true

因此下面可以使用构造函数解决这个问题。

3.构造函数

在JavaScript中,new关键字可以让一个函数变得与众不同

var Father = function(name, age) {
    this.name = name;
    this.age = age;
    this.getName = function() {
        return this.name;
    }
}

var p1 = new Father('Ness', 20);
console.log(p1.getName());  // Ness

console.log(p1 instanceof Person); // true

关于构造函数,如果你暂时不能够理解new的具体实现,就先记住下面这几个结论吧。

  • 与普通函数相比,构造函数并没有任何特别的地方,首字母大写只是我们约定的小规定,用于区分普通函数;

  • new关键字让构造函数具有了与普通函数不同的许多特点,而new的过程中,执行了如下过程:

      1.声明一个中间对象;
      2.将该中间对象的原型指向构造函数的原型;
      3.将构造函数的this,指向该中间对象;
      4.返回该中间对象,即返回实例对象。
    
4.原型

上面的工厂模式,还是构造函数模式,说白了,还是一个对象的复制过程,假如我们声明了100个father对象,那么就有100个getName方法重生。这样内存就需要给他们分配空间,占用大量的内存,这样很大浪费了内存空间。

这时候我们想,既然他们都是实现同一个功能,那么能不能让每一个实例对象都访问同一个方法呢?

这时候就需要原型对象帮我们解决问题了:

在 JavaScript 中,每当定义一个对象(函数也是对象)时候,对象中都会包含一些预定义的属性。其中每个函数对象都有一个prototype 属性,prototype就是原型,这个属性指向函数的原型对象,这个对象就是我们说的原型对象

当我们创建对象是,我们可以根据自己的需求,选择性的将一些属性和方法通过prototype属性,挂载到原型对象上。而每一个new 出来的实例,都会有一个_proto_属性,该属性指向构造函数的原型对象,通过这个属性,让实例对象也能访问原型对象上的方法或者属性。依次所有的实例都能通过_proto_访问原型对象时,原型对象的方法和属性就成了共有方法与共有属性。

// 声明构造函数
function Father(name, age) {
    this.name = name;
    this.age = age;
}
// 通过prototye属性,将方法挂载到原型对象上
Person.prototype.getName = function() {
    return this.name;
}
var f1 = new Father('tom', 10);
var f2 = new Father('jack', 22);

图示:
在这里插入图片描述
通过图示我们可以看出,构造函数的prototype与所有实例对象的__proto__都指向原型对象。而原型对象的constructor指向构造函数。

下面验证实例中和原型访问的优先级比较:
当我们访问实例对象中的属性或者方法时,会优先访问实例对象自身的属性和方法。

// 声明构造函数
function Father(name, age) {
    this.name = name;
    this.age = age;
    this.getName = function() {
        console.log('this is constructor.');
}
// 通过prototye属性,将方法挂载到原型对象上
Person.prototype.getName = function() {
    return this.name;
}
var f1 = new Father('tom', 10);
var f2 = new Father('jack', 22);

在这个例子中,我们同时在原型与构造函数中都声明了一个getName函数,运行代码的结果表示原型中的getName并没有被访问。
拓展更简单的原型写法:

function Person() {}

Person.prototype = {
    constructor: Person,
    getName: function() {},
    getAge: function() {},
    sayHello: function() {}
}

注意: Person.prototype = {}实际上是重新创建了一个{}对象并赋值给Person.prototype,这里的{}并不是最初的那个原型对象。因此它里面并不包含constructor属性。为了保证正确性,我们必须在新创建的{}对象中显示的设置constructor的指向。即上面的constructor: Person。

5.原型链

了解到原型,那么原型链就很容易的理解了。

我们都知道函数都有一个toString方法,那么这么方法在哪里呢?
先声明一个函数:

function add()   {}

可以用原型链来表示:
在这里插入图片描述
其中add是Function对象的实例。而Function的原型对象同时又是Object原型的实例。这样就构成了一条原型链。原型链的访问,其实跟作用域链有很大的相似之处,他们都是一次单向的查找过程。因此实例对象能够通过原型链,访问到处于原型链上对象的所有属性与方法。这也是foo最终能够访问到处于Object原型对象上的toString方法的原因。

三、作用域链与原型链的区分:

区别:

  1. 作用域是对于变量而言,原型链是对于对象的属性。
  2. 作用域链顶层是window,原型链顶层是Object。

联系: 从链表开头寻找,直到找到为止。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值