10分钟入门~面试官问我这个问题,我自信的站了起来!一笑生花
Posted 贪吃ღ大魔王
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了10分钟入门~面试官问我这个问题,我自信的站了起来!一笑生花相关的知识,希望对你有一定的参考价值。
如果你是一个入门初学者,对前端的重要知识掌握的不够扎实或间接遗忘,亦或你是现在正在寻找工作,背烂了面试题,却被面试官处处难为到,这篇博文一定对你有很大的帮助,这篇博文主要详细结合案例讲述前端的难点:闭包和继承。 是许多前端大佬拿来反复咀嚼的知识,也是面试官们必问的题了吧。下面结合案例来讲述,希望这篇博文对你能起到较大的帮助。
认识函数
函数在内存中执行原理
函数定义过程:
function fn(){
var num = 10;
num++;
console.log(num)
}
函数调用过程:
函数调用的时候,会在内存中的另外一个空间(执行空间)中进行,当函数执行结束后,执行空间立马销毁:
fn()
执行空间不销毁的函数
如果一个函数中返回了一个复杂类型数据,这个函数中的执行空间就不会被销毁:
function fn(){
return {
}
}
var obj = fn()
执行的时候的过程:
因为栈中变量obj跟执行中间中返回的数据空间保持了引用关系,所以这个fn的执行空间不能销毁,因为一旦销毁后,就没有了obj引用的数据空间了。
闭包
作用域嵌套形成的一种js的高级应用场景。
闭包xmind图
形成条件
大函数中直接或间接的返回一个小函数,小函数访问大函数中的变量。此时大函数的执行空间不会被销毁了。小函数叫做大函数的闭包函数。
function fn(){
var num = 10;
function fun(){
num++;
return num
}
return fun;
}
var f = fn();
/当一个大函数中,返回一个小函数,小函数中使用了大函数中的变量 - 里面小函数叫做大函数的闭包
function fn(){
var num = 10;
return function(){
num++;
console.log(num);
}
}
var fun = fn()
console.log(fun);
fun()
fun()
fun()
fun()
fun()
fun()
钱包系统 - 存钱、消费、查看余额
var money = 100;
money += 10;
console.log(money);
money -= 20;
定义在全局中的变量,容易被覆盖 - 会污染全局
function fn(){
var money = 100;
return money
}
var m = fn()
console.log(m);
没有办法操作局部的money,无法存钱和消费了
function fn(){
var money = 100;
function fun(num){
money += num
return money
}
return fun
}
var f = fn()
var m = f(30)
console.log(m);
var n = f(40)
console.log(n);
闭包的应用场景
闭包的应用场景 - 通常会使用闭包解决 在循环中执行异步代码的问题
for(var i=1;i<=3;i++){
setTimeout(function(){
console.log(i);
},1000*i)
}
for(var i=1;i<=3;i++){
function fn(i){
// var i = 1/2/3
setTimeout(function(){
console.log(i);
},1000*i)
}
fn(i)
}
for(var i=1;i<=3;i++){
var fn = (function(i){
return function(){
setTimeout(function(){
console.log(i);
},i*1000)
}
})(i)
fn()
}
tab切换
var oLis = document.querySelectorAll('li')
var oDivs = document.querySelectorAll('div')
for(var i=0;i<oLis.length;i++){
(function(i){
oLis[i].onclick = function(){
// 将所有的li的active去掉
for(var j=0;j<oLis.length;j++){
oLis[j].className = '';
oDivs[j].className = '';
}
this.className = 'active';
// 让i能用
console.log(i);
}
})(i)
}
星星评分:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>document</title>
</head>
<body>
<img src="./images/rank_3.gif" alt=""><img src="./images/rank_4.gif" alt="">
<img src="./images/rank_3.gif" alt=""><img src="./images/rank_4.gif" alt="">
<img src="./images/rank_3.gif" alt=""><img src="./images/rank_4.gif" alt="">
<img src="./images/rank_3.gif" alt=""><img src="./images/rank_4.gif" alt="">
<img src="./images/rank_3.gif" alt=""><img src="./images/rank_4.gif" alt="">
</body>
<script>
var oImgs = document.querySelectorAll('img')
for(var i=0;i<oImgs.length;i++){
oImgs[i].onmouseover = fn(i)
}
function fn(i){
return function(){
for(var j=0;j<=i;j++){
if(j%2){
oImgs[j].src = './images/rank_2.gif';
}else{
oImgs[j].src = './images/rank_1.gif';
}
}
for(var j=i+1;j<oImgs.length;j++){
if(j%2){
oImgs[j].src = './images/rank_4.gif';
}else{
oImgs[j].src = './images/rank_3.gif';
}
}
}
}
</script>
</html>
闭包的作用:
- 保护变量私有化 - 定义在函数内部的变量就是私有变量
- 使用函数外部访问到函数内部的私有变量
- 延长了变量的生命周期
缺点:有一个不会被销毁的内存空间,容易造成内存泄漏。
闭包的应用案例:循环中绑定事件 - 星星评分
闭包的语法糖
语法糖:就是利用这个语法的一种场景,且使用起来方便, 但是看起来不舒服。
闭包有一个语法糖是获取器和设置器,分别是get和set。
正常的写法和用法:
function fn(){
var num = 100;
return {
getNum(){
return num;
},
setNum(val){
num = val
}
}
}
var res = fn();
console.log(res.getNum()) // 100
res.setNum(500)
console.log(res.getNum()) // 500
语法糖写法:
function fn(){
var num = 100;
return {
get num(){
return num;
},
set num(val){
num = val
}
}
}
var res = fn();
console.log(res.num) // 100
res.num = 500
console.log(res.num) // 500
get方法想要做的事情就是访问num的值,set方法想要做的事情就是设置num的值
换语法糖写之后,num作为了res对象的属性,直接可以访问,当访问num值的时候,其实就是在调用get方法,当给num直接赋值的时候,其实就是在调用set方法。
函数柯里化
函数柯里化其实就是利用闭包将函数的多个参数拆分成多个函数使用,方便模块化开发,例:
function fn(reg,username){
return reg.test(username)
}
这是一个验证用户名是否正确的函数,调用方式如下:
var reg = /^[a-zA-Z]\\dw{3,11}$/;
var res = fn(reg,'cuihua')
var res1 = fn(reg,'ruhua')
从上面重复使用的代码中可以看出来,每次使用的reg都是一样的,且在模块化开发中,不建议将变量暴露出来,暴露的都是一个一个的方法,也就是函数,所以讲上面的函数改写:
function fn(reg){
return function(username){
return reg.test(username)
}
}
使用方式如下:
var test = fn(/^[a-zA-Z]\\dw{3,11}$/);
var res = test('cuihua')
var res1 = test('ruhua')
这种使用方式,没有将变量暴露在外,而是将函数内部的函数暴露在外面,更加符合模块化的要求。
这就是函数的柯里化。
案例:正则验证的模块化封装
闭包面试题
function fn(i){
return function(n){
console.log(n + (--i))
}
}
var f = fn(2)
f(3)
fn(4)(5)
fn(6)(7)
f(8) ---结果是 8
继承
继承xmind图
概念
如下三个构造函数:
function Animal(name,age){
this.name = name;
this.age = age
}
Animal.prototype.say = function(){
console.log('各种声音')
}
function Cat(eye) {
this.eye = eye
}
Cat.prototype.xingwei = function () {
console.log('抓老鼠')
}
function Bird(body) {
this.body = body
}
Bird.prototype.fly = function () {
console.log('飞')
}
const c = new Cat('蓝黄')
console.log(c)
const b = new Bird('翅膀')
console.log(b)
从下面两个实例对象中可以看出,每个对象都有自己的属性和方法,不带有Animal的属性和方法,但Cat和Bird又希望能使用到Animal的属性和方法。
当多个构造函数需要使用一些共同的属性和方法时候,就需要将将共同的属性和方法单独封装在一个构造函数中,方便多个构造函数继承使用。
继承:就是让子构造函数的实例对象能使用父构造函数的属性和方法。
父构造函数叫父类,子构造函数叫子类。继承的目的,就是让子类拥有父类的属性和方法。
继承方案
原型继承
通过概念对象的原型链结构来实现继承,因为对象默认能访问到原型对象的属性和方法
function Animal(name,age){
this.name = name;
this.age = age
}
Animal.prototype.say = function(){
console.log('各种声音')
}
function Cat(eye) {
this.eye = eye
}
// 改变Cat的原型
Cat.prototype = new Animal('猫',5)
Cat.prototype.xingwei = function () {
console.log('抓老鼠')
}
var c = new Cat('眼睛')
console.log(c)
优点:只要处在原型上的方法和属性都能使用
缺点:一个构造函数的实例创建需要在两个地方传递参数,且同样是属性,不在同一个地方显示
借用函数继承
利用借用函数,将父构造函数中的this改成子构造函数中的this,让子构造函数具备跟父构造函数同样的属性。
function Animal(name,age){
this.name = name;
this.age = age
}
Animal.prototype.say = function(){
console.log('各种声音')
}
function Cat(name,age,eye) {
// 使用借用函数调用父构造函数
Animal.call(name,age)
this.eye = eye
}
Cat.prototype.xingwei = function () {
console.log('抓老鼠')
}
var c = new Cat('猫',5,'眼睛')
console.log(c)
优点:在同一个地方传递参数了,且属性都放在了一起。
缺点:父构造函数原型上的方法继承不了。
组合继承
组合继承是将原型继承和借用函数继承同时使用。
function Animal(name,age){
this.name = name;
this.age = age
}
Animal.prototype.say = function(){
console.log('各种声音')
}
function Cat(name,age,eye) {
// 使用借用函数调用父构造函数
Animal.call(name,age)
this.eye = eye
}
// 改变Cat的原型
Cat.prototype = new Animal()
Cat.prototype.xingwei = function () {
console.log('抓老鼠')
}
var c = new Cat('猫',5,'眼睛')
console.log(c)
优点:
- 父类构造函数体内和原型上的内容都能继承
- 继承下来的属性放在自己身上
- 在一个位置传递所有参数
缺点:当给子类添加方法的时候, 实际上是添加在了父类的实例身上
拷贝继承
拷贝继承是利用for in循环能将原型中的属性和方法也遍历出来的特性,将父对象中属性和方法遍历绑定到子对象的原型上。
function Animal(name,age){
this.name = name;
this.age = age
}
京东面试官:Redis 这些我必问