Code Monkey home page Code Monkey logo

blog's Introduction

  • 👋 Hi, I’m @ifency
  • 👀 I’m interested in ...
  • 🌱 I’m currently learning ...
  • 💞️ I’m looking to collaborate on ...
  • 📫 How to reach me ...

blog's People

Contributors

ifency avatar

Watchers

 avatar

blog's Issues

JavaScript深入系列之原型与原型链

前言

每次遇到新的概念,我们可能会想到三个问题,它是什么呢(what)?为什么会出现呢(why)?又能解决什么呢(how)?这次我们来看看原型与原型链这个概念,要解决这个3W(what, why, how),首先得从js的面向对象说起...

此文需要准备好电脑,大脑,一杯咖啡

面向对象

什么是面向对象?应该不需要我多说吧,任何编程语言都有面向对象,然后它们都有类(class) 的概念,但在ECMAScript(以下用es表示)中却没有,除了es6之外(其实上是语法糖,这样是为了更像面向对象编程的语法而已),但不代表因为es6有了类,我们就可以把原型与原型链的概念一笔带过,因为es6一般是在es5的基础上优化来的,而且现在面试对js的基础扎实要求比较高,上次被问到过有几种继承方法,回答没完全正确...🙈

那么问题来了,既然没有类的概念,那es又是如何实现的呢?起初是用工厂模式实现,这个又是什么玩意呢?继续看下面👇

工厂模式

正如字面的意思,就拿制造娃娃(Doll)的🌰来说吧,工厂要制造一个娃娃的过程:首先制造娃娃的对象(new Object()),然后赋给娃娃的属性,比如颜色,多高,多重,最后就制造出来,代码如下:

function createDoll(color,height,weight){ //制造工厂模式的函数
  let o = new Object();//创建一个娃娃的对象
  o.color = color;//颜色
  o.height = height;//多高
  o.weight = weight;//多重
  o.sayColor = function(){//报告颜色
    alert(o.color);
  }
  return o;
}
//为了显示是工厂模式,变量名后面加F(工厂:Factory)
let dollF1 = createDoll('orange',20,10);//要制造一个娃娃,就调用上面的函数
let dollF2 = createDoll('red',30,10);//同上

把一个对象的所有属性和方法(所谓本身的信息)封装在一个工厂模式的函数,这样就能制造多个对象

  • 好处:

    减少大量重复的代码

  • 缺点:

    无法识别对象的类型问题,换句话说,就是无法判断一个对象来自于哪个函数呢?这个稍后再说,正是因为这个缺点,所以才会出现构造函数模式~

构造函数模式

什么是构造函数呢?写法其实和普通函数差不多的,但只要被new调用就称为构造函数,代码如下:

function Doll(color,height,weight){//本来是普通函数
  this.color = color;
  this.height = height;
  this.weight = weight;
  this.sayColor = function(){
    alert(this.color);
  }
}
//为了表示是构造函数模式,变量名后面加C
let dollC1 = new Doll('orange',20,10);//用new调用了,上面的函数就称为构造函数
let dollC2 = new Doll('red',30,10);

为了更好的识别构造函数,函数名应该以一个大写字母开头,比如上面的大写字母D,然后与工厂模式相比,去掉创建对象( let o = new Object();),直接把属性和方法赋给this对象,去掉return返回语句。

这个有啥优点,之前提到过工厂模式无法识别对象的类型问题,我们先看看调用构造函数和调用工厂模式的打印结果

我们不光要看文章,还要动手打code,这样比较容易理解~

调用工厂模式的结果如下:

调用工厂函数的结果

调用构造函数的结果如下:

调用构造函数的结果

我们可以发现,调用构造函数比工厂模式多了一个constructor属性,这个属性指向Doll,为了证明这两个是相等,我们可以打印出看看:

dollC1.constructor === Doll; //true
//然后我们再来看看工厂模式的又是如何呢
dollF1.constructor === createDoll; //false,因为constructor属性找不到creteDoll,所以无法判断来自于createDoll函数,这就是工厂模式的缺点

为什么非要判断对象类型,别急,这个要说的原因很长,要一个一个解释,
就好比说吃美食,要慢慢咬嚼,才能知道美食是什么味道,相反吃的太快,就说不出味道的,也就没办法向别人(面试官或者写代码)解释这个本质~

这个constructor属性可以用来标识对象类型,但小红书推荐用instanceof来判断,这个是表示可以是Object的实例,也可以是构造函数的实例,看看上面调用工厂模式和构造函数的图片,工厂模式中的dollF1__proto__下面有一个constructor,指向Object;而构造函数中的dollC1__proto__下面有两个constructor,分别指向Object和Doll,然后我们运用instanceof来判断:

//构造函数模式
dollC1 instanceof Doll; //true
dollC1 instanceof Object; //true
//工厂模式
dollF1 instanceof createDoll; //false
dollF1 instanceof Object; //false

所以这就是工厂模式和构造函数模式的优劣比,至于为什么会有指向Object,凡是创建的实例对象都有Object构造函数,至于详细,可能比较长,有个知乎大佬写的比较有意思,推荐看看JavaScript世界万物诞生记

然而不要太天真了,任何事物绝对没有完美的,构造函数也是如此的,不然我就不会讲原型与原型链,那么构造函数有什么缺点呢?我们先看构造函数中的sayColor方法,这个方法在多个实例对象上应该是一样,然而看下面👇的代码:

dollC1.sayColor === dollC2.sayColor; //false

结果打印出false,哈?为什么呢?调用构造函数后,会生成不同作用域的多个实例,相当于开辟多个实例的内存,拥有自己的属性可以理解的,但拥有自己的方法这就没必要,就好比说两个人要去旅行,需要准备东西,由于卫生问题,带上自己的毛巾(属性)可以理解的,然后你和朋友都要带上沐浴液,带的越多,提的就越重,换个角度想,如果不带自己的沐浴液,就借朋友的沐浴液来用,是不是可以减少自己的容量呢?在构造函数也是一样的道理,带自己的属性和方法越多,开辟的内存就越大,所以出现原型模式这个概念(终于到这一步了🤪)

原型模式(也可以叫原型)

什么是原型模式呢?原型模式也可以这么叫共享模式(个人理解哈),这几年不是很流行共享嘛?共享单车,共享充电宝,共享汽车等等,一个单车可以被所有人共用,相当于一个原型模式可以被所有的实例共用,区别在于单车要花钱哈~

之前讲到的构造函数的属性和方法,这次在原型模式中,把属性和方法移到构造函数的原型对象,用prototype实现:

function Doll(){};
Doll.prototype.color='orange';
Doll.prototype.height=12;
Doll.prototype.weight=6;
Doll.prototype.sayColor=function(){
    console.log(this.color);
}
//为了表示是原型模式,变量名后面加P
let dollP1 = new Doll();
dollP1.sayColor();  //orange;
let dollP2 = new Doll();

这就是原型模式,之前说的构造函数有个缺点的,多个实例对象的方法不一致,这次我们来看看多个实例对象的方法在原型模式又是如何呢?

dollP1.sayColor === dollP2.sayColor; //true

一样的耶,就能省下好多内存的,关系图如下:

原型模式

然后我们再看看dollP1的打印结果,对比上面的关系图:

dollP1

dollP1.__proto__指向Doll的原型对象(Doll.prototype),如果要读取color的值,就会沿着这个方向去原型对象里面找,找到color,就返回color的值,找不到的话,则继续往上Object的原型对象,还是找不到的话,则会返回"undefined",下面我们试试dollP1的color和nama属性:

dollP1.color; //orange
dollP1.name; //undefined

然而如果我要给dollP1这个实例增加自己的属性,比如color,那直接在dollP1上增加这个属性就好了,代码如下:

dollP1.color='red';
dollP1.color; //red

关系图如下:

实例对象增加一个属性
实例上多了一个属性,这样读取color的值,首先会在自己的实例中搜索,找到了,就返回‘red’,就不会再往上原型对象里搜索。

注意:在实例对象上即使没赋值,比如undefined,也仍然会返回undefined,也就是说实例对象只要有该属性,不管有没有赋值还是null,也会直接返回该属性的值

看起来很完美的,先别这么想的,我们再来试试引用类型,比如数组,在原型对象上加一个数组arr的属性:

Doll.prototype.arr=[1,2,3];
//然后在实例对象dollP1修改原型对象的数组,会发生什么呢?
dollP1.arr.push(4); //4,返回数组的长度
Doll.prototype.arr; //[1, 2, 3, 4],唔,原型对象的arr也发生变化
dollP2.arr; //[1, 2, 3, 4]
一定要动手打code,不然就没办法体会到呢

就因为实例对象修改原型对象的数组属性,结果实例对象,原型对象都被改变了,这样原型对象就失去自己的原则,想看看如果多个实例对象调用一个原型对象,如果修改原型对象的数组属性,多个实例对象就都拥有同样的值,那创建多个实例对象有什么意义呢?所以这也就是原型模式的缺点,所以出现组合模式~

组合模式

组合模式又是什么呢?就是利用构造函数模式和原型模式的各自特点组合成一个模式,构造函数模式特点是能创建独立的实例,而原型模式的特点是能被共享,所以就把属性放进构造函数模式,而方法则放进原型模式,代码如下:

function Doll(color,height,weight){
    this.color = color;
    this.height = height;
    this.weight = weight;
    this.arr=[1,2,3];
};
Doll.prototype.sayColor=function(){
    console.log(this.color);
}
let doll1 = new Doll('orange',14,6);
dollP1.sayColor();  //orange;
let doll2 = new Doll('red',12,4);

doll1.arr.push(4); //4
doll1.arr; //[1,2,3,4]
doll2.arr; //[1,2,3];
doll1.sayColor === coll2.sayColor; //true

这样每个实例就拥有自己属性的副本,还能共享原型对象的方法,就能省下很多内存的。

这下我们解决了原型的3W问题,然后面向对象还有一个特点的,那就是继承,在es中又是通过什么方式解决继承的问题?答案就是原型链,看下面👇

原型链

什么是原型链呢?我们可以把这个概念拆分成“原型”+“链”,原型,就是之前讲到过的原型,而链呢,就是在原型模式上产生指向的链条,之前我们讲了三个构造函数,原型对象,实例对象,对吧?假如再创建另一个构造函数,然后让这个新的构造函数的原型对象等于原来构造函数的实例化对象?怎么理解的?比如说创建一个娃娃(目前只能制造人偶娃娃,Doll默认为人偶娃娃),现在有一个新的需求,要制造动物娃娃,动物娃娃和人偶娃娃差不多,可以直接借助Doll构造函数来实例化,说白就是继承Doll的属性和方法,不需要自己再创建同样的属性,代码如下:

function Doll(color,height,weight){ //默认人偶娃娃
    this.color = color;
    this.height = height;
    this.weight = weight;
};
Doll.prototype.sayColor=function(){
    console.log(this.color);
}

//制造动物娃娃的构造函数
function AnimateDoll(name){
    this.name = name;
}
//借助Doll构造函数来实例化AnimateDoll的原型对象
AnimateDoll.prototype = new Doll();
AnimateDoll.prototype.sayName=function(){
    return this.name;
}

let animate1=new AnimateDoll('pig');
animate1.name; //pig
animate1.sayName(); //pig
animate1.color; //undefined, 因为没传值,这个稍后再说呢~
animate1.sayColor(); //同上

假如要制造新的抱枕娃娃,这个抱枕娃娃也可以是动物,也可以是人偶,那么就借用动物娃娃的构造函数实例化抱枕娃娃的原型对象,这样就能继承动物娃娃和人偶娃娃的所有属性和方法,这三个之间的关系就成为原型链,当然不只有三个~

之前说到animate.color结果打印出undefined,因为没有传值的,这就需要利用call继承Doll的属性,然后把参数赋值给对应的属性,代码如下:

function Doll(color,height,weight){
    this.color = color;
    this.height = height;
    this.weight = weight;
};
Doll.prototype.sayColor=function(){
    console.log(this.color);
}

function AnimateDoll(name,...args){
    Doll.call(this, ...args); //新增一行
    this.name=name;
}

AnimateDoll.prototype = new Doll();
AnimateDoll.prototype.sayName=function(){
    return this.name;
}


let animate1=new AnimateDoll('pig','pink',20,10);
animate1.name; //pig
animate1.sayName(); //pig
animate1.color; //pink
animate1.sayColor(); //pink
animate1.height; //20
animate1.weight; //10

继承方法不只有这个原型链,还有其他的,有兴趣,可以自己去看看小红书或者google~

之前提到过一个问题“为什么非要判断对象类型”,假如没有对象类型,我们就没办法判断一个对象是指向哪个,也就无法实现继承的方法。

到此为止吼~觉得不错,求个赞~觉得有不足的,求个建议~

笔芯

vue项目技术小记

最近做的项目快要结尾了,本项目用前后端分离的,然后前端是用vue开发的,为什么选vue呢?一来公司要求效率要高些的,那就应该用到三大流行的框架之一,然后项目的内容不太复杂的,觉得用vue更合适的。二来正好应该要“炒熟”vue了哈。其中陷入了不少坑,一个一个爬过来的,总结下入坑的原因,另外会给出官方文档的对应内容,以便加深理解的。如有不足之处,请提出来哈~

  • 异步加载组件

    • 我们都知道vue一般都是单页面的,也就意味着一开始就要加载全部的组件,这太不友好的,加载时间会慢些。这时候我们就用到异步组件~
     const Index=()=>import('@/page/Index');
     const Home=()=>import('@/page/Home');
     const router=new Router({
     	routes:[
     	{
     		path:'/index',
     		component:Index
     	},
     	{
     		path:'/home',
     		component:Home
     	}
     	]
     })
    

    然后你会发现js被分开的,这就是所谓的异步加载~

  • 页面后退时,保持之前的状态,不刷新

    • 在app.vue文件中
     <keep-alive>
     <router-view v-if="$route.meta.keepAlive"></router-view>
     </keep-alive>
     <router-view v-if="!$route.meta.keepAlive"/>
    
    • 在router.js中
     const router = new Router({
       routes: [
         {
           path: '/index',
           components:Index,
           meta:{
             keepAlive:false
           }
         },
         {
             path:'/goods',
             component:Goods,
             meta:{
               keepAlive:true
             }
         },
     }
    

    用meta中的keepAlive来判断需不需要缓存,keep-alive就是保存缓存的组件。

  • 在相同路由的情况下,再次点击导航栏上的本路由,再次刷新

    在vue中,路由地址相同的情况下,是不会再次刷新的,即使点击本路由的导航文字上。一开始我没想到解决的方案,后来寻求大佬商量,大佬说可以用事件传递的。然后他告诉我一句话的,

    vue.js一定不会阻碍传统方法实现的。

    这话说得我惭愧,我意识到我太依赖vue.js的框架,没拓展思维的。再次感谢大佬的点拔~

    • 对,是可以用事件传递来实现的。在主路由上绑定isFresh,判断需不需要刷新子路由,然后在子路由上绑定事件,传递到主路由的事件。主路由监听到事件的,就把isFresh变为false,以防下面还会需要到。不设置为false,下面的就不会刷新。看代码如下:
    • App.vue
     <router-view :isFresh="isFresh" @tempBtn="tempBtn"/>
     //data
     data(){
     	return {
     		isFresh:false,
     	}
     }
     //事件函数
     tempbtn(){
     	this.isFresh=false;
     }
    
    • Index.vue
     watch:{
     	isFresh(){
     		//console.log('ShopisFresh:'+this.isFresh);
     		if(this.isFresh){
     			Object.assign(this.$data,this.$options.data());
     			this.fetchData();
     		}
     	}
     },
     //然后加载完后,还要绑定事件,传递到主路由的tempbtn事件
     this.$emit('tempbtn');
    

    嗯,在子路由上监听到isFresh为true,表示要刷新的,Object.assign是利用data的初始化数据覆盖,然后重新加载数据。

  • checkbox整体点击

    UI设计师要求能点击checkbox的整体,包括checkbox后面的文字,不是只能点击checkbox的框,这可把我为难了,然后艰难的爬出坑了。

     <div class="group_item" @click="selfChecked = !selfChecked">
     	<input type="checkbox" v-bind:checked="selfChecked">
     	<label>点我呀</label>
     	data(){
     		return {
     			tempChecked:null
     		}
     	},
     	props:['checked'],
     	watch:{
     		checked(){
     			this.tmpChecked=this.checked
     		}
     	},
     	computed: {
     		selfChecked: {
     			get: function(val){
     				return this.tmpChecked;
     			},
     			set: function(newVal){
     				this.tmpChecked = newVal
     				this.$emit('input', newVal)
     			}
     		}
     	},
     </div>
    

    然后在父组件上用v-model即可。用selfChecked来判断是不是要点击,至于为什么要watch,是判断checked为真还是假,比如说在父组件上,提交后,表单要清空的,这时候要监听到checked为假的,然后传递到selfChecked的值。这样就能清空checkbox。

  • setInterval的问题

    用了setInterval之后,切换路由的时候发现还在计时,不得了,真的会影响性能唉,就吓的赶紧啃官方文档,找到了~

     beforeDestroy(){
     	clearInterval(this.interId);
     }
    

    beforeDestroy是毁路由之前的函数,就是说毁路由之前就清除setInterval

    • 参考vue的生命周期
  • 在v-for循环中进行v-model数据绑定

     <div v-for="(item,index) in list">
     	<textarea type="text" v-model="list[index]['content']"></textarea>
     </div>
    

下面说说和vue没关系的问题

  • 上线时去掉console

    我们一般都会用console来打印,以便检查的,万一console写多了,上线时总不能打印出console吧,要注释掉,但太费时间吧,这时候webpack神器上场(本人用webpack3)

    • 在webpack.prod.config.js中
     new UglifyJsPlugin({
       uglifyOptions: {
         compress: {
           warnings: false,
           drop_debugger: true,
           drop_console: true
         }
       },
       sourceMap: config.build.productionSourceMap,
       parallel: true
     }),
    

    这prod是生产环境的,然后drop_console为true,是去掉console

  • 兼容ie的问题

    部署到生产环境后,测试ie11时发现空白屏的,我慌了,不是说可以兼容到ie9吗?还是说没配置好的,然后google查,ie还没支持js新的api,比如promise,所以需要babel-polyfill来转换的。真是蛋疼的ie,还能怎么办呢?当然是加babel-polyfill插件的。

     npm install babel-polyfill --save
     //然后在webpack.base.config.js中引用这插件
     module.exports=require('babel-polyfill');
     module.export={
     	...
     	entry:{
     		app:['babel-polyfill','./src/main.js']
     	}
     }
    
  • 打包第三方库

    打包时把第三方库合并成vendor.js,但是这个vendor.js有hash,这就意味着每次打包时vendor.js的hash会变化的,然后在浏览器会重新加载。vendor.js的代码本来就很少被改变的,重新加载就影响加载时间的。从目前搜索的解决方案,比较好的方案有两种:
    1、 第一种用dll来打包的,webpoack自带的

    • 首先在build里新建一个文件,命名为webpack-dll.config.js
    const webpack=require('webpack')
    const path=require('path')
    
    module.export={
    	entry:{
    		vendors:['vue.js','axios','vue-router','vuex','vue/dist/vue.esm.js']   //加入要打包的第三方库
    	},
    	output:{
    		filename:'[name].dll.js',  //输入的文件名
    		path:'path.join(__direname,'../static')',   //输入的路径
    		library:'[name]'
    	},
    	plugin:[  //为了和第三方库的dll.js对应,build时不会把这个打包进去,就是说能减少build构建的时间
    		new webpack.DllPlugin({
    			path:path.join(__direname,'../','[name]-mainfest.json'),  //输入的mainfest文件的路径
    			name:'[name]'
    		})
    	]
    }
    
    • 然后命令生成vendor-mainfest.json
    //在package.json添加一行
    "dll":"webpack --config build/webpack.dll.config.js"
    

    然后npm run dll,就生成vendor-mainfest.json

    • 然后还要在webpack.base.conf.js配置,改变构建的配置
    //添加一行
    const mainfest=require('../vendors-mainfest.json')  //
    //在module.exports添加配置
    module.exports={
    	...
    	plugins:[
    		new webpack.DllReferencePlugin({
    			mainfest
    		})
    	],
    	...
    }
    

    然后把CommonsChunkPlugin的这块注释掉,不然要重复打包。

    • 最后在index.html加上一行
    <script src="./static/vendors.dll.js"></script>
    
    2、 第二种方案就是用bootcdn
    

JavaScript深入系列之继承的方法

信息卡片

记录时间:2020-10-07

Tag:JavaScript

更新时间:2020-10-08

前言

有半年没更新文章,上半年一直处于焦虑的状态,不怎么想动手提笔,直到现在,才意识到,一直焦虑下去也不是办法的,还不如沉淀自己,提升自己的,即使个人有些特殊情况的,但我相信金子总会闪闪发光的,还是不会闪闪发光的话,但至少我试过~

今天是中秋国庆长假最后一天,抓住尾巴写完一篇文章,继续写上一篇的续集~

此文需要准备好电脑,大脑,一杯咖啡

1.原型链继承

上一篇文章介绍到过原型链的概念,其实这是一种继承的方法之一,因为在ECMAScript是没有类的概念,然而有些时候自己具有独立的属性或方法,但有些地方跟另一个对象的属性或方法很像的,比如说我们之前新建过一个动物Animal类,然后这个类包含名字,身高,爱吃什么,有多重呢等等?代码如下:

function Animal(name,age,height){
	this.name=name;
    this.age=age;
    this.height=height;
}
Animal.prototype.eatFood=function(food){
	console.log(`${this.name}喜欢吃${food}`);
}
Animal.prototype.getWeight=function(weight){
	console.log(`${this.name}体重有${weight}kg`);
}
let buouCat = new Animal('布偶猫',3,10);
buouCat.eatFood('🐟'); //布偶猫喜欢吃🐟
buouCat.getWeight(30); //布偶猫体重有30kg

然后有一天新的需求来了,需要对鸟类进行详细的记录呢,是不是会飞的,还要有名字,多大,多高,喜欢吃什么,有多重呢?想看看这个鸟类也是动物类之一的,需要记录的东西,Animal类就有的,所以只要继承就好了,而怎么实现继承的呢?还是利用原型让一个对象继承另一个对象的属性和方法,看👇code:

//新增的代码
function Bird(isFly){
	this.isFly = isFly;
}
Bird.prototype = new Animal();
Bird.prototype.isOrFly = function(){
	console.log(`${this.name}${this.isFly ? '会飞' : '不会飞'}的鸟类`);
};
let b1 = new Bird(true);
b1.name = "燕子";
b1.isOrFly(); // 燕子是会飞的鸟类
b1.eatFood('虫子'); //燕子喜欢吃虫子

利用Bird的原型来实例化Animal构造函数的,然后Bird的原型指针向Animal,这就扩展了原型搜索的,首先会在Bird搜索该属性,找不到的就沿着Bird的原型往上搜索的,找到的就会返回,找不到的继续往上搜索,直到原型链的末端才停下来呢,这就是原型链的继承方法。当然如果Bird类重写喜欢吃什么样的虫子,就会覆盖Animal类的eatFood方法,看👇code:

Bird.prototype.eatFood(food1,food2){
	console.log(`${this.name}喜欢吃${food1},但它一般吃${food2}`);
}
b1.eatFood('大米','虫子'); //燕子喜欢吃大米,但它一般吃虫子。

原型链看起来很强大的,但它还是有弱点的,上一篇文章中的“原型”介绍到过的,就是在引用类型方面上还是会影响到所有的实例对象,原型链也是如此的,看👇code:

//假如在Animal构造函数上新增一个引用类型的属性
function Animal(name){
	this.name = name
    this.info={
    	isSleep: true,
        isEat: true,
    }
}
Animal.prototype.eatFood=function(food){
	console.log(`${this.name}喜欢吃${food}`);
}
Animal.prototype.getWeight=function(weight){
	console.log(`${this.name}体重有${weight}kg`);
}
function Bird(){
}
Bird.prototype = new Animal();
let b1 = new Bird();
let b2 = new Bird();
//然后我要修改b1的info引用类型
b1.info.isSleep=false;
//然后再看看b2的info有没有变化呢?
console.log(b2.info.isSleep); //false

哈?不应该啊,我只是想改下b1这一个的属性而已呢,结果却影响到b2的,那是因为Animal的所有属性是在Bird的prototype上呢,然后Bird实例化b1和b2对象,它们就有同一个prototype属性,要改b1的info,也就相当于改变prototype上的info,然后读取b2的info时,就是从该prototype的info属性(此时这个已经被改变了),所以才会返回false;所以这就是原型链的一个弱点,所以出现借用构造函数

2.借用构造函数

说完整点,就是一个子类型构造函数里借父类型的构造函数来调用,因此可以通过call和apply来执行的,这样Bird就拥有Animal的所有属性,这就是借用构造函数的过程,优点是实例化多个对象时,每个实例对象都是独立的,互不影响,看👇code:

function Animal(name){
	this.name = name
    this.info={
    	isSleep: true,
        isEat: true,
    }
}
function Bird(){
	Animal.call(this);
}
let b1 = new Bird();
let b2 = new bird();
b1.info.isSleep = false;
b2.info.isSleep = true;//没被影响到的

很遗憾的,仅靠借用构造函数的话,就是没办法复用父类型的原型对象,看👇Code:

function Animal(name){
	this.name = name
    this.info={
    	isSleep: true,
        isEat: true,
    }
}
Animal.prototype.sayName = function(){
	console.log(`这个动物的名字是${this.name}`);
}
function Bird(name){
	Animal.call(this,name);
}
let b1 = new Bird('麻雀'); 
b1.sayName(); //报错:VM3962:1 Uncaught TypeError: b1.sayName is not a function

找不到b1.sayName(), 因为call或者apply只能执行构造函数的,并不包括构造函数的原型对象,所以找不到的,才会报错的,所以这就是缺点,这就出现组合继承的概念,不得不感叹es原来有这么多故事呢~

3.组合继承

虽然原型链和借用构造函数都有缺点的,但我们不能因为白板上的一个小黑点就否定所有的,它们还是有自己的优点,原型链优点就是能继承原型属性和方法,而借用构造函数优点就是实例对象能独立继承构造函数的属性,所以把这两个的优点组合在一起,就称为组合继承的,怎么实现,还是用上面的代码来优化下,看👇code:

function Animal(name){
	this.name = name
    this.info={
    	isSleep: true,
        isEat: true,
    }
}
Animal.prototype.sayName = function(){
	console.log(`这个动物的名字是${this.name}`);
}
function Bird(name){
	//借用构造函数继承属性
	Animal.call(this,name);
}
//新增代码
Bird.prototype = new Animal();//原型链继承
let b1 = new Bird('麻雀'); 
b1.sayName(); //这个动物的名字是麻雀

let b2 = new Bird('猫头鹰');
b2.info.isSleep = false;
b1.info.isSleep //true;

改变info里的isSleep,不影响另一个实例对象的info(借用构造函数的功劳),然后又能打印sayName方法(原型链的功劳)~所以这个成为JavaScript最常用的继承方法,所以说这是最完美的么?并不是呢,它也有不足的,稍后再说这个~

4.原型式继承

和上一篇文章中的原型模式类似,利用原型把一个已知对象赋值给未知对象,看👇代码:

function object(o){
	function F(){}
    F.prototype = o
    return new F();
}
let animal = {
	name:'🐶',
    info:{
    	isSleep: true,
        isEat: true
   	}
}
let cat = object(animal);
cat.name = '🐱';
cat.name; //🐱
cat.info.isCute = true;
animal.info; //{isSleep: true, isEat: true, isCute: true}

要说优点,如果就仅仅要求对象一样的,可以考虑用这个的,但引用类型还是一个缺点的,和原型链缺点一样的,所以谨慎用~

5.寄生式继承

这个是在原型式继承上改进的,创建一个封装继承过程的函数,然后返回该对象,用法比较像工厂模式,看👇code:

function object(o){
	function F(){}
    F.prototype = o
    return new F();
}
function create(obj){
	let clone = object(obj);
    clone.sayName = function(){
    	cobsole.log(this.name);
    }
    return clone;
}
let animal = {
	name:'🐶',
    info:{
    	isSleep: true,
        isEat: true
   	}
}
let a1 = create(animal);
a1.sayName(); // '🐶'

缺点就是不能实现函数的复用~但它的优点还是可以值得借鉴,何出此言呢?继续看👇就知道了~

6.寄生组合式继承

嗯,寄生式+组合式来继承的,组合式继承上面讲到过它也有不足之处,不足在哪儿呢?我们先看看code:

function Animal(name){
	this.name = name
    this.info={
    	isSleep: true,
        isEat: true,
    }
}
Animal.prototype.sayName = function(){
	console.log(`这个动物的名字是${this.name}`);
}
function Bird(name){
	Animal.call(this,name);//第二次调用Animal构造函数
}
Bird.prototype = new Animal();//第一次调用Animal构造函数
let b1 = new Bird('麻雀'); 
b1.sayName(); //这个动物的名字是麻雀

let b2 = new Bird('猫头鹰');
b2.info.isSleep = false;
b1.info.isSleep //true;

其中Bird类两次调用Animal构造函数,第一次是在Bird原型上调用Animal构造函数Bird.prototype=new Animal(),Bird.prototype就有了name和info,第二次是调用Bird构造函数时,又会调用Animal构造函数Animal.call(this,name),然后b1有了name和info,屏蔽Bird.prototype上的name和info,看下面的截图:

我们本来目的是继承父类的原型对象,并不包括构造函数的所有属性,所以我们需要的无非就是父类的原型的一个副本而已呢,所以这就需要用到寄生式继承的,可以把父类的原型对象看作一个对象,然后赋值给子类的原型

function object(o){
	function F(){}
    F.prototype = o
    return new F();
}
function clonePrototype(child,parent){
	let pro = object(parent.prototype); //新建一个对象
    pro.constructor = child; //别忘把construcotr指向子类型,这样instanceof才能正常使用
    child.prototype = pro; //赋值给子类型的原型
}
function Animal(name){
	this.name = name
    this.info={
    	isSleep: true,
        isEat: true,
    }
}
Animal.prototype.sayName = function(){
	console.log(`这个动物的名字是${this.name}`);
}
function Bird(name){
	Animal.call(this,name);
}
clonePrototype(Bird,Animal);
Bird.prototype = new Animal();//第一次调用Animal构造函数
let b1 = new Bird('麻雀'); 
b1.sayName(); //这个动物的名字是麻雀

然后我们来看看用寄生组合式继承的打印结果:

可以发现,Bird的原型上没有name和info,这就可以避免重复的,与此同时还能保持原型链的特征,所以这个是最佳的继承方法~

好了,咖啡喝完了,就到此为止~如有错误之处,请多多指出的,笔芯~

【译】js代码规范-最佳实践

原文地址:JavaScript Clean Code - Best Practices
原作者:Milos Protic


介绍

如果你关心代码的本身以及编写方式,而只是担心代码是否有效的,你可以说你实践并且关心代码的规范性。专业的开发人员总是会考虑到自己便于将来还能可读和其他的团队人员,并不是仅仅考虑到运行的结果。你写的任何代码并不是只写一次,总会有其他的开发人员来做你写的代码,如果你写的不规范,而他则会痛苦不堪。希望这个人不是你。

总体来说,规范性的代码可以定义为不言自明,易于人理解且易于更改或者扩展的编写方式。

当你接到别人写的代码,第一个印象就是“这个代码什么玩意儿”,那你问自己,你还会有多少次愿意继续做别人写的代码呢?

"WTF is that?"

"WTF did you do here?"

"WTF is this for?"
(这个就不翻译了,自己感受下)

下图就是代表以上所述内容的流行图片
image

自配的WTF表情包

引用Robert C.Martin的一句话,这话应该会让你思考编写方式。

Even bad code can function. But if the code isn’t clean, it can bring a development organization to its knees.

在本文中,重点将在于js,这些原则也能用于其他的编程语言。

1、强类型检查
=== 代替 ==

0 == false // true
0 === false // false
2 == "2" // true
2 === "2" // false

// 例子
const value = "500";
if (value === 500) {
  console.log(value);
  // 没打印出来
}

if (value === "500") {
  console.log(value);
  // 打印出来了
}

2、变量

  • 给变量命名时,应该要使变量名具有代表意图的象征,使人易于搜索并且容易理解。

Bad:

let daysSLV = 10;
let y = new Date().getFullYear();

let ok;
if (user.age > 30) {
  ok = true;
}
//本人解释:y,ok这些什么玩意儿呢,30又是什么意思呢?

Good:

const MAX_AGE = 30; //哦,是最大的年龄
let daysSinceLastVisit = 10;
let currentYear = new Date().getFullYear();//哦,是当前年份

...

const isUserOlderThanAllowed = user.age > MAX_AGE;
  • 不要在变量名中增加没必要额外的单词

Bad:

let nameValue;
let theProduct;

Good:

let name;
let product;
  • 不要强制记忆变量名的上下文

Bad:

const users = ["John", "Marco", "Peter"];
users.forEach(u => {
  doSomething();
  doSomethingElse();
  // ...
  // ...
  // ...
  // ...
  // 这u什么玩意儿呢?
  register(u);
});

Good:

const users = ["John", "Marco", "Peter"];
users.forEach(user => {
  doSomething();
  doSomethingElse();
  // ...
  // ...
  // ...
  // ...
  register(user);
});
  • 变量名不要加上下文重复的单词

Bad:

const user = {
  userName: "John",
  userSurname: "Doe",
  userAge: "28"
};

...

user.userName;

Good:

const user = {
  name: "John",
  surname: "Doe",
  age: "28"
};

...

user.name;

3、函数

  • 函数名应该是动词或者短语,代表某种行为,描述它们在做什么

Bad:

function notif(user) {
  // implementation
}

Good:

function notifyUser(emailAddress) {
  // implementation
}
  • 避免使用大量的参数,理想的情况就是用两个或者更少的参数。参数越少,测试就越容易

Bad:

function getUsers(fields, fromDate, toDate) {
  // implementation
}

Good:

function getUsers({ fields, fromDate, toDate }) {
  // implementation
}

getUsers({
  fields: ['name', 'surname', 'email'],
  fromDate: '2019-01-01',
  toDate: '2019-01-18'
}); 
  • 函数应该使用默认参数,而不是条件语句

Bad:

function createShape(type) {
  const shapeType = type || "cube";
  // ...
}

Good:

function createShape(type = "cube") {
  // ...
}

(这个的原因,可能有些人不明白的,在此放链接阮一峰es6入门-函数参数的默认值

  • 一个函数应该做一件事,避免在一个函数中执行多个操作。

Bad:

function notifyUsers(users) {
  users.forEach(user => {
    const userRecord = database.lookup(user);
    if (userRecord.isVerified()) {
      notify(user);
    }
  });
}

Good:

function notifyVerifiedUsers(users) {
  users.filter(isUserVerified).forEach(notify);
}

function isUserVerified(user) {
  const userRecord = database.lookup(user);
  return userRecord.isVerified();
}
  • 使用Object.assign设置默认对象。

Bad:

const shapeConfig = {
  type: "cube",
  width: 200,
  height: null
};

function createShape(config) {
  config.type = config.type || "cube";
  config.width = config.width || 250;
  config.height = config.width || 250;
}

createShape(shapeConfig);

Good:

const shapeConfig = {
  type: "cube",
  width: 200
  // Exclude the 'height' key
};

function createShape(config) {
  config = Object.assign(
    {
      type: "cube",
      width: 250,
      height: 250
    },
    config
  );

  ...
}

createShape(shapeConfig);
  • 不要使用标志记作为参数,因为它们告诉你该函数正在做的比它应该做的更多。

Bad:

function createFile(name, isPublic) {
  if (isPublic) {
    fs.create(`./public/${name}`);
  } else {
    fs.create(name);
  }
}

Good:

function createFile(name) {
  fs.create(name);
}

function createPublicFile(name) {
  createFile(`./public/${name}`);
}
  • 不要污染全局变量。如果需要扩展现有对象,请使用ES6类和继承,而不是在现有对象的原型链上创建函数

Bad:

Array.prototype.myFunc = function myFunc() {
  // implementation
};

Good:

class SuperArray extends Array {
  myFunc() {
    // implementation
  }
}

4、条件语句

  • 避免负面条件

Bad:

function isUserNotBlocked(user) {
  // implementation
}

if (!isUserNotBlocked(user)) {
  // implementation
}

Good:

function isUserBlocked(user) {
  // implementation
}

if (isUserBlocked(user)) {
  // implementation
}
  • 使用条件语句尽量短点。这可能是微不足道的,但值得一提。此方法仅用于布尔值,并且如果您确定该值不是未定义的或为null。

Bad:

if (isValid === true) {
  // do something...
}

if (isValid === false) {
  // do something...
}

Good:

if (isValid) {
  // do something...
}

if (!isValid) {
  // do something...
}
  • 尽可能避免switch分支,请改用多态和继承。

Bad:

class Car {
  // ...
  getMaximumSpeed() {
    switch (this.type) {
      case "Ford":
        return this.someFactor() + this.anotherFactor();
      case "Mazda":
        return this.someFactor();
      case "McLaren":
        return this.someFactor() - this.anotherFactor();
    }
  }
}

Good:

class Car {
  // ...
}

class Ford extends Car {
  // ...
  getMaximumSpeed() {
    return this.someFactor() + this.anotherFactor();
  }
}

class Mazda extends Car {
  // ...
  getMaximumSpeed() {
    return this.someFactor();
  }
}

class McLaren extends Car {
  // ...
  getMaximumSpeed() {
    return this.someFactor() - this.anotherFactor();
  }
}

5、Es6类

  • 类是JavaScript中的新语法糖,跟原型对象一样,只是它现在看起来不同,你应该更喜欢它们而不是ES5的使用构造函数。

Bad:

const Person = function(name) {
  if (!(this instanceof Person)) {
    throw new Error("Instantiate Person with `new` keyword");
  }

  this.name = name;
};

Person.prototype.sayHello = function sayHello() { /**/ };

const Student = function(name, school) {
  if (!(this instanceof Student)) {
    throw new Error("Instantiate Student with `new` keyword");
  }

  Person.call(this, name);
  this.school = school;
};

Student.prototype = Object.create(Person.prototype);
Student.prototype.constructor = Student;
Student.prototype.printSchoolName = function printSchoolName() { /**/ };

Good:

class Person {
  constructor(name) {
    this.name = name;
  }

  sayHello() {
    /* ... */
  }
}

class Student extends Person {
  constructor(name, school) {
    super(name);
    this.school = school;
  }

  printSchoolName() {
    /* ... */
  }
}
  • 使用方法链接,许多库如jQuery和Lodash都使用这种模式。因此,您的代码将不那么冗长。在你的类中,只需在每个函数的末尾返回它,你就可以将更多的类方法链接到它上面。

Bad:

class Person {
  constructor(name) {
    this.name = name;
  }

  setSurname(surname) {
    this.surname = surname;
  }

  setAge(age) {
    this.age = age;
  }

  save() {
    console.log(this.name, this.surname, this.age);
  }
}

const person = new Person("John");
person.setSurname("Doe");
person.setAge(29);
person.save();

Good:

class Person {
  constructor(name) {
    this.name = name;
  }

  setSurname(surname) {
    this.surname = surname;
    // Return this for chaining
    return this;
  }

  setAge(age) {
    this.age = age;
    // Return this for chaining
    return this;
  }

  save() {
    console.log(this.name, this.surname, this.age);
    // Return this for chaining
    return this;
  }
}

const person = new Person("John")
    .setSurname("Doe")
    .setAge(29)
    .save();

6、常规的避免

一般来说,尽量不要重复自己,就是说不应该编写重复的代码,也不要在你身后留下尾巴,比如未使用的函数和死代码。
由于各种原因,你最终可能会有重复的代码。比如,你可能有两个略有不同的东西,它们有许多共同之处,有时期限不足的迫使你创建包含几乎同样代码的单独函数。
关于死代码,这正如它的名字。它没有做任何事情,因为在某些开发阶段,你已经决定不再用它,您应该在代码库中搜索这些不需要的函数和代码块并删除。 我建议你,一旦你决定不再需要它,就马上删除它, 以防你以后可能会忘记它的用途。
这只是改进代码所能做的小部分。 在我看来,这里所说的原则是人们经常不遵循的原则。 他们尝试但总是因各种原因而没成功。 也许项目一开始时,代码是整洁干净,但在最后期限时,原则经常被忽略并转移到“TODO”或“REFACTOR”部分。 到那时,您的客户宁愿让您满足截止日期,而不是规范的代码。
到此为止!
感谢你阅读并且希望在下一篇文章看到你。

翻译系列之float是否已弃用?

原文地址:Is CSS float deprecated?,作者:Robin Rendle

前言

本人未必按一字一句进行翻译,只是根据原文的意思并且结合自己的理解能力来翻译,如有误处,欢迎提出~另外祝小姐姐小哥哥新年快乐呀,发量多多~至于为什么在新年第一天写文章呢?其实很早就想写的,但最近一直阅读源代码,但总结不出来的,好卑微啊~然后没事就看看国外文章,正好看到一篇比较短的文章,就想翻译下~

正文

前几天上班时碰到一个有趣的话题:我们现在已经有了flex和grid,还需要float么?

简单来说

不!好吧,其实大多数时候我只用于在文字围绕图像上,但在布局方面上我完全避免用float

具体来说

flex和grid出来之前,我们用float来实现布局和网格。某一个酷热的夏天下午,我打开Jeffrey Zeldman写的《web标准设计》副本,然后翻到一个带有float:right的红色div,它的浮动简直神奇了。

现在我想知道有多少设计师因为web能实现屏幕上随意的位置而喜欢呢?

但是使用float来实现复杂的布局是一种技巧,它的真正目的只是为了让文字围绕图像。

img {
  width: 150px;
  float: left;
}

当我们在巨大的布局要实现像杂志风格那样,使用float就暴露问题,但当时没有其他的方案可以实现,我们不得不继续用float。

使用float的一个问题就是,你使用了float后不得不使用clearfix来清除浮动,比如:

<div class="clearfix">
  <div class="float-left">Column</div>
  <div class="float-left">Column</div>
  <div class="float-left">Column</div>
</div>
clearfix:after {
  content: "";
  display: table;
  clear: both;
}

不久前,Jay Hoffman描述过clearfix hack(清除浮动):

对于那些不知道的人来说,clearfix是一个css浮动的,是为了解决当两个浮动的元素在一起发生重叠的问题。但一个元素以这种方式对齐时,该元素的父容器的高度就为0,这就容易破坏布局。clearfix就是这样解决的。

从那之后就开始慢慢地改变了。早在2017年,Rachel Andrew解释了浏览器如何处理清除浮动的问题,我们需要的就是下面的css例子来处理相同的问题:

.container {
  display: flow-root;
}

奇怪的是,我开始写这篇文章三分钟之前才知道flow-root这个,但这个并不是本文要说的重点,我要表达的意思是,有了flex和grid,我们根本不需要float,float的作用本来就是让文本围绕图像。但现在有了flex和grid,他们足够可以完成复杂的布局。


回到我上班的话题那个,然后有些人说我们应该回去把代码库里的那些float都用flex和grid来替换掉。但就在这时,我就说:等会儿。我不认为在我们的代码库几个地方用到float会引发问题。

因此除了让文字围绕图像,我们还能用float做其他的事么?并不。但是因为float并不是纯粹的,也不是“正确”的处理方式,我们就应该立即清除这个么?并不。

web的最佳在于旧的代码不会破坏东西。没用到最新的属性或者最酷的技巧也并不是没用或者是一件坏事。我们只是用更好的方案去代替float。我认为这个是一个很好的教训,这些css属性可能永远存在,因为它们在现代设计中仍然可用的。

到此为止

使用forEach、some、every、find、findIndex的正确姿势

一旦遇到需要遍历循环的时候,我们就会用到for语句或者forEach。我更喜欢用forEach,因为这代码写起来简洁多了,然而某一天遇到一个奇怪的问题,比如说判断某一个对象是否在一个数组里,如果在则直接结束循环。我用类似for语句的思维去写用forEach的代码,代码是这样写:

function foreachArr(arr,id){
    arr.forEach(item=>{
        console.log('id:'+item.id);
        if(item.id==id){
            return;
        }
    })
}

let person=[{id:1,name:'Nancy',age:24},{id:2,name:'Carol',age:26},{id:3,name:'Lucy',age:20}];
foreachArr(person,2);
//打印出来的结果:
[Log] id:1
[Log] id:2
[Log] id:3

咦?不是应该到 id:2 就结束了,怎么还有 id:3? 我试了写for语句,看看是怎样的结果:

function forinArr(arr,id){
    for(let i in arr){
        console.log('id:'+arr[i].id);
        if(arr[i].id==id){
            return;
        }
    }
}
forinArr(person,2);
//打印出来的结果:
[Log] id:1
[Log] id:2

这输出的结果符合我的要求,没想到forEach和for语句还是有很大的区别,然而我对forEach多了一份陌生感,但我还是那么喜欢你,想要深入了解你,所以就去网上查找。然后MDN关于forEach特别提到注意的点:

注意: 没有办法中止或者跳出 forEach() 循环,除了抛出一个异常。如果你需要这样,使用 forEach() 方法是错误的。

哇,真是好尴尬了哦,认识你这么久,却没注意到你有一个特别的地方~

难道只能用for语句来实现,并不,数组还有很多其他的方法,比如some,every,find,findIndex,get到这些,妈妈就再也不用担心我打键盘打得麻了~

需求还是上面的那个,如果某个对象是否在数组,如果在就结束循环,这就用到some,先看看MDN的说法:

some() 方法测试数组中是不是有元素通过了被提供的函数测试。它返回的是一个Boolean类型的值。

就是说如果有一个元素满足条件,则返回true,否则返回false

下面我们来康康用some怎么写的:

let isInArr=person.some(item=>{
    console.log('id:'+item.id);
    return item.id==2;
})
//打印出来的结果:
[Log] id:1
[Log] id:2
//isInArr:true

看吧,只要符合条件,就能提前结束循环的,some代码多么短小精悍,所以当遇到有类似这个需求的,记得多用some,保证你爽歪歪~

然后我们再来康康 every,MDN是这样介绍:

every() 方法测试一个数组内的所有元素是否都能通过某个指定函数的测试。它返回一个布尔值。

every和上面some差不多,但区别在于,some只要搜索一个符合条件的元素,则直接返回true,而every比较严格,每一个都要符合条件的...

比如说一个篮球队每个人身高必须170以上,否则不能参加比赛(false),代码是这样写的:

let members=[{id:1,name:'Nancy',height:176},{id:2,name:'Carol',height:180},{id:3,name:'Lucy',height:175}];

let isHeight=members.every(item=>{
    return item.height>=170;
})
console.log('isHeight:'+isHeight);

//打印出来的结果:
isHeight:true

然后我们再来康康 find,这个与上面两个区别在于,这个会返回元素的值,不符合的则返回 undefined,回到第一个需求,如果某个id是否在数组,并且想要知道该元素的具体信息,并且结束循环,这就用到find,代码是这样:

let obj=person.find(item=>{
    return item.id==2;
})
console.log(obj);
//打印出来的结果:
{id: 2, name: "Carol", age: 26}

但如果我想要返回元素的下标,这样就能根据下标来进行操作的,这就需要用到findIndex,代码是这样:

let objIndex=person.findIndex(item=>{
    return item.id==2;
})
console.log(objIndex);
//打印出来的结果:
1

timg.jpg

cool! 短小精悍!让我们一起抛弃for语句臃肿的代码,多撸some,every,find,findIndex。如果非要兼容旧版本的IE,那打扰了~

翻译系列之你能回答7个闭包的面试题么?

原文:https://dmitripavlutin.com/javascript-closures-interview-questions/ 作者:Dmitri Pavlutin

作为js开发,必须知道闭包是什么。在前端面试中,很可能会被问到闭包的概念。

我整理了7个有趣且比较有难度的问题。

准备好一只笔和一张纸,尽量不看答案或者敲代码运行。我估算你大概需要30分钟。

尽情开始吧!

如果你需要学习闭包,我推荐看闭包简单的例子

目录

1、哪个是闭包

2、参数问题

3、谁的谁

4、棘手的闭包

5、消息对还是错

6、恢复封装

7、智能相乘

小结

1、哪个是闭包

思考一下三个函数:clickHandler, immediate 和 delayedReload:

let countClicks = 0;
button.addEventListener('click', function clickHandler() {
  countClicks++;
});
const result = (function immediate(number) {
  const message = `number is: ${number}`;
  return message;
})(100);
setTimeout(function delayedReload() {
  location.reload();
}, 1000);

问题:以上哪个是闭包以及为什么?

答案:

判断是否是闭包的简单规则就是,一个函数是否能访问外部函数的变量
1、clickHandler函数是闭包,因为它能访问外部的countCLicks。
2、immediate函数不是闭包,因为它没有访问到外部的任何一个变量。
3、delayedReload函数是闭包,因为它访问到全局变量location,也就是最顶层的函数域。

2、 参数问题

以下代码打印出什么?

(function immediateA(a) {
  return (function immediateB(b) {
    console.log(a); // 打印出什么
  })(1);
})(0);
答案:

打印出 0
因为immediateA函数的参数是0,因此传输给a为0.
然后immediateB又是在immediateA的函数里,而且它是一个闭包的,所以immediateB里的a能访问到外面immediateA的a,所以打印出 0

3: 谁的谁

以下的代码块打印出什么

let count = 0;
(function immediate() {
  if (count === 0) {
    let count = 1;
    console.log(count); // What is logged?
  }
  console.log(count); // What is logged?
})();
答案:

打印出 1 和 0
因为一开头声明了count = 0,然后在immediaye函数是一个闭包,因为它的count能访问到一开头声明的那个count,所以此时count是0,然后在条件块上,因为满足count===0的条件,所以进入条件块里,然后因为let具有块级作用域,所以用let声明count时,此时的count为1,所以第一个console.log(count)打印出1
第二个console.log(count)因为是在immediate函数里,而count是会访问到外部的count,也就是一开头声明的那个count,所以为0

4: 棘手的闭包

以下的代码块打印出什么

for (var i = 0; i < 3; i++) {
  setTimeout(function log() {
    console.log(i); // What is logged?
  }, 1000);
}
答案:

打印出 3,3,3
该代码块执行有两个阶段:
阶段一:
1、for循环有3次,每次循环时都会创建一个新的log函数,而log函数里的setTimeout()是在1000ms后开始执行的。
2、循环完成后,i就变成3,而setTimeout还没开始执行的。
阶段二:
第二个就是发生在1000ms后:
1、setTimeou()就开始执行的,因为是闭包的,所以里面的i能访问到外部的i,而外部的i此时就是3,所以打印出3,之后的setTimeou也是如此的。
这就是为什么打印出3,3,3的原因。

挑战另一个问题:如何让这个代码块打印出0,1,2?请在下面的评论写下你的答案。

5: 消息对还是错

以下代码块打印出什么

function createIncrement() {
  let count = 0;
  function increment() { 
    count++;
  }

  let message = `Count is ${count}`;
  function log() {
    console.log(message);
  }
  
  return [increment, log];
}

const [increment, log] = createIncrement();
increment(); 
increment(); 
increment(); 
log(); // What is logged?
答案:

打印出 Count is 0
increment被调用3次,每次count都+1,3次后就成为3.
message变量是在createIncrement函数内,它的初始化是“count is 0"。然而,即使count增加1,message始终保持“count is 0"
log函数是一个闭包,它能访问到外部的message,所以打印出“count is 0"

挑战另一个问题:如何让message同步显示count的数?请在下面的评论写下你的答案。

6、恢复封装

createStack函数创建stack的实例:

function createStack() {
  return {
    items: [],
    push(item) {
      this.items.push(item);
    },
    pop() {
      return this.items.pop();
    }
  };
}

const stack = createStack();
stack.push(10);
stack.push(5);
stack.pop(); // => 5

stack.items; // => [10]
stack.items = [10, 100, 1000]; // 破坏封装

这stack运行看起来正常的,但有一个小小的问题,items属性被暴露了,所以任何人能直接修改这个属性。

这确实会破坏stack的封装,按理来说应该只有push和pop方法被公开的,而items就不应该被公开的。

利用闭包的概念来重构上面的createStack函数,实现items不能被初createStack函数之外访问。

function createStack() {
  // 写下你的代码
}

const stack = createStack();
stack.push(10);
stack.push(5);
stack.pop(); // => 5

stack.items; // => undefined
答案:

function createStack() {
  const items = [];
  return {
    push(item) {
      items.push(item);
    },
    pop() {
      return items.pop();
    }
  };
}
const stack = createStack();
stack.push(10);
stack.push(5);
stack.pop(); // => 5
stack.items; // => undefined

7、智能相乘

在multiply函数内,写下两个数相乘。

function multiply(num1, num2) {
  // Write your code here...
}

如果multiply有两个参数,则返回两个参数相乘的结果

如果multiply只有一个参数,比如说const anotherFunc = multiply(num1),则返回anotherFunc函数,然后anotherFunc函数又赋值给一个参数num2,则返回num1 * num2的结果

multiply(4, 5); // => 20
multiply(3, 3); // => 9

const double = multiply(2);
double(5);  // => 10
double(11); // => 22
答案:

function multiply(number1, number2) {
  if (number2 !== undefined) {
    return number1 * number2;
  }
  return function doMultiply(number2) {
    return number1 * number2;
  };
}
multiply(4, 5); // => 20
multiply(3, 3); // => 9
const double = multiply(2);
double(5);  // => 10
double(11); // => 22

小结

对比你的答案:

1、如果正确有5个以及以上,说明你的理解能力强;

2、如果没达到,我推荐你看看闭包的例子

还想挑战吗?来试试这个7个比较难的js面试题

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.