原型链作用域链

原型链

普通对象与函数对象

JavaScript 中,万物皆对象!但对象也是有区别的。分为普通对象和函数对象,Object 、Function 是 JS 自带的函数对象。下面举例说明

javascript 复制代码
var o1 = {}; 
var o2 =new Object();
var o3 = new f1();
 
function f1(){}; 
var f2 = function(){};
var f3 = new Function('str','console.log(str)');
 
console.log(typeof Object); //function 
console.log(typeof Function); //function  
 
console.log(typeof f1); //function 
console.log(typeof f2); //function 
console.log(typeof f3); //function   
 
console.log(typeof o1); //object 
console.log(typeof o2); //object 
console.log(typeof o3); //object

在上面的例子中 o1 o2 o3 为普通对象,f1 f2 f3 为函数对象。怎么区分,其实很简单,凡是通过 new Function() 创建的对象都是函数对象,其他的都是普通对象。f1,f2,归根结底都是通过 new Function()的方式进行创建的。Function Object 也都是通过 New Function()创建的。 一定要分清楚普通对象和函数对象,下面我们会常常用到它。

构造函数

我们先复习一下构造函数的知识:

ini 复制代码
function Person(name, age, job) {
 this.name = name;
 this.age = age;
 this.job = job;
 this.sayName = function() { alert(this.name) } 
}
var person1 = new Person('Zaxlct', 28, 'Software Engineer');
var person2 = new Person('Mick', 23, 'Doctor');

上面的例子中 person1 和 person2 都是 Person 的实例。这两个实例都有一个 constructor (构造函数)属性,该属性(是一个指针)指向 Person。 即:

ini 复制代码
console.log(person1.constructor == Person); //true
console.log(person2.constructor == Person); //true

我们要记住两个概念(构造函数,实例): person1 和 person2 都是 构造函数 Person 的实例 一个公式: 实例的构造函数属性(constructor)指向构造函数。

原型对象

在 JavaScript 中,每当定义一个对象(函数也是对象)时候,对象中都会包含一些预定义的属性。其中每个函数对象 都有一个prototype 属性,这个属性指向函数的原型对象 。(先用不管什么是 proto 第二节的课程会详细的剖析)

ini 复制代码
function Person() {}
Person.prototype.name = 'Zaxlct';
Person.prototype.age  = 28;
Person.prototype.job  = 'Software Engineer';
Person.prototype.sayName = function() {
  alert(this.name);
}
  
var person1 = new Person();
person1.sayName(); // 'Zaxlct'
 
var person2 = new Person();
person2.sayName(); // 'Zaxlct'
 
console.log(person1.sayName == person2.sayName); //true

我们得到了本文第一个「定律」:

每个对象都有 proto 属性,但只有函数对象才有 prototype 属性

那什么是原型对象呢? 我们把上面的例子改一改你就会明白了:

javascript 复制代码
Person.prototype = {
   name:  'Zaxlct',
   age: 28,
   job: 'Software Engineer',
   sayName: function() {
     alert(this.name);
   }
}

原型对象,顾名思义,它就是一个普通对象(废话 = =!)。从现在开始你要牢牢记住原型对象就是 Person.prototype ,如果你还是害怕它,那就把它想想成一个字母 A: var A = Person.prototype


在上面我们给 A 添加了 四个属性:name、age、job、sayName。其实它还有一个默认的属性:constructor )在默认情况下,所有的原型对象 都会自动获得一个 constructor(构造函数)属性,这个属性(是一个指针)指向 prototype 属性所在的函数(Person) 上面这句话有点拗口,我们「翻译」一下:A 有一个默认的 constructor 属性,这个属性是一个指针,指向 Person。即: Person.prototype.constructor == Person

在上面第二小节《构造函数》里,我们知道实例的构造函数属性(constructor)指向构造函数 :person1.constructor == Person

这两个「公式」好像有点联系:

ini 复制代码
person1.constructor == Person
Person.prototype.constructor == Person

原型对象的作用 为每个实例对象存储共享的方法和属性,它仅仅是一个普通对象而已。并且所有的实例是共享同一个原型对象,因此有别于实例方法或属性,原型对象仅有一份。所有就会有如下等式成立:

结论:原型对象(Person.prototype)是 构造函数(Person)的一个实例。 原型对象其实就是普通对象(但 Function.prototype 除外,它是函数对象,但它很特殊,他没有prototype属性(前面说道函数对象都有prototype属性))。看下面的例子:

javascript 复制代码
 function Person(){};
 console.log(Person.prototype) //Person{}
 console.log(typeof Person.prototype) //Object
 console.log(typeof Function.prototype) // Function,这个特殊
 console.log(typeof Object.prototype) // Object
 console.log(typeof Function.prototype.prototype) //undefined

Function.prototype 为什么是函数对象呢?

ini 复制代码
var A = new Function ();
 Function.prototype = A;

上文提到凡是通过 new Function( ) 产生的对象都是函数对象。因为 A 是函数对象,所以Function.prototype 是函数对象。

那原型对象是用来做什么的呢?主要作用是用于继承。举个例子:

javascript 复制代码
  var Person = function(name){
    this.name = name; // tip: 当函数执行时这个 this 指的是谁?
  };
  Person.prototype.getName = function(){
    return this.name;  // tip: 当函数执行时这个 this 指的是谁?
  }
  var person1 = new person('Mick');
  person1.getName(); //Mick

从这个例子可以看出,通过给 Person.prototype 设置了一个函数对象的属性,那有 Person 的实例(person1)出来的普通对象就继承了这个属性。具体是怎么实现的继承,就要讲到下面的原型链了。

小问题,上面两个 this 都指向谁?

ini 复制代码
  var person1 = new person('Mick');
  person1.name = 'Mick'; // 此时 person1 已经有 name 这个属性了
  person1.getName(); //Mick

从这个例子可以看出,通过给 Person.prototype 设置了一个函数对象的属性,那有 Person 的实例(person1)出来的普通对象就继承了这个属性。具体是怎么实现的继承,就要讲到下面的原型链了。

小问题,上面两个 this 都指向谁?

ini 复制代码
  var person1 = new person('Mick');
  person1.name = 'Mick'; // 此时 person1 已经有 name 这个属性了
  person1.getName(); //Mick 

proto

JS 在创建对象(不论是普通对象还是函数对象)的时候,都有一个叫做__proto__ 的内置属性,用于指向创建它的构造函数的原型对象。 对象 person1 有一个 __proto__属性,创建它的构造函数是 Person,构造函数的原型对象是 Person.prototype ,所以: person1.proto == Person.prototype

JS 在创建对象(不论是普通对象还是函数对象)的时候,都有一个叫做__proto__ 的内置属性,用于指向创建它的构造函数的原型对象。 对象 person1 有一个 __proto__属性,创建它的构造函数是 Person,构造函数的原型对象是 Person.prototype ,所以: person1.proto == Person.prototype

请看下图:

根据上面这个连接图,我们能得到:

ini 复制代码
Person.prototype.constructor == Person;
person1.__proto__ == Person.prototype;
person1.constructor == Person;

不过,要明确的真正重要的一点就是,这个连接存在于实例(person1)与构造函数(Person)的原型对象(Person.prototype)之间,而不是存在于实例(person1)与构造函数(Person)之间。

构造器

熟悉 Javascript 的童鞋都知道,我们可以这样创建一个对象: var obj = {} 它等同于下面这样: var obj = new Object()

obj 是构造函数(Object)的一个实例。所以: obj.constructor === Object obj.proto === Object.prototype

新对象 obj 是使用 new 操作符后跟一个构造函数来创建的。构造函数(Object)本身就是一个函数(就是上面说的函数对象),它和上面的构造函数 Person 差不多。只不过该函数是出于创建新对象的目的而定义的。所以不要被 Object 吓倒。 同理,可以创建对象的构造器不仅仅有 Object,也可以是 Array,Date,Function等。 所以我们也可以构造函数来创建 Array、 Date、Function

ini 复制代码
var b = new Array();
b.constructor === Array;
b.__proto__ === Array.prototype;
 
var c = new Date(); 
c.constructor === Date;
c.__proto__ === Date.prototype;
 
var d = new Function();
d.constructor === Function;

这些构造器都是函数对象:

原型链

小测试来检验一下你理解的怎么样:

person1.proto 是什么? Person.proto 是什么? Person.prototype.proto 是什么? Object.proto 是什么? Object.prototype__proto__ 是什么?

答案: 第一题: 因为 person1.proto === person1 的构造函数.prototype 因为 person1的构造函数 === Person 所以 person1.proto === Person.prototype

第二题: 因为 Person.proto === Person的构造函数.prototype 因为 Person的构造函数 === Function 所以 Person.proto === Function.prototype

第三题: Person.prototype 是一个普通对象,我们无需关注它有哪些属性,只要记住它是一个普通对象。 因为一个普通对象的构造函数 === Object 所以 Person.prototype.proto === Object.prototype

第四题,参照第二题,因为 Person 和 Object 一样都是构造函数

第五题: Object.prototype 对象也有proto属性,但它比较特殊,为 null 。因为 null 处于原型链的顶端,这个只能记住。 Object.prototype.proto === null

什么是原型链 简单理解就是原型组成的链,对象的__proto__它的是原型,而原型也是一个对象,也有__proto__属性,原型的__proto__又是原型的原型,就这样可以一直通过__proto__想上找,这就是原型链,当向上找找到Object的原型的时候,这条原型链就算到头了。

函数对象 (复习一下前面的知识点)

所有函数对象的proto都指向Function.prototype,它是一个空函数(Empty function)

ini 复制代码
Number.__proto__ === Function.prototype  // true
Number.constructor == Function //true
 
Boolean.__proto__ === Function.prototype // true
Boolean.constructor == Function //true
 
String.__proto__ === Function.prototype  // true
String.constructor == Function //true
 
// 所有的构造器都来自于Function.prototype,甚至包括根构造器Object及Function自身
Object.__proto__ === Function.prototype  // true
Object.constructor == Function // true
 
// 所有的构造器都来自于Function.prototype,甚至包括根构造器Object及Function自身
Function.__proto__ === Function.prototype // true
Function.constructor == Function //true
 
Array.__proto__ === Function.prototype   // true
Array.constructor == Function //true
 
RegExp.__proto__ === Function.prototype  // true
RegExp.constructor == Function //true
 
Error.__proto__ === Function.prototype   // true
Error.constructor == Function //true
 
Date.__proto__ === Function.prototype    // true
Date.constructor == Function //true

JavaScript中有内置(build-in)构造器/对象共计12个(ES5中新加了JSON),这里列举了可访问的8个构造器。剩下如Global不能直接访问,Arguments仅在函数调用时由JS引擎创建,Math,JSON是以对象形式存在的,无需new。它们的proto是Object.prototype。如下

javascript 复制代码
Math.__proto__ === Object.prototype  // true
Math.construrctor == Object // true
 
JSON.__proto__ === Object.prototype  // true
JSON.construrctor == Object //true

作用域

变量提升

首先我们要知道,js的执行顺序是由上到下的,但这个顺序,并不完全取决于你,因为js中存在变量的声明提升。

这里比较简单,直接上代码

javascript 复制代码
console.log(a)  //undefined
var a = 100

fn('zhangsan')
function fn(name){
    age = 20
    console.log(name, age)  //zhangsan 20
    var age
}

打印a的时候,a并没有声明,为什么不报错,而是打印undefined。

执行fn的时候fn并没有声明,为什么fn的语句会执行?

这就是变量的声明提升,代码虽然写成这样,但其实执行顺序是这样的。

javascript 复制代码
var a

function fn(name){
    age = 20
    console.log(name, age)
}

console.log(a) 
a = 100

fn('zhangsan')
相关推荐
GIS程序媛—椰子23 分钟前
【Vue 全家桶】7、Vue UI组件库(更新中)
前端·vue.js
DogEgg_00130 分钟前
前端八股文(一)HTML 持续更新中。。。
前端·html
ZL不懂前端33 分钟前
Content Security Policy (CSP)
前端·javascript·面试
乐闻x36 分钟前
ESLint 使用教程(一):从零配置 ESLint
javascript·eslint
木舟100936 分钟前
ffmpeg重复回听音频流,时长叠加问题
前端
王大锤43911 小时前
golang通用后台管理系统07(后台与若依前端对接)
开发语言·前端·golang
我血条子呢1 小时前
[Vue]防止路由重复跳转
前端·javascript·vue.js
黎金安1 小时前
前端第二次作业
前端·css·css3
啦啦右一1 小时前
前端 | MYTED单篇TED词汇学习功能优化
前端·学习