《前端JavaScript面试技巧》笔记一

思考:

  • 拿到一个面试题,你第一时间看到的是什么 -> 考点
  • 又如何看待网上搜出来的永远也看不完的题海 -> 不变应万变
  • 如何对待接下来遇到的面试题 -> 题目到知识再到题目

知识体系:

JS基础知识

一、变量类型和计算

题目:

  • JS中使用typeof能得到哪些类型?
  • 何时使用 === 何时使用 == ?
  • JS中有哪些内置函数
  • JS变量按照存储方式区分为哪些类型,并描述其特点
  • 如何理解JSON

知识点:

/*一、变量类型*/
  /*值类型vs引用类型*/
  //从内存来说,值类型是把每个值分块存放在内存中,而引用类型是好几个变量公用一个内存块,节省内存空间。
  //引用类型包括:对象、数组、函数。引用类型的属性是可以无限扩展的,属性多了,就会占用更多的内存空间,所以引用类型是为了公用内存空间。
  //值类型
  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

  /*typeof运算符详解*/
  //typeof只能区分值类型,引用类型也只能区分function,因为function的定位非常高。
  typeof undefined //undefined (值类型)
  typeof 'abc' //string (值类型)
  typeof 123 //number (值类型)
  typeof true //boolean (值类型)
  typeof {} //object (引用类型)
  typeof [] //object (引用类型)
  typeof null //object (引用类型)
  typeof console.log //function (引用类型)


/*二、变量计算---强制类型转换*/ //字符串拼接 var a = 100+10; //110 var b = 100+'10'; //'10010' //运算符 //==会把两边的值转换为true或false 100 == '100'; //true 0 == ''; //true null == undefined; //true //if语句 //if会把括号里面的值转换为true或false var a = true; if(a){...} var b = 100; if(b){...} var c = ''; if(c){...} //逻辑运算 console.log(10 && 0); //0 console.log('' || 'abc'); //'abc' console.log(!window.abc); //true //判断一个变量会被当作true还是false var a = 100; console.log(!!a);

解题:

JS中使用typeof能得到哪些类型?

  undefined、string、number、boolean、object、function

何时使用 === 何时使用 == ?

if(obj.a==null){
   //这里相当于 obj.a === null || obj.a ===undefined ,简写形式
   //这是 jquery 源码中推荐的写法
}

  双等会进行强制类型转换,三等不会进行强制类型转换。

  除了上面的例子用双等,其它的都用三等。

JS中有哪些内置函数 

  数据封装类对象
  Object
  Array
  Boolean
  Number
  String
  Function
  Date
  RegExp :正则表达式
  Error

JS变量按照存储方式区分为哪些类型,并描述其特点

  值类型和引用类型。

  从内存来说,值类型是把每个值分块存放在内存中,而引用类型是好几个变量公用一个内存块,节省内存空间。

如何理解JSON  

  JSON只不过是一个JS对象,也是一种数据格式。
  JSON基本的api只有两个:
  JSON.stringify({a:10,b:20}); //对象转字符串
  JSON.parse('{"a":10,"b":20}'); //字符串转对象

 

 二、原型和原型链

题目:

  • 如何准确判断一个变量是数组类型
  • 写一个原型链继承的例子
  • 描述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时把参数传进去。new函数执行时里面的this会变成空对象,给this赋值后,再把this给return回来,return回来就把值赋值给了f,这时f就具备f.name = 'zhangsan',f.age = 20,f.class ='' 'class-1'
  

/*二、构造函数-扩展*/
  /*
  var a = {} 其实是 var a = new Object() 的语法糖。 构造函数是Object函数。
  var a = [] 其实是 var a = new Array() 的语法糖。 构造函数是Array函数。
  function Foo(){...} 其实是 var Foo = new Function(...)。 构造函数是Function。
  推荐前面的书写方式。
  使用instanceof判断一个函数是否是一个变量的构造函数。比如:判断一个变量是否为“数组”:变量 instanceof Array
  */

/*三、原型规则和示例*/ //5条原型规则-原型规则是学习原型链的基础 //1、所有的引用类型(数组、对象、函数),都具有对象特性,即可自由扩展属性(除了null以外) var obj = {}; obj.a = 100; var arr = []; arr.a = 100; function fn(){}; fn.a = 100; //2、所有的引用类型(数组、对象、函数),都有一个__proto__属性,属性值是一个普通的对象。 __proto__ 隐式原型 console.log(obj.__proto__); console.log(arr.__proto__); console.log(fn.__proto__); //3、所有的函数,都有一个prototype属性,属性值也是一个普通的对象。 prototype显式原型 console.log(fn.prototype); //4、所有的引用类型(数组、对象、函数),__proto__属性值指向它的构造函数的"prototype"属性值。 console.log(obj.__proto__ === Object.prototype); //5、当试图得到一个对象的某个属性时,如果这个对象本身没有这个属性,那么会去它的 __proto__(即它的构造函数的prototype)中寻找。 //构造函数 function Foo(name,age){ this.name = name; } Foo.prototype.alertName = function(){ alert(this.name); } //创建示例 var f = new Foo('zhangsan'); f.printName = function(){ console.log(this.name); } //测试 f.printName(); //zhangsan f.alertName(); //zhangsan //补充 -- 循环对象自身的属性 var item; for(item in f){ //高级浏览器已经在 for in 中屏蔽了来自原型的属性 //但是这里建议还是加上这个判断,保证程序的健壮性 if(f.hasOwnProperty(item)){ console.log(item); } } /*四、原型链*/ //构造函数 function Foo(name,age){ this.name = name; } Foo.prototype.alertName = function(){ alert(this.name); } //创建示例 var f = new Foo('zhangsan'); f.printName = function(){ console.log(this.name); } //测试 f.printName(); //zhangsan f.alertName(); //zhangsan f.toString(); //要去f.__proto__.__proto__中查找 /*五、instanceof*/ //用于判断引用类型属于哪个构造函数的方法 //构造函数 function Foo(name,age){ this.name = name; } Foo.prototype.alertName = function(){ alert(this.name); } //创建示例 var f = new Foo('zhangsan'); f.printName = function(){ console.log(this.name); } //测试 f.printName(); //zhangsan f.alertName(); //zhangsan f.toString(); //要去f.__proto__.__proto__中查找 //instanceof //f instanceof Foo 的判断逻辑是: //1、f 的 __proto__ 一层一层往上,能否对应到 Foo.prototype //2、再试着判断 f instanceof Object

 

  

 

 

解题:

如何准确判断一个变量是数组类型 

var arr = [];
arr instanceof Array; //true
typeof arr; //object   typeof是无法判断是否是数组的 

写一个原型链继承的例子

 //动物
  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){
  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);
  return this;
}

var div1 = new Elem('div1');
console.log(div1.html());
div1.html('<h2>新内容</h2>').on('click',function(){
alert(div1.html());
}).html('<h2>新内容新内容新内容</h2>').on('click',function(){
alert('第二次');
}); //链式操作


/*div1.html('<h2>新内容</h2>')
div1.on('click',function(){
alert(div1.html());
})*/

 

描述new一个对象的过程

/*
  1、创建一个新对象
  2、this指向这个新对象
  3、执行代码,即对this赋值
  4、返回this
 */
 /*构造函数*/
  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); //创建多个对象

zepto(或其他框架)源码中如何使用原型链

  1. 阅读源码是高效提高技能的方式。如zepto
  2. 但不能“埋头苦钻”,有技巧在其中
  3. 慕课网搜索“zepto设计和源码分析”

 

三、作用域和闭包

题目:

  • 说一下对变量提升的理解
  • 说明this几种不同的使用场景
  • 创建10个<a>标签,点击时弹出对应的序号
  • 如何理解作用域
  • 实际开发中闭包的应用

知识点:

/*一、执行上下文*/
 //范围:一段<script>或者一个函数
 //全局:变量定义、函数声明(一段<script>)
 //函数:变量定义、函数声明、this、arguments(函数)
 //PS:注意“函数声明”和“函数表达式”的区别
 //实际代码中不要下面这种写法,都是先定义,再执行,提高代码可读性。
 console.log(a); //undefined
 var a = 100;

 fn('zhangsan'); //'zhangsan' 20
 function fn(name){ //函数声明
  age = 20;
  console.log(name,age);
  var age;
 }
 var fn = function(){} //函数表达式


/*二、this*/
	//this要在执行时才能确认值,定义时无法确认。
	//作为构造函数执行
	//作为对象属性执行
	//作为普通函数执行
	//call、apply、bind
	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


/*作用域*/
	/*无块级作用域*/
	//没有块级作用域,写在里面和外面是一样的。不建议下面这种写法,程序不易读。
	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);
	fn();


/*作用域链*/
	//函数的父级作用域是函数定义时的作用域,不是函数执行时的作用域。
	var a = 100;
	function fn(){
		var b = 200;

		//当前作用域没有定义的变量,即“自由变量”
		console.log(a); //100
		console.log(b); //200
	}
	fn();
	//例子
	var a = 100;
	function F1(){
		var b = 200;
		function F2(){
			var c = 300;
			console.log(a); //100 //自由变量
			console.log(b); //200 //自由变量
			console.log(c); //300
		}
		F2();
	}
	F1();



/*闭包*/
	function F1(){
		var a = 100;

		//返回一个函数(函数作为返回值)
		return function(){
			console.log(a);
		}
	}
	//f1得到一个函数
	var f1 = F1();
	var a = 200;
	f1();
	//f1执行的是return里的函数,return里的a是个自由变量,要去父级作用域寻找,父级作用域F1里面定义了a,所以打印出来的a的值是100.
	/*闭包的使用场景
	1、函数作为返回值(上一个demo)
	2、函数作为函数传递(下面这个例子)
	*/
	function F1(){
		var a = 100;
		return function(){
			console.log(a);
		}
	}
	var f1 = F1();
	function F2(fn){
		var a = 200;
		fn();
	}
	F2(f1);

解题:

说一下对变量提升的理解 

变量定义
函数声明(注意和函数表达式的区别)
执行上下文的概念。
各个函数中,它的变量的声明和定义,以及函数的声明都会提前,放在前面,由此就是变量提升主观上、形象上的理解。

说明this几种不同的使用场景

作为构造函数执行

作为对象属性执行

作为普通函数执行

call、apply、bind

创建10个<a>标签,点击时弹出对应的序号

var i;
for(i=0;i<10;i++){
	(function(i){
		var a = document.createElement('a');
		a.innerHTML = i+'<br>';
		a.addEventListener('click',function(e){
			e.preventDefault();
			alert(i);
		})
		document.body.appendChild(a);
	})(i);
}

如何理解作用域

要领:
1、自由变量
2、作用域链,即自由变量的查找
3、闭包的两个场景

实际开发中闭包的应用

//闭包实际应用中主要用于封装变量,收敛权限
	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); //true
	firstLoad(10); //false
	firstLoad(20); //true

//在 isFirstLoad 函数外面,根本不可能修改掉 _list 的值

  

四、异步和单线程

题目: 

  • 同步和异步的区别是什么?分别举一个同步和异步的例子
  • 一个关于setTimeout的笔试题
  • 前端使用异步的场景有哪些

知识点:

/*什么是异步(对比同步)*/
	//同时执行,不会阻塞程序执行
	console.log(100);
	setTimeout(function(){
		console.log(200);
	},1000);
	console.log(300);
	setTimeout(function(){
		console.log(400);
	},1000);
	//下面这个例子类似同步
	console.log(100);
	alert(200);
	console.log(300);



/*前端使用异步的场景*/
	/*何时需要异步:
	在可能发生等待的情况
	等待过程中不能像alert一样阻塞程序进行
	因此,所有的“等待的情况”都需要异步
	1、定时任务:setTimeout、setInterval
	2、网络请求:ajax请求、动态<img>加载
	3、事件绑定
	*/
	//ajax请求代码示例
	console.log('start');
	$.get('./data1.json',function(data1){
		console.log(data1);
	})
	console.log('end');
	//<img>加载示例
	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');
	



/*异步和单线程*/
	console.log(100);
  setTimeout(function(){
    console.log(200);
  });
  console.log(300);
  //执行结果:100、300、200
  //setTimeout是异步,最后执行。这个例子的setTimeout没有等待时间,所以待其它执行完之后立马执行setTimeout。
  //单线程就是一次只能做一件事
  /*解析:
  1、执行第一行,打印100
  2、执行setTimeout后,传入setTimeout的函数会被暂存起来,不会立即执行(单线程的特点,不能同时干两件事)
  3、执行最后一行,打印300
  4、待所有程序执行完,处于空闲状态时,会立马看有没有暂存起来的要执行。
  5、发现暂存起来的setTimeout中的函数无需等待时间,就立即拿过来执行。
  */

 

解题:

同步和异步的区别是什么?分别举一个同步和异步的例子

同步会阻塞代码执行,而异步不会。
alert是同步,setTimeout是异步。

例子:

//异步
	console.log(100);
	setTimeout(function(){
		console.log(200);
	},1000);
	console.log(300);
//同步
	console.log(100);
	alert(200);
	console.log(300);

 

一个关于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

 

前端使用异步的场景有哪些  

  1. 定时任务:setTimeout、setInterval
  2. 网络请求:ajax请求、动态<img>加载
  3. 事件绑定

 

五、其它知识点

题目:

  • 获取2017-06-10格式的日期
  • 获取随机数,要求是长度一致的字符串格式
  • 写一个能遍历对象和数组的通用forEach函数

知识点: 

/*日期*/
 Date.now(); //获取当前时间毫秒数
 var dt = new Date();
 dt.getTime(); //获取毫秒数
 dt.getFullYear(); //年
 dt.getMonth(); //月(0-11)
 dt.getDate(); //日(0-31)
 dt.getHours(); //小时(0-23)
 dt.getMinutes(); //分钟(0-59)
 dt.getSeconds(); //秒(0-59)



/*Math*/
	Math.random(); //获取随机数。(返回的是 >0 和 <1 的小数)。有清除缓存的作用。



/*数组API*/
	//forEach //遍历所有元素
	var arr = [1,2,3];
	arr.forEach(function(item,index){
		//遍历数组的所有元素。item:元素的值。index:元素的位置
		console.log(index,item);
	})
	//every   //判断所有元素是否都符合条件
	var arr = [1,2,3];
	// var arr = [1,2,3,4,5]; 
	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,5,3];
	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 '<b>' + item + '</b>';
	})
	console.log(arr2);
	//filter  //过滤符合条件的元素
	var arr = [1,2,3,4];
	var arr2 = arr.filter(function(item,index){
		if(item>=2){
			return true;
		}
	})
	console.log(arr2);



/*对象API*/
	var obj = {
		x:100,
		y:200,
		z:300
	}
	var key;
	for(key in obj){ //key是obj的属性名
		//注意这里的 hasOwnProperty ,再讲原型链时候讲过了
		if(obj.hasOwnProperty(key)){ //判断key是obj原生的属性,而不是原型里面的属性
			console.log(key,obj[key]);
		}
	}

 

解题:

获取2017-06-10格式的日期

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 = new Date();
var formatDate = formatDate();
console.log(formatDate);

获取随机数,要求是长度一致的字符串格式

var random = Math.random();
var random = random + '0000000000'; //后面加上10个零,为了确保长度一致
var random = random.slice(0,10); //截取前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 in循环
      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);
  })

 

 

 

 

 

posted @ 2017-09-15 09:45  孟三秋  阅读(410)  评论(0编辑  收藏  举报