相信看到題目都知道,這些都是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里為了實現數據和方法私有化的方式。內層函數可以調用外層函數的變量和方法。
經典的面試題
如果有這樣的需求
- 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);