原型链作用域链

原型链

普通对象与函数对象

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')
相关推荐
come1123410 分钟前
Vue 响应式数据传递:ref、reactive 与 Provide/Inject 完全指南
前端·javascript·vue.js
前端风云志32 分钟前
TypeScript结构化类型初探
javascript
musk12121 小时前
electron 打包太大 试试 tauri , tauri 安装打包demo
前端·electron·tauri
翻滚吧键盘1 小时前
js代码09
开发语言·javascript·ecmascript
万少2 小时前
第五款 HarmonyOS 上架作品 奇趣故事匣 来了
前端·harmonyos·客户端
OpenGL2 小时前
Android targetSdkVersion升级至35(Android15)相关问题
前端
rzl022 小时前
java web5(黑马)
java·开发语言·前端
Amy.Wang2 小时前
前端如何实现电子签名
前端·javascript·html5
海天胜景2 小时前
vue3 el-table 行筛选 设置为单选
javascript·vue.js·elementui
今天又在摸鱼2 小时前
Vue3-组件化-Vue核心思想之一
前端·javascript·vue.js