一、基础知识
1.变量类型
- JS中使用typeof 能得到哪些类型
- 何时使用 === 何时使用 ==
- JS中有哪些内置函数
- JS变量按照存储方式区分为哪些类型,并描述其特点
- 如何理解JSON
值类型:
每个变量存储各自的值,不会相互影响,number , string , boolean
var a = 100;var b = a;a = 200;console.log(b); //100
引用类型:
变量只是通过一个指针指向对象,改变了age对象,由于a也指向该对象,所以a指向的age对象为21, object , array , function
var a = {age:20}var b = ab.age = 21console.log(a.age) //21
typeof 运算符
有六种类型:undefined , string , number , boolean , object , function ; typeof只能区分值类型的详细类型;
typeof {} //objecttypeof [] //objecttypeof null //objecttypeof console.log //function
2.变量计算 - 强制类型转换
字符串拼接
var a = 100 + 10 //110var b = 100 + '10' //10010
==运算符
obj.a == null ——> obj.a === null || obj.a === undefined
(除了 obj.a == null 用== 其余都用=== ,jQuery 源码推荐写法)
100 == ‘100’ //true0 == '' //truenull == undefined //true
if语句
var a = trueif(a){ //...}var b = 100if(b){ //...}var c = ''if(c){ //...}
if中转换为false的情况:
if(0){}if(NaN){}if(''){}if(null){}if(false){}
逻辑运算符
console.log(10 && 0) //0console.log('' || 'abc') //'abc'console.log(!window.abc) //true 因为window.abc 是undefined
判断一个变量会被当作true还是false
var a = 100;console.log(!!a)
JS中的内置函数
Object , Array , Boolean , Number , String , Function , Date , RegExp , Error
(注意:Math 是对象,不是函数)
理解JSON
//JSON 是 JS 一个内置对象而已
JSON.stringify({a:10, b:20}) 对象转换字符串
JSON.parse('{"a":10, "b":20}') 字符串转换成对象
3.原型和原型链
- 如何准确判断变量是数组类型?
- 写一个原型链继承的例子
- 描述new 一个对象的过程
- 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) //可创建多个对象
开头字母须大写
new Foo()时把参数传进去,this会先变成空对象,挨个赋值之后,会return出来给f,
f就具备了调用属性的能力。
构造函数扩展
- var a = {} 其实是 var a = new Object() 的语法糖
- var a = [] 其实是 var a = new Array()的语法糖
- function Foo(){...} 其实是 var Foo = new Function(...)
- 使用instanceof 判断一个函数是否是一个变量的构造函数(判断一个变量是否为“数组”: 变量 instanceof Array)
原型规则和示例
- 所有的引用类型(数组、对象、函数),都具有对象特性,即可自由扩展属性(除了“null” 以外)
-
//自由扩展属性var obj = {}; obj.a = 100;var arr = []; arr.a = 100;function fn (){}fn.a = 100;
- 所有的引用类型(数组、对象、函数),都有一个 __proto__属性(隐式原型),属性值是一个普通对象
console.log(obj.__proto__); //Objectconsole.log(arr.__proto__); //Array[0]console.log(fn.__proto__); //function(){}
- 所有的函数,都有一个prototype属性(显式原型),属性值也是一个普通的对象
console.log(fn.prototype); //Object{}
- 所有的引用类型(数组、对象、函数),__proto__属性值指向它的构造函数的"prototype"属性值,(指向和完全等是一个概念)
console.log(obj.__proto__ === Object.prototype); //true
- 当试图得到一个引用类型值的某个属性时,如果这个对象本身没有这个属性,那么会去它的__proto__(即它的构造函数的prototype)中寻找。
function Foo(name,age){ this.name = name;}Foo.prototype.altername = function(){ alert(this.name);}var f = new Foo('zhangsan');f.printname = function(){ console.log(this.name);}f.printname();f.altername(); f.toString(); //要去 f.__proto__.__proto__中查找
- 当试图得到一个引用类型值的某个属性时,如果这个对象本身没有这个属性,那么会去它的__proto__(即它的构造函数的prototype)中寻找。
循环对象自身的属性:
var item;for(item in f){ //高级浏览器已经在 for in 中屏蔽了来自原型的属性 //但是这里建议大家还是加上这个判断,保证程序的健壮性 if(f.hasOwnProperty(item)){ console.log(item); }}
原型链:
instanceof
判断引用类型 属于哪个构造函数的方法
比如 f instanceof Foo 的判断逻辑是:
f 的 __proto__ 一层一层往上,能否对应到Foo.prototype,再试着判断 f instanceof Object
原型链继承的例子
基本:
//动物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();
实例:
function Elem(id){ this.elem = document.getElementById(id);}Elem.prototype.html = function(val){ var elem = this.elem; if(val){ elem.innerHTML = val; return this; //链式操作 }else{ return elem.innerHTML; }}Elem.prototype.on = function(type,fn){ var elem = this.elem; elem.addEventListener(type,fn);}var div1 = new Elem('div1');div1.html('hello world
').on('click', function(){ alert('clicked');})
描述new一个对象的过程
- 创建一个新对象
- this 指向这个新对象
- 执行代码,即对 this 赋值
- 返回 this
4、作用域和闭包
- 说一下对变量提升的理解
- 说明this几种不同的使用场景
- 创建 10 个<a>标签,点击的时候弹出来对应的序号
- 如何理解作用域
- 实际开发中闭包的应用
执行上下文
在执行代码前,先把定义声明获取一遍,再由上到下执行
- 范围:一段<script>或者一个函数
- 全局:变量定义、函数声明
- 函数:变量定义、函数声明、this、arguments
PS:注意“函数声明”和“函数表达式”的区别
console.log(a); //undefinedvar a = 100;fn('zhangsan'); //'zhangsan' 20function fn(name){ age = 20; console.log(name, age); var age; }
this
this要在执行时才能确认值,定义时无法确认
var a = { name: 'A'; fn: function(){ console.log(this.name); }}a.fn(); //this === aa.fn.call({name: 'B'}); //this === {name: 'B'} var fn1 = a.fn;fn1() //this === window
几种场景:
- 作为构造函数执行
-
function Foo(name){ this.name = name; //this === Foo}var f = new Foo('zhangsan');
-
- 作为对象属性执行
-
var obj = { name : 'A', printName : function(){ console.log(this.name); }}obj.printName(); //this === obj
- 最为普通函数执行
-
function fn(){ console.log(this);}fn(); //this就是Window
- call apply bind (其实就是用来改变this指向的)
-
function fn1(name,age){ alert(name); console.log(this);}fn1.call({x:100}, 'zhangsan', 20); //this就是{x:100}fn1.apply({x:100}, ['zhangsan', 20]); //this就是{x:100}var fn2 = function(name,age){ alert(name); console.log(this);}.bind({y:200});fn2('zhangsan',20);//this就是{y:200}
作用域
- 没有块级作用域
-
if(true){ var name = 'zhangsan';}console.log(name);
- 只有函数和全局作用域
-
var a = 100;function fn(){ var a = 200; console.log('fn',a);}console.log('global',a)fn();
作用域链
function fn(){ var b = 200; //当前作用域没有,向父级作用域去找 console.log(a); console.log(b);}fn();
var a = 100;function F1(){ var b = 200; function F2(){ var c = 300; console.log(a); //a是自由变量 console.log(b); //b是自由变量 console.log(c); } F2();}F1();
一个自由变量,一直不断的去往它的父级作用域找,形成了一个链式结构
闭包
function F1(){ var a = 100; //返回一个函数(函数作为返回值) return function(){ console.log(a) }}//f1 得到一个函数var f1 = F1();var a = 200;f1(); //100
使用场景:
- 函数作为返回值(上一个例子)
- 函数作为参数传递
-
function F1(){ var a = 100; return function(){ console.log(a) //这个a首先去声明的作用域找 }}var f1 = F1();function F2(fn){ var a = 200; fn();}F2(f1); //100
实际开发的应用:
//闭包实际应用中主要用于封装变量,收敛权限 function isFirstLoad(){ var _list = []; return function(id){ if(_list.indexOf(id) >= 0){ return false; }else{ _list.push(id); return true; } }}var firstLoad = isFirstLoad();firstLoad(10) //truefirstLoad(10) //falsefirstLoad(20) //true //在isFirstLoad 函数外,根本不可能修改掉 _list 的值
创建 10 个<a> 标签,点击的时候弹出来对应的序号
var i;for (i = 0; i < 10; i++) { (function(i){ //自执行函数 var a = document.createElement('a'); a.innerHTML = i + ''; a.addEventListener('click', function(){ alert(i); return false; }) document.body.appendChild(a); })(i)};
for (var i = 0; i < 10; i++) { (function(i){ var a = document.createElement('a'); a.innerHTML = '第'+i+'个'; a.onclick = function(){ alert(i); } document.body.appendChild(a); })(i)};
5、异步和单线程
- 同步和异步的区别是什么?分别举一个同步和异步的例子
- 一个关于setTimeout的笔试题
- 前端使用异步的场景有哪些
什么是异步
console.log(100)setTimeout(function(){ console.log(200)},1000)console.log(300)//100//300//200
异步和同步最大的区别在于有没有阻塞程序的进行
对比同步
console.log(100)alert(200)console.log(300)//不点击确认,程序会一直卡,不输出300
何时需要异步
- 可能发生等待的情况
- 等待过程中不能像 alert 一样阻塞程序运行
- 因此,所有的“等待的情况”都需要异步
使用异步场景:
- 定时任务:setTimeout,setInterval
- 网络请求:ajax请求,动态<img>加载
- 事件绑定
//ajax请求代码实例 响应事件需要等待console.log('start');$.get('./data1.json',function(data1){ console.log(data1);})console.log('end');
//加载实例 图片加载需要等待console.log('start');var img = document.createElement('img');img.onload = function(){ console.log('loaded');}img.src = '/xxx.png';console.log('end');
//事件绑定实例 触发事件需要等待console.log('start');document.getElementById('btn1').addEventListener('click',function(){ alert('clicked');})console.log('end');
单线程
JS是单线程语言,所有的异步程序会被拿出去先不执行,主线程执行完后,它要看边上有没有等待的程序需要执行
console.log(100)setTimeout(function(){ console.log(200)})console.log(300)//100//300//200
- 执行第一行,打印100
- 执行setTimeout后,传入setTimeout的函数会被暂存起来,不会立即执行(单线程的特点,不能同时干两件事)
- 执行最后一行,打印300
- 待所有程序执行完,处于空闲状态时,会立马看有没有暂存起来的要执行
- 发现暂存起来的setTimeout 中的函数无需等待时间,就立即过来执行
一个关于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//4
六、其他知识
- 获取 2018-01-23 格式的日期
- 获取随机数,要求是长度一致的字符串格式
- 写一个能遍历对象和数组的通用 forEach 函数
日期
Date.now() //获取当前时间毫秒数var dt new Date();dt.getTime() //获取毫秒数dt.getFullYear() //年dt.getMonth() //月(0-11)dt.getDate() //日(1-31)dt.getDay() //星期(0-6)dt.getHours() //小时(0-23)dt.getMinutes() //分钟(0-59)dt.getSeconds() //秒(0-59)
Math
获取随机数 Math.random() (0-1之间的小数)
常见作用:清除缓存
数组API
- forEach 遍历所有元素
-
var arr = ['苹果','香蕉','西瓜'];arr.forEach(function(item,index){ //遍历数组的所有元素 console.log(index,item); //0 "苹果" //1 "香蕉" //2 "西瓜"})
- every 判断所有元素是否都符合条件
-
var arr = [1,2,3];var result = arr.every(function(item,index){ //用来判断所有的数组元素,都满足一个条件 if(item < 4){ return true; }})console.log(result); //true
- some 判断是否有至少一个元素符合条件
-
var arr = [1,2,3];var result = arr.some(function(item,index){ //用来判断所有的数组元素,只要有一个满足条件 if(item < 2){ return true; }})console.log(result); //true
- sort 排序
-
var arr = [1,4,2,3,5];var arr2 = arr.sort(function(a,b){ //从小到大排序 return a - b; //从大到小排序 //return b - a})console.log(arr2);
- map 对元素重新组装,生成新数组
-
var arr = [1,2,3,4];var arr2 = arr.map(function(item,index){ //将元素重新组装,并返回 return '' + item + '';})console.log(arr2);//["1", "2", "3", "4"]
- filter 过滤符合条件的元素
-
var arr = [1,2,3,4,5];var arr2 = arr.filter(function(item,index){ //通过某一个条件过滤数组 if(item >= 2){ return true; }});console.log(arr2);//[2, 3, 4, 5]
对象API
var obj = {x:100,y:200,z:300};for(var key in obj){ //如果key是自身的属性,那么打印出来 if(obj.hasOwnProperty(key)){ console.log(key,obj[key]); }}
获取 2018-01-23 格式的日期:
var dt = new Date();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'+month; } return year + '-' + month + '-' + date;}console.log(formatDate(dt));
获取随机数,要求是长度一致的字符串格式:
var random = Math.random();random = random + '0000000000';//后面加上10个0random = random.slice(0,10);console.log(random);
写一个能遍历对象和数组的通用 forEach 函数
function forEach(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];//注意,这里参数顺序换了,为了和对象的遍历格式一致forEach(arr,function(index,item){ console.log(index,item);})var obj = {x:100,y:200};forEach(obj,function(key,value){ console.log(key,value);})