在这一章以函数为主的讲解中,也会不可避免的牵涉到数组和对象的内容,这也不难理解,知识往后走牵涉的内容自然也越多。
(1)经典作用域问题
题一:
var a = 0
function f(){
console.log(a); ==>undefined
var a = 2;
console.log(a); ==>2
}
f()
第二次日志输出结果相对容易理解,关键是第一次打印结果并不是全局变量中的a,因为按照作用域链的查找规则,在函数执行的时候会先查找局部作用域,而此时局部作用域已经存在变量a,只不过还没有执行到赋值阶段,所以打印结果为undefined。
题二:
var a = 0
function f(a){
console.log(a); ==>1
var a = 2;
console.log(a); ==>2
}
f(1)
相比题一,这里多了个同名参数a,在函数体执行之前,a就已经被实参赋值,所以打印结果为1。
关于作用域链:函数在执行的时候,会在内部生成执行上下文对象,它包含了两个属性,一个是活动对象,一个是scope属性;活动对象在函数体执行之初就被实参、函数体内变量、this对象、argument对象填充,scope属性指向上层函数的活动对象,当执行函数体内的变量查找时,会首先在本函数活动对象内查找,如果未找到,则根据scope指向的上层函数的活动对象内查找,以此类推,一直往上查找到全局window对象。
(2)预编译期与执行期
var f = function(){console.log(1)}
function f(){console.log(2)}
f() ==>1
这里不能简单的以为js代码从上至下执行,所以下一个函数定义会覆盖上一个函数定义。实际上js在执行之前存在一个预编译期,所有通过var声明的变量和function声明的函数都在js的预编译期进行处理(包括对变量名进行索引和对function函数体进行解析),function函数体内部语句和相关的逻辑解析后,会存储在函数变量所指向的地址中等待函数调用。而在js的执行期,f又被重新赋值改变了引用地址,而实际上执行期间function f(){console.log(2)}这条声明式的语句并不会再去执行(在预编译期间已经处理)或产生任何其它改变,所以最终结果为1。
思考题:
function test() {
alert("1");
}
test();
function test() {
alert("2");
}
test();
(3)函数判断数据类型并返回
function getType(obj){
return Object.prototype.toString.call(obj).slice(8,-1);
}
这种方法相比于使用typeof/instance/constructor等更加便捷,也是目前使用最多的方法。它利用了Object内置对象原型方法返回各类型特殊字符串的性质,可以全部区分出8种数据类型。但实际上Object.prototype.toString方法并不是专门为区分数据类型而设计的,它返回的字符串类型也并不是仅仅有8种,如果有必要需要考虑这两种特殊情况:arguments对象返回'Arguments',dom对象返回'HTMLDivElement'。
(4)小试牛刀:函数实现取最大值,函数参数个数不限,比如fun(1,2)返回2,fun(1,2,3,4)返回4
方法一:
function max(){
var arr = [].slice.call(arguments,0);
arr.sort(function(a,b){
return a-b;
})
return arr.pop();
}
这里考察了arguments对象的使用和排序问题,需要注意的是这里需要将arguments对象先转换为真正的数组,才能使用sort方法,这里使用了[].slice.call,等效于Array.prototype.slice.call,它们地址引用是相同的。当然如果你换成下面这种解题答法我相信面试官会更开心的。
方法二:
function max(){
return Math.max.apply(null,arguments);
}
当然,这里并没有对参数类型进行考虑,如果要考虑一些异常情况或者,可以把这些特殊处理加上去。
(5)函数、数组、对象综合题
已有数据对象为:
var students = [{
name:'lilei',
sex:'girl',
scores:{
Math:88,
English:69
}
},
......
]
函数功能需求(1):
//功能需求:日志打印出存放男学生或女学生或全部学生的名字的数组
function getStudent(sex){
//ToDo
......
console.log(arr);
}
getStudent();
解答:
function getStudent(sex){
var arr = []; //存放最终结果
for(i in students){
if(sex == undefined){
arr.push(students[i].name);
}else if(students[i].sex == sex){
arr.push(students[i].name);
}
}
console.log(arr);
}
函数功能需求(2):
//功能需求:日志打印出存放某科目所有学生成绩的数组
function getScore(Subject){
//ToDo
...
console.log(arr);
}
//打印所有学生数学成绩
//getScore('Math');
解答:
function getScore(Subject){
var arr = []; //存放该科目所有成绩
for(i in students){
arr.push(students[i].scores[Subject]);
}
console.log(arr);
}
函数功能需求(3):
//功能需求:日志打印一个对象,该对象为某科目为最低分或最高分的学生名字、对于科目成绩
function getObj(Subject,maxOrMin){
//ToDo
...
console.log(obj);
}
//打印数学最高分的学生名字和数学成绩:{name:***,Math:**}
getObj('English','min');
解答:
function getObj(Subject,maxOrMin){
var obj = {};
var student;
students.sort(function(a,b){
return a.scores[Subject] - b.scores[Subject]; //按成绩从小到大排列
})
if(maxOrMin == 'max'){
student = students[students.length-1];
}else{
student = students[0];
}
obj.name = student.name;
obj[Subject] = student.scores[Subject];
}
(6)函数中的this应用场景
题一:普通函数中的this
var a = 0;
function fun(){
var a = 'fun';
console.log(this.a); ==>0
}
普通函数中的this指向window对象
题二:方法中的this
var a = 0;
var obj = {
a:'obj',
fun:function(a){
console.log(this.a); ==>'obj'
}
}
obj.fun(1);
当函数作为对象的方法(对象的属性值为一个函数时,我们称此函数为该对象的方法)调用时,方法中的this指向调用方法的对象。
思考题:
var a = 0;
var obj = {
a:'obj',
fun:function(a){
return function(){
console.log(this.a); ==> ? 自己动手试试,然后思考为什么
}
}
}
obj.fun(1)();
题三:在setTimeout和setInterval中的this
var a = 0;
var obj = {
a:'obj',
fun:function(a){
setTimeout(function(){
console.log(this.a); ==>0
},1000)
}
}
obj.fun(1);
在setTimeout和setInterval中this永远指向window,所以,假设在题三中期望输出结果为obj对象的a属性值,则可以这样修改:
var a = 0;
var obj = {
a:'obj',
fun:function(){
var _this = this;
setTimeout(function(){
console.log(_this.a); ==>'obj'
},1000)
}
}
obj.fun();
题四:作为构造函数调用
var a = 0;
function Obj() {
this.a = 1;
}
var o = new Obj();
console.log(o.a); ==>1
这是个简单的构造函数,在new操作过程中,实例化对象将在构造函数中替代this指向,相当于Obj.call(o)。
题五:在dom元素的事件句柄中
<div οnclick="fun(this)">ddd</div>
<script>
function fun(el) {
console.log(el); ==>触发事件句柄的dom对象
}
</script>
(7)面向对象问题
题一:实现一个继承