原型、原型链、作用域、作用域链、闭包
相信看到题目都知道,这些都是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);