JS三座大山:原型和原型连、作用域和闭包、异步和单线程
  第一篇-----变量类型和计算  
 参考:https://www.jianshu.com/p/8cb184b26ed1
 题目:
 1.JS中使用typeof能得到哪些类型
 2.何时使用'===',何时使用'=='
 3.JS有哪些内置函数
 4.JS变量按照存储方式分为哪些类型,并描述其特点
 5.如何理解JSON
 知识点#####
- 值类型vs引用类型
按照存储方式分为值类型和引用类型
 引用类型 :数组、对象、null、函数。
 其他是值类型。
  //值类型
 var a=100
 var b=a
 a=200
 console.log(b) //100
  //引用类型
 var a={age:20}
 var b=a
 b.age=21
 console.log(a.age) //21
 引用类型:对象、数组、函数,由于引用类型占用内存空间较大,所以会出现公用内存空间的情况,变量a,b是指向某个内存空间的指针。而var b=a时,其实是b又指向了与a相同的内存空间,{age:20}仅存在一份。
  - typerof运算符详解
typeOf有五种基本类型。
 typeof undefined   //undefined
 typeof 'abc'   //string
 typeof 123   //number
 typeof true   //boolean
 typeof {}// object
 typeof [] // object
 typeof null // object
 typeof console.log //function
  后四种都是引用类型,但typeof只能区分出函数,和值类型。
   以下情况会发生强制类型转换:
 - 字符串拼接
'=='需要慎用,会试图进行类型转换使前后相等
 - if语句
var a=true;
 if(a){        //true }
 var b = 100;
 if(a){ //true  }
 var a = '';
 if(a){   //false  }
 - 逻辑运算符
console.log(10 & 0) //0 转换为true&&0
 console.log(''||'abc') //abc 转换为false||'abc'
 console.log(!window.abc) //true !undefined为true
 - undefined 和 is not defined 不同,后者是报错,前者是未定义
解题#####
1.JS中使用typeof能得到哪些类型
 undefined,string,number,boolean,object,function
 2.何时使用'===',何时使用'=='
 //仅有这种情况使用'==
 'if(obj.a==null){
 //此时条件相当于obj.a===null||obj.a===undefined,简写形式
 //这是jQuery源码中推荐的写法
 }
 除此之外,其它情况均建议使用'==='
 3.JS有哪些内置函数
 Object,Array,Boolean,Number,String,Function,Date,RegExp,Error
 4.JS变量按照存储方式分为哪些类型,并描述其特点
 分为值类型和引用类型,值类型可以将数据分块存储在内存中,但是引用类型是多个变量共用一个内存块,引用类型的赋值是指定了一个指针,并不是真正的值的拷贝,它们之间是会相互干预的。
 5.如何理解JSON
 JSON是JS中的一个内置对象,也是一种数据格式
 JSON.stringify({a:10,b:20}) //将对象转换为字符串
 JSON.parse('{"a":10,"b":20}') //把字符串转换为对象
        第二篇-----原型与原型链
 参考:https://www.jianshu.com/p/dc3015d68c46
 题目
 1.如何准确判断一个变量是数组类型
 2.写一个原型链继承的例子
 3.描述new一个对象的过程
 4.zepto框架中如何使用原型链
  知识点
 - 构造函数
function Foo(name,age){
 this.name=name
 this.age=age
 this.class='class-1'
 //return this //默认有这一行
 }
 var f=new Foo('zhangsan',20)
 //var f1=new Foo('lisi',22) //创建多个对象
 - 构造函数-扩展
var a={} //其实是var a=new Object()的语法糖
 var b=[] //其实是var b=new Array()的语法糖
 function Foo(){...} //其实是var Foo=new Function(...)
 使用instanceof判断一个函数是否是一个变量的构造函数
 所有的引用类型(对象、数组、函数)都有构造函数,a的构造函数是Object(),b的构造函数是Array(),Foo的构造函数是Function()。所以假如想要判断一个变量是否为数组就可以使用
 var a={}
 a instanceof Array //false
 - 原型规则和实例(熟记)- 所有的引用类型都具有对象特性,即可自由扩展属性(null除外)
- 所有引用类型都有一个__proto__(隐式原型属性),属性值是一个普通的对象
- 所有函数都有一个prototype (显式原型属性),属性值也是一个普通的对象
- 所有引用类型的__proto__属性值指向它的构造函数的prototype的属性值
- 当试图得到一个引用类型的某个属性时,如果这个对象本身没有这个属性,那么会去它的__proto__(即它构造函数的prototype)中寻找
 var obj={};obj.a=100;
 var arr=[];arr.a=100;
 function fn(){}
 fn.a=100
 console.log(obj.__proto__)
 console.log(arr.__proto__)
 console.log(fn.__proto__)
 console.log(fn.prototype)
 console.log(obj.__proto__===Object.prototype) //true
 
//构造函数
 function Foo(name,age){
 this.name=name
 }
 Foo.prototype.alertName=function(){ //由于prototype是一个普通对象,所以也可以扩展属性
 alert(this.name)
 }
 //创建实例
 var f=new Foo('zhangsan')
 f.printName=function(){
 console.log(this.name)
 }
 //测试
 f.printName() //zhangsan
 f.alertName() //f没有alertName属性,于是去f._proto_即Foo.prototype中查找
 由对象调用原型中的方法,this指向对象
 //循环对象自身的属性
 var item   
 //理论上有三个属性 name, printName,alertName 。但是自身的属性只有前两个,使用hasOwnProperty() 能过滤掉原型上的属性
 for(item in f){
   //高级浏览器已在for in中屏蔽了来自原型的属性
 //但是这里建议还是加上这个判断以保证程序的健壮性
 if(f.hasOwnProperty(item)){
 console.log(item)
 }
 }
 - 原型链
//在刚刚的代码中加入
 f.toString() //要去f.__proto__.__proto__中查找 找到了
 所有的引用类型都有__proto__属性,且__proto__属性值指向它的构造函数的prototype的属性值,所以当f不存在toString时,便会在f.__proto__即Foo.prototype中查询,而Foo.prototype中也没有找到toString。由于Foo.prototype也是一个对象,所以它隐式原型__proto__的属性值便指向它的构造函数Object的prototype的属性值。
 一个函数都会有显式原型属性,属性值是一个普通对象,(见原型规则3)。而普通对象的构造函数是Object
 //试一试
 console.log(Object.prototype)
 console.log(Object.prototype.__proto__) //为了避免死循环,所以此处输出null
   原型链
 - instanceof
 用于判断引用类型属于哪个构造函数的方法
 f instanceof Foo //true
判断逻辑是f的__proto__一层层向上能否对应到Foo.prototype,再试着判断
 f instanceof Object //true
  解题#####
1.如何准确判断一个变量是数组类型
 使用instanceof  (用于判断引用类型属于哪个构造函数的方法)
  var arr=[]
 arr instanceof Array //true
 typeof arr //object typeof无法准确判断是否是数组
 2.写一个原型链继承的例子
 //简单示例,比较low,下面有更贴近实战的例子
 //动物
 function Animal(){
 this.eat=function(){
 console.log('Animal eat')
 }
 }
 //狗
 function Dog(){
 this.bark=function(){
 console.log('Dog bark')
 }
 }
 Dog.prototype=new Animal()
 //哈士奇
 var hashiqi=new Dog()
    //更贴近实战的原型继承实例
 //封装一个DOM查询
 function Elem(id){
 this.elem=document.getElementById(id);
 }
 Elem.prototype.html = function(val){
 if(val){
 this.elem.innerHTML =val;
 }
 return this;            
 }
  Elem.prototype.on=function(type,fn){
 var elem=this.elem;
 elem.addEventListener(type,fn);
 return this;   //链式操作
 }
  var div1=new Elem('div1');
 // console.log(div1.html());
 // div1.html('<p>Hello</p>');
 // div1.on('click',function(){
 //      alert('clicked')
 // });
 div1.on('click',function(){
 alert('clicked');
 }).html('<p>链式操作</p>');
 //在之前的函数中增加了return this,由div1调用时便会返回当前对象,即div1,便可以实现链式操作
 3.描述new一个对象的过程
 function Foo(name,age){
 // this={}
 this.name=name;
 this.age=age;
 this.class='class-1';
 //return this;  //默认有这一行
 }
 var f=new Foo('zhangsan',20);
 //var f1=new Foo('lisi',22); //创建多个对象
  过程:
 创建一个新对象  //{}
 this指向这个新对象 this={}
 执行代码,即对this赋值 this.xxx=xxx
 返回this return this
  4.zepto框架如何使用原型链
   第三篇-----作用域和闭包
  题目
 1.说一下对变量提升的理解
 2.说明this几种不同的使用场景
 3.创建10个<a>标签,点击时弹出对应序号
 4.如何理解作用域
 5.实际开发中闭包的应用
  知识点#####
- 执行上下文
范围:一段<script>或者一个函数 或者eval代码
 全局:变量定义、函数声明 (提前拿出来)                    针对一段<script>
 函数:变量定义、函数声明、this、arguments  (提前拿出来)                    针对一个函数
 eval不常用,也不推荐大家用。
  在一段js代码拿过来真正一句一句运行之前,浏览器已经做了一些“准备工作”,在“准备工作”中完成了哪些工作:
 1.变量、函数表达式——变量声明,默认赋值为undefined;
 2.this——赋值;
 3.函数声明——赋值;
 这三种数据的准备情况我们称之为“执行上下文”或者“执行上下文环境”。
   ps:注意函数声明和函数表达式的区别
 //函数声明
 function fn(){
   //.....
 }
  //函数表达式
 var fn1=function(){
 //.....
 }
  //全局console.log(a); //undefined
 var a=100
  fn('zhangsan') //zhangsan 20
 function fn(name){
 //函数
 console.log(this); //Window
 console.log(arguments); //"zhangsan"
 age=20
 console.log(name,age);
 var age   //age会提前
 }
  - this
this要在执行时才确认值,定义时无法确认
 var a={
 name:'A',
 fn:function(){
 console.log(this.name);
 }
 }
 a.fn() //this===a
 a.fn.call({name:'B'}) //this==={name:'B'}
 var fn1=a.fn
 fn1() //this===Window
  - - 作用域
js没有块级作用域
 if(true){
 var name='zhangsan'
 }
 console.log(name); //zhangsan
 只有函数和全局作用域
 var a=100
 function fn(){
 var a=200
 console.log('fn',a)
 }
 console.log('global',a)  //global 100
 fn() //fn 200
 - 作用域链
自由变量
 var a=100
 function fn(){    
 var b=200    
 console.log(a) //当前作用域没定义的变量,即'自由变量'    
 console.log(b)
 }
 fn() //100 200
  调用在当前作用域不存在的变量,便会向父级作用域查找。需要注意的是,父级作用域是函数定义时产生的,并非函数调用时。
 var a=100
 function F1(){
 var b=200
 function F2(){
 var c=300
 console.log(a) //a是自由变量,在F2中未找到便向父级作用域F1查找,仍未找到,继续向上查找,在Window中找到
 console.log(b) //b是自由变量
 console.log(c)
 }
 F2()
 }
 F1() //100 200 300
 - - 闭包
使用场景,函数作为返回值;函数作为参数传递
 //闭包的使用场景:函数作为返回值
 function F1(){
 var a=100
 //返回一个函数
 return function(){
 console.log(a) //自由变量,父作用域查找,仍未找到,继续向上查找,在Window中找到
 }
 }
 //f1得到一个函数
 var f1=F1()
 var a=200
 f1()  //100   
 //闭包的使用场景:函数作为参数传递
 function F1(){
 var a=100
 return function(){
 console.log(a) //自由变量,父作用域查找
 }
 }
 var f1=F1()
 function F2(fn){
 var a=200
 fn()
 }
 F2(f1) //100
 #####解题#####
 **1.说一下对变量提升的理解**
 在ES6之前,JavaScript没有块级作用域(一对花括号{}即为一个块级作用域),只有全局作用域和函数作用域。变量提升即将变量声明提升到它所在作用域的最开始的部分。
 在<script>或函数中,各个变量、函数的声明与定义会被提前
 执行上下文。变量、函数表达式、this、函数声明,这几种数据的准备情况称之为“执行上下文”
  **2.说明this几种不同的使用场景**
 this的几种执行情况:
 - 作为构造函数执行
- 作为对象属性执行
- 作为普通函数执行
- call apply bind
//构造函数
 function Foo(name){
 this.name=name
 }
 var f=new Foo('zhangsan')
   //对象属性
 var obj={
 name:'zhangsan',
 printName:function(){
 console.log(this.name)
 }
 }
 obj.printName()
   //普通函数
 function fn(){
 console.log(this);
 }
 fn()    //window
  //call apply bind
 function fn1(name,age){
 alert(name)
 console.log(this)    
 }
 fn1.call({x:100},'zhangsan',20)    //({x:100}
 //apply
 fn1.apply({x:100},['zhangsan',20])    //({x:100}
 call, apply方法区别是,从第二个参数起, call方法参数将依次传递给借用的方法作参数, 而apply直接将这些参数放到一个数组中再传递
 //bind
 var fn2=function (name,age){ //bind在函数声明的形式后不可用,必须是函数表达式
 alert(name)
 console.log(this)
 }.bind({y:200})
 fn2('zhangsan',20)   //{y: 200}
 **3.创建10个```<a>```标签,点击时弹出对应序号**
 //错误的写法
 var i,a;
 for(i=0;i<10;i++){
 //除了click函数内部,都为全局作用域,会被覆盖。因此最终 i 的值为10
 //全局作用域
 a = document.createElement('a');
 a.innerHTML = i+'</br>' ;
 a.addEventListener('click',function(e){
 e.preventDefault();
 alert(i);  //i为自由变量,向上去父作用域查找时,值已经变成10. 因为click事件执行时,其它部分早已执行完毕。
 })        
 document.body.appendChild(a);
 }
  //正确的写法
 var i
 for(i=0;i<10;i++){
 //多包了一层,除了click函数,其它变量的作用于都变成了函数作用域,而不是全局作用域,因此不会被覆盖。相当于创建了10个函数
 (function(i){      
 //函数作用域
 var a=document.createElement('a')
 a.innerHTML=i
 a.addEventListener('click',function(e){
 e.preventDefault()
 alert(i)   //i为自由变量,向上去父作用域查找,就找到调用时的i值 (加粗处)
 })
 document.body.appendChild(a)
 })(i)
 }
 工作实例:
  不用闭包 结果永远是i的最大值+1,因此必须使用闭包
    **4.如何理解作用域**
 回答要点:
 自由变量    //当前作用域没定义的变量,即'自由变量'    
 作用域链,即自由变量的查找
 闭包的两个场景
  **5.实际开发中闭包的应用**
 //闭包实际应用中主要用于封装变量,收敛权限
 function isFirstLoad(){
 var _list=[]
 return function(id){
 if(_list.indexOf(id)>=0){
 return false
 }else {
 _list.push(id)
 return true
 }
 }
 }
 //使用
 var firstLoad=isFirstLoad()9
 firstLoad(10) //true
 firstLoad(10) //false
 firstLoad(20) //true
 //在isFirstLoad函数外,无法修改_list的值
  第四篇 异步和单线程
题目
1.同步和异步的区别是什么?分别举一个同步和异步的例子
 2.一个关于setTimeout的笔试题
 3.前端使用异步的场景有哪些
 知识点#####
- 什么是异步(对比同步)
//异步
 console.log(100);
 setTimeout(function(){
 console.log(200);
 },1000)
 console.log(300);
 // 100
 // 300
 // +1s后 200
  //同步
 console.log(100);
 alert(200);
 console.log(300);
 //100
 //对话框200
 //关闭对话框后300
  同步会出现阻塞,```alert(200)```不执行结束,后面的代码不会继续执行
 - 前端使用异步的场景
 在可能发生等待的情况。
 等待的过程中不能像alert一样阻塞程序运行
 因此所有的“等待的情况”都需要异步执行。
 -1) 定时任务:setTimeout、setInterval
 -2) 网络请求:ajax请求,动态```<img>```加载等
 -3) 事件绑定
 ```
  //ajax请求
 console.log('start');
 $.get('data/data1.json',function(data1){
 console.log(data1);
 })
 console.log('end');
 //start
 //end
 //数据
 ```
 ```
 //图片加载
 console.log('start');
 var img=document.createElement('img')
 img.οnlοad=function(){
 console.log('loaded');
 }
 img.src='images/icon.jpg'
 document.body.appendChild(img)
 console.log('end');
 //start
 //end
 //loaded
 ```
 //事件绑定
 ```
 console.log('start');
 document.getElementById('btn1').addEventListener('click',function(){
 console.log('clicked')
 })
 console.log('end');
 //start
 //end
 //点击打印clicked
 ```
    - 异步和单线程
 ```
 console.log(100);
 setTimeout(function(){
 console.log(200);
 }) //未设置等待时间
 console.log(300);
 //100
 //300
 //200
 ```
 - 执行第一行,打印100
 - 执行setTimeout后,传入setTimeout的函数会被暂存起来,不会立即执行(单线程的特点,不能同时执行两个任务)
 - 执行最后一行,打印300
 - 待所有任务执行完,处于空闲状态,才执行暂存的任务
 - 暂存的setTimeout无等待时间,立即执行
  #####解题#####
 **1.同步和异步的区别是什么?分别举一个同步和异步的例子**
 - 同步与异步最大的区别是阻塞代码,同步会阻塞代码,而异步不会
 - alert是同步,setTimeout是异步
  **2.一个关于setTimeout的笔试题**
 console.log(1);
 setTimeout(function(){
 console.log(2);
 },0)
 console.log(3);
 setTimeout(function(){
 console.log(4);
 },1000)
 console.log(5);
 //1
 //3
 //5
 //2
 //+1s后 4
  **3.前端使用异步的场景有哪些**
 - 定时任务:setTimeout、setInterval
 - 网络请求:ajax请求,动态```<img>```加载
 - 事件绑定
  它们共同的特点是需要等待,由于js是一个单线程语言,为了避免阻塞,所以要使用异步
  第五篇   其它知识点(如date、Math、常用API)
  1.获取2017-07-13格式的日期     //日期
 2.获取随机数,要求是长度一致的字符串格式       //Math3.写一个能遍历对象和数组的通用forEach函数       //数组API  对象API 
 知识点#####
- 日期
Date.now()       //获取当前时间毫秒数
 var dt=new Date()
 dt.getTime()//获取毫秒数
 dt.getFullYear()        //年
 dt.getMonth()//月(0-11)
 dt.getDate()      //日(0-31)
 dt.getDay()      //星期(0 代表星期日, 1 代表星期一,2 代表星期二, 依次类推)
 dt.getHours()    //小时(0-23)
 dt.getMinutes()      //分钟(0-59)
 dt.getSeconds()//秒(0-59)
 //获取的一切时间都是var dt=new Date()时的时间
 - Math
- 数组API
var arr=[1,2,3]
 arr.forEach(function(item,index){
   // forEach() 方法对数组的每个元素执行一次提供的函数
 console.log(index,item);
 })
 //0 1//1 2
 //2 32) every判断所有元素是否符合条件
 var arr=[1,2,3]
 var result=arr.every(function(item,index){
 //every() 方法测试数组的所有元素是否都通过了指定函数的测试(所有!所有!)
 if(item<4){
       return true
 }
 })
 console.log(result);   //true
 ```
 3) some 判断是否至少有一个元素符合条件
 var arr=[1,2,3]
 var result=arr.some(function(item,index){
   //some() 方法测试数组中的某些元素是否通过由提供的函数实现的测试,只需有一个满足条件即返回true
 if(item<2){
 return true
 }
 })
 console.log(result); //true
 ```
 4) sort 排序
 var arr=[1,4,2,3,5]
 var arr2=arr.sort(function(a,b){     //arr.sort()默认从小到大排序
 //从小到大排序
 return a-b
 //从大到小排序
 // return b-a
 })
 console.log('arr2:'+arr2);
 //arr2:1,2,3,4,5
 ```
 5) map 对元素重新组装,生成新数组
 var arr=[1,2,3,4,5]
 var arr2=arr.map(function(item,index){
 //将元素重新组装并返回
 return '<b>'+item+'</b>'
 })
 console.log(arr2);
 // ["<b>1</b>", "<b>2</b>", "<b>3</b>", "<b>4</b>", "<b>5</b>"]
 ```
 6) filter 过滤符合条件的元素
 var arr=[1,2,3,4,5]
 var arr2=arr.filter(function(item,index){
 // filter() 方法创建一个新数组, 其包含通过所提供函数实现的测试的所有元素
 if(item>=3){
 return true
 }
 })
 console.log(arr2);
 // [3, 4, 5]
 ```
 - 对象API
var obj={
 x:100,
 y:200,
 z:300
 }
 var key
 for(key in obj){
   //hasOwnProperty会返回一个布尔值,判断是否是原生的属性,以此来排除原型链上的属性
 if(obj.hasOwnProperty(key)){
 console.log(key,obj[key]);
 }
 }
 //x 100
 //y 200
 //z 300
 ```
 解题#####
1.获取2017-07-13格式的日期
 function formatDate(dt){
 if(!dt){
 dt=new Date();
 }
 var year=dt.getFullYear()
 var month=dt.getMonth()+1
 var date=dt.getDate()
 if(month<10){
 month='0'+month
 }
 if(date<10){
       date='0'+date
 }
 return year+'-'+month+'-'+date
 }
 var dt
 dt=new Date()
 alert(formatDate(dt))
 2.获取随机数,要求是长度一致的字符串格式
 var random=Math.random()
 var random=random+'0000000000' //10个0
 var random=random.slice(0,10)
 //slice() 方法返回一个从0开始到1结束(不包括结束)选择的数组的一部分,浅拷贝到一个新数组对象。原始数组不会被修改console.log(random);
 3.写一个能遍历对象和数组的通用forEach函数
 function myForEach(obj,fn){
 var key
 if(obj instanceof Array){
 //判断是否为数组
 obj.forEach(function(item,index){
 fn(index,item)
 })
 }else{
 //不是数组就是对象
 for(key in obj){
 fn(key,obj[key])
 }
  }
 }
  var arr=[1,2,3]
 //参数顺序换了,为了和对象的遍历格式一致
 myForEach(arr,function(index,item){
 console.log(index,item);
 })
  var obj={x:100,y:200}
 myForEach(obj,function(key,value){
 console.log(key,value);
 })
 //0 1
 //1 2
 //2 3
 //x 100
 //y 200