重学前端(7)词法做用域,原型链

基础语法

目标

  • 知道函数有原型对象javascript

  • 构造函数,原型对象和实例三者的关系java

  • 原型链面试

  • 会给内置的对象添加自定义的方法数组

  • 会使用更简单的原型使用方式浏览器

1. 做用域问题

1.1 做用域:

####1.1.1 全局做用域函数

整个js执行的环境就是一个全局做用域ui

####1.1.2 局部做用域this

es5规范中: 只有函数才能构成一个局部做用域es5

####1.1.3 做用域链spa

将js执行时变量查找的方式,以链式形式表示出来

var num = 0;
function fn(){
    var num1;
    num1 = 1;
    console.log(num1);
    function fnSon(){
        var num2 = 2;
        console.log(2)
    }
}
复制代码

将上面的代码用链式的形式展现出来

1.2 词法做用域规则

词法做用域又叫静态做用域.

  • 做用域是在代码书写完毕以后就造成了,与代码执行无关

  • 内部做用域能够访问外部做用域的变量,可是外部不能够访问内部的

  • 函数的形参就至关于在当前函数的做用域中申明了这个变量

  • 访问变量时,先在本身的做用域中查找,若是没有则沿着做用域链往上找,直到全局.若是全局也没有就报错

  • 给变量赋值以前,要先找变量.查找变量也是沿着做用域链查找,直到全局,若是全局也没有,则会再全局做用域建立这个变量(隐式全局)

  • 代码执行以前先考虑预解析规则,调用函数时,执行函数里的代码以前,函数内也要先执行预解析规则

###面试题:

var a;
if ("a" in window) {
    var a = 10;
}
alert(a);  //
//=============================================
var foo = 1;
function bar() {
    if (!foo) {
        var foo = 10;
    }
    alert(foo); //
}
 bar();
//================================================
var num = 123;
function f1(num) {
    console.log(num); // 
}
function f2() {

    var num = 456;
    f1(num);
}
f2();

//======================================================
 function fn(){
   var a = 1, b = 1, c = 1;
 }
 fn();
 console.log(c); //
 console.log(b); //
 console.log(a); //
  
 function fn1(){
   var a = b = c = 1;
 }
 fn1();
 console.log(c); //
 console.log(b); //
 console.log(a); //
//========================================================
var a = 1;
function fn(){
    var a = 2;
    function fnSon(a){
        a = 3;
        console.log(a); //
    }
    fnSon();
    console.log(a);  // 
}
console.log(a);  // 
fn();
console.log(a); // 

//==========================================================
var a ;
function a(){
    console.log('呵呵')
    function a(){
        a = 4;
        console.log('哈哈')
    }
    a();
    console.log(a);  //
}
a();
console.log(a); //
//=================================================================
var a = 1;
function a(){
    a++;
}
console.log(a) //

//==================================================================
 var a = { x : 1 }
 var b = a;
 a.x = a = { n : 1};
 console.log(a.x); //
 console.log(b.x); //
 
 //本身把代码运行下,看看和本身想的结果有啥差异
复制代码

###1.3. 建立对象的方式

####1.3.1 简单方式

咱们能够直接经过 new Object() 建立:

var person = new Object()
person.name = 'Jack';
person.age = 18;
person.sayName = function () {
  console.log(this.name);   //jack
}
复制代码

每次建立经过 new Object() 比较麻烦,因此能够经过它的简写形式对象字面量来建立:

字面量形式的建立方式,底层也是new Object建立出来的

var person = {
  name: 'Jack',
  age: 18,
  sayName: function () {
    console.log(this.name);
  }
}
复制代码

上面的写法比较简单,可是若是咱们要建立多个person对象呢?

var person1 = {
  name: 'Jack',
  age: 18,
  sayName: function () {
    console.log(this.name);
  }
}

var person2 = {
  name: 'Mike',
  age: 16,
  sayName: function () {
    console.log(this.name);
  }
}

var person3 = {
  name: 'zs',
  age: 17,
  sayName: function () {
    console.log(this.name);
  }
}
复制代码

经过上面的代码咱们不难看出,这样写的代码太过冗余。

####1.3.2 简单方式的改进:工厂函数

咱们能够写一个函数,解决代码重复问题:

function createPerson (name, age) {
  return {
    name: name,
    age: age,
    sayName: function () {
      console.log(this.name);
    }
  }
}
复制代码

而后生成对象:

var p1 = createPerson('Jack', 18);
p1.sayName(); //jack
var p2 = createPerson('Mike', 18);
p2.sayName(); // Mike
复制代码

####1.3.3 更优雅的方式(更推荐使用的一种方式):构造函数

一种更优雅的工厂函数就是下面这样,构造函数:

function Person (name, age) {
  this.name = name;
  this.age = age;
  this.sayName = function () {
    console.log(this.name);
  }
}

var p1 = new Person('Jack', 18);
p1.sayName() // => Jack

var p2 = new Person('Mike', 23);
p2.sayName() // => Mike
复制代码

**构造函数中new关键字作什么事在上一篇博客里写的很详细有须要能够看看

2. 构造函数的问题

使用构造函数带来的最大的好处就是建立对象更方便了,可是其自己也存在一个浪费内存的问题:

function Person (name, age) {
  this.name = name;
  this.age = age;
  this.sayHello = function () {
    console.log('hello ' + this.name);
  }
}
var p1 = new Person('Tom', 18);
var p2 = new Person('Jack', 16);

复制代码

以上代码的图示:

经过上面的图示,咱们发现,每个对象都引用了一个函数,咱们建立了多少个对象,对应的就会在内存中建立出对应数量的同样的函数.这样形成了内存的极大浪费

3 解决构造函数浪费内存的方法:

3.1 把对象的行为定义在构造函数外面

function Person (name, age) {
  this.name = name;
  this.age = age;
  this.sayHello = say;
}
  
function say (){
   console.log('hello ' + this.name);
}
    
var p1 = new Person('Tom', 18);
var p2 = new Person('Jack', 16);
p1.sayHello(); // hello Tom
p2.sayHello(); // hello Jack
复制代码

**注意: ** 这种方式,能够解决构造函数浪费内存的问题,可是,同时又出现了一个新的问题,咱们把函数定义在了全局,

全局的函数,很容易被别人写的代码覆盖.

###3.2 利用函数的原型对象(更优雅的解决方案)

js给每个函数,提供了一个对应的原型对象.能够经过函数的prototype属性访问到这个原型对象.

原型对象有一个constructor的属性会指向本身对应的函数

而咱们经过 new 函数 建立出来的实例对象,默承认以访问到函数对应的原型对象上的属性

function Person (name, age) {
  this.name = name;
  this.age = age;
}
  
Person.prototype.sayName = function (){
   console.log('hello ' + this.name);
}
    
var p1 = new Person('Tom', 18);
var p2 = new Person('Jack', 16);
p1.sayHello(); // hello Tom
p2.sayHello(); // hello Jack
复制代码

**注意: **为了咱们方便查看,实例和原型的关系.浏览器很贴心的帮咱们实现了一个属性 __proto__, 经过这个属性,咱们能够在控制台上清楚的看到原型.可是 __proto__不是w3c标准的属性,因此不要在生产环境(上线)下使用.

function Person (name, age) {
  this.name = name;
  this.age = age;
}

var p1 = new Person();

console.log(Perosn.prototype === p1.__proto__) //true
复制代码

3.3 小结:

  • 利用原型对象,能够更加优雅的解决构造函数浪费内存的问题

  • 通常对象私有的属性直接写在构造函数中,而对象公有的属性写在原型对象上

  • 函数对应有一个本身的原型对象 , 经过prototype属性能够访问

  • 原型对象有一个constructor属性,能够指回本身的对应的函数

  • 经过函数new出来的实例,默承认以访问到原型对象的属性 ,咱们能够经过__proto__在控制台看到

3. 原型链

原型对象也是对象,那么这个对象的是被谁建立出来的呢?

function Person (name, age) {
  this.name = name;
  this.age = age;
}

var p1 = new Person();

console.log(Person.prototype) // 指向Person的原型对象
console.log(Person.prototype.__proto__.constructor) // 咱们能够看到Person的原型对象是Object的实例
复制代码

4. js中对象属性的查找规则

由于这个查找规则,因此Object函数原型对象上的全部属性均可以被其余对象访问到

  • 访问对象的属性时,先在对象本身身上找,找到就直接返回
  • 若是对象的身上没有这个属性,就会往原型上面找,若是找 到就直接返回
  • 若是原型上也没有,就往原型的原型上面找(沿着原型链一直往上找),找到就当即返回
  • 若是最终都没有找到,则返回undefined
function Student(){}
var s1 = new Student();
s1.toString()  //[object Object] 
复制代码

5. 内置对象的原型

  • Array.prototype

  • String.prototype

    ...

    经过观察内置函数的原型,咱们发现咱们在数组/字符串经常使用的API,其实都定义在他们对应的原型上的.因此全部的数组对象/字符串,都能使用这些API.

##6. 更简单的原型使用方式

若是咱们有不少公用的属性,那么一个一个的添加在函数的原型上就比较麻烦,咱们还能够有一种更简单的方式

直接新建一个对象赋值给函数的prototype属性

function Person (name, age) {
  this.name = name;
  this.age = age;
}

Person.prototype = {
  constructor: Person, // => 手动定义一个 constructor 属性, 指向正确的构造函数
  sayHello: function () {
    console.log('我叫' + this.name + ',我今年' + this.age + '岁了');
  }
  eat : function(){
    console.log('吃饭ing...');
  }
}

var p1 = new Person('zs', 18);
复制代码

相关文章
相关标签/搜索