原型、原型鏈、做用域、做用域鏈、閉包

相信看到題目都知道,這些都是js千年不變的面試題。javascript

原型、原型鏈?

什麼是原型、原型鏈?

原型:至關於一個模具,用來生產實例對象。java

原型鏈:原型對象有個指針指向構造函數,實例對象又有一個指針指向原型對象,就造成了一條原型鏈,最終指向null。面試

爲何存在?

原型:就是js裏實現面向對象的方式,也就是說,js就是基於原型的面向對象。閉包

原型鏈:是js實現繼承的方式。函數

做用域、做用域鏈?

什麼是做用域、做用域鏈?

  • 做用域this

    所謂做用域,就是變量或者是函數能做用的範圍。prototype

那麼JavaScript裏有什麼做用域呢?指針

一、全局做用域code

除了函數中定義的變量以外,都是全局做用域。對象

舉個栗子:

var a = 10;
function bar(){
    console.log(a);
}
bar();//10

以上的a就是全局變量,處處能夠訪問a。
然鵝,

var a = 10;
function bar(){
    console.log(a);
    var a = 20;
}
bar();//undefined

什麼鬼?undefined?

是的,你沒看錯。由於先搜索函數的變量看是否存在a,存在,又因爲a被預解析(變量提高),提高的a綁定了這裏的a做用域,因此結果就是undefined。

二、局部做用域

函數裏用var聲明的變量。

舉個栗子:

var a = 10;
function bar(){
   var a  = 20;
    console.log(a);
}
bar();//20

三、沒有塊級做用域(至ES5),ES6中有塊級做用域

ES6以前,除了函數以外的塊都不具有塊級做用域。

常見的經典例子:

for(var i=0;i<4;i++){
    setTimeout(function(){
        console.log(i);
    },200);
}
//4 4 4 4

解決辦法:

for(var i=0;i<4;i++){
    (function(j){
            setTimeout(function(){
        console.log(j);
    },200);
    })(i)
}
//0 1 2 3
  • 做用域鏈

    變量隨着做用長輩函數一級一級往上搜索,直到找到爲止,找不到就報錯,這個過程就是做用域鏈起的做用。

var num = 30;
function f1(){
    var num  = 20;
    function f2(){
        var num = 10;
        function f3(){
            var num = 5;
            console.log(num);
        }
        f3();
    }
   f2();
}
f1();

js

閉包

閉包:js裏爲了實現數據和方法私有化的方式。內層函數能夠調用外層函數的變量和方法。

經典的面試題

若是有這樣的需求

  • go('l') -> gol
  • go()('l') -> gool
  • go()()('l') -> goool
var go = function (a) {
var str = 'go';
    var add0 = function (a) {
        str += 'o';
        return a ? str += a : add0;// 巧妙使用
    }
    return a ? str += a : add0;// 巧妙使用
}
console.log(go('l'));//gol
console.log(go()('l'));//gool
console.log(go()()('l'));//goool

繼承

既然前面說到繼承的問題。繼承指的是一個對象能夠共享父級對象的一些屬性。那麼爲何須要繼承?好比上文的問題中,形狀Shape有頂點這個屬性,三角形和矩形均可以繼承該屬性而不須要再從新定義。那麼就ES6之前跟ES6之後JavaScript中實現繼承的問題來聊聊吧。

  • ES6以前

組合繼承

function Parent(name, age) {
    this.name = name;
    this.age = age;
}  
Parent.prototype.getName = function() {
    return this.name;
}
function child(name, age, sex) {
    Parent.call(this, name, age);
    this.sex = sex;
}

child.prototype = new Parent()
var c1 = new child('zenquan', '23', 'M')
console.log(c1.getName())
console.log(c1)

這種繼承方式優勢在於構造函數能夠傳參,不會與父類引用屬性共享,能夠複用父類的函數,可是也存在一個缺點就是在繼承父類函數的時候調用了父類構造函數,致使子類的原型上多了不須要的父類屬性,存在內存上的浪費。

寄生組合繼承

function parent(name, age) {
    this.name = name;
    this.age = age;
}  
parent.prototype.getName = function() {
    return this.name;
}

function child(name, age, sex) {
    parent.call(this, name, age);
    this.sex = sex;
}

child.prototype = Object.create(parent.prototype, {
    constructor: {
        value: child,
        enumerable: true,
        writable: true,
        configurable: true
    }
})

var c1 = new child('zenquan', 23, 'M');

console.log(c1.getName())
console.log(c1)

以上繼承實現的核心就是將父類的原型賦值給了子類,而且將構造函數設置爲子類,這樣既解決了無用的父類屬性問題,還能正確的找到子類的構造函數。

  • ES6以後class繼承

    以上兩種繼承方式都是經過原型去解決的,在 ES6 中,咱們可使用 class 去實現繼承,而且實現起來很簡單

    class parent {
        constructor(name, age) {
            this.name = name;
            this.age = age;
        }
        getName() {
            return this.name;
        }
    }  
    class child extends parent {
        constructor(name, age, sex) {
            super(name, age);
            this.sex = sex;
        }
    }
    var c1 = new child('zenquan', 23, 'M');
    console.log(c1);
相關文章
相關標籤/搜索