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

Zenquan發表於2019-02-23

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

原型、原型鏈?

什麼是原型、原型鏈?

原型:相當於一個模具,用來生產例項物件。

原型鏈:原型物件有個指標指向建構函式,例項物件又有一個指標指向原型物件,就形成了一條原型鏈,最終指向null。

為什麼存在?

原型:就是js裡實現物件導向的方式,也就是說,js就是基於原型的物件導向。

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

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

作用域、作用域鏈?

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

  • 作用域

所謂作用域,就是變數或者是函式能作用的範圍。

那麼JavaScript裡有什麼作用域呢?

1、全域性作用域

除了函式中定義的變數之外,都是全域性作用域。

舉個栗子:

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。

2、區域性作用域

函式裡用var宣告的變數。

舉個栗子:

var a = 10;
function bar(){
   var a  = 20;
    console.log(a);
}
bar();//20
複製程式碼

3、沒有塊級作用域(至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);
    複製程式碼

相關文章