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