2020.11.16——函数、层叠上下文

JS基础 && CSS面试题 -2020.11.16

  • a. JS基础

     i. 了解JS中的函数,什么是函数,判断函数的方法,函数调用的方式,与之对应的this在函数中的使用,
     this的几种取值,如何创建函数,了解特殊函数(匿名函数, IIFE(立即执行函数表达式)),
     箭头函数, 箭头函数和普通函数的区别,函数的参数(arguments)
    

函数

函数实际上是对象。每个函数都是 Function类型的实例,而 Function 也有属性和方法,跟其他引用类型一样。
因为函数是对象,所以函数名就是指向函数对象的指针,而且不一定与函数本身紧密绑定。

函数名

因为函数名就是指向函数的指针,所以它们跟其他包含对象指针的变量具有相同的行为。这意味着
一个函数可以有多个名称,如下所示:

function sum(num1, num2) {
return num1 + num2;
}
console.log(sum(10, 10)); // 20
let anotherSum = sum;
console.log(anotherSum(10, 10)); // 20
sum = null;
console.log(anotherSum(10, 10)); // 20

函数参数

 ECMAScript 函数的参数在内部表现为一个数组。函数被调用时总会接收一个数组,但函数并不关心这个数组中包含什么。
 如果数组中什么也没有,那没问题;如果数组的元素超出了要求,那也没问题。
 事实上,在使用 function 关键字定义(非箭头)函数时,可以在函数内部访问 arguments 对象,从中取得传进来的每个参数值。
 arguments 对象是一个类数组对象(但不是 Array 的实例),因此可以使用中括号语法访问其中的元素(第一个参数是 arguments[0] ,第二个参数是 arguments[1] )。
 而要确定传进来多少个参数,可以访问 arguments.length 属性。
function sayHi(name, message) {
console.log("Hello " + name + ", " + message);
}

可以通过 arguments[0] 取得相同的参数值。因此,把函数重写成不声明参数也可以:
function sayHi() {
console.log("Hello " + arguments[0] + ", " + arguments[1]);
}

通过arguments.length取得传入参数的个数:
function howManyArgs() {
console.log(arguments.length);
}
howManyArgs("string", 45); // 2
howManyArgs(); // 0
howManyArgs(12); // 1

传入参数不同进行不同处理
function doAdd() {
if (arguments.length === 1) {
console.log(arguments[0] + 10);
} else if (arguments.length === 2) {
console.log(arguments[0] + arguments[1]);
}
}
doAdd(10); // 20
doAdd(30, 20); // 50

在这个 doAdd() 函数中,同时使用了两个命名参数和 arguments 对象。命名参数 num1 保存着与
arugments[0] 一样的值,因此使用谁都无所谓。(同样, num2 也保存着跟 arguments[1] 一样的值。)
arguments 对象的另一个有意思的地方就是,它的值始终会与对应的命名参数同步。来看下面的
例子:
function doAdd(num1, num2) {
arguments[1] = 10;
console.log(arguments[0] + num2);
}
这个 doAdd() 函数把第二个参数的值重写为 10。因为 arguments 对象的值会自动同步到对应的命
名参数,所以修改 arguments[1] 也会修改 num2 的值,因此两者的值都是 10。但这并不意味着它们都
访问同一个内存地址,它们在内存中还是分开的,只不过会保持同步而已。

箭头函数中的参数

如果函数是使用箭头语法定义的,那么传给函数的参数将不能使用 arguments 关键字访问,而只
能通过定义的命名参数访问。
function foo() {
console.log(arguments[0]);
}
foo(5); // 5
let bar = () => {
console.log(arguments[0]);
};
bar(5); // ReferenceError: arguments is not defined

虽然箭头函数中没有 arguments 对象,但可以在包装函数中把它提供给箭头函数:
function foo() {
let bar = () => {
console.log(arguments[0]); // 5
};
bar();
}
foo(5);

箭头函数

ECMAScript 6 新增了使用胖箭头( => )语法定义函数表达式的能力。
很大程度上,箭头函数实例化的函数对象与正式的函数表达式创建的函数对象行为是相同的。
任何可以使用函数表达式的地方,都可以使用箭头函数
let arrowSum = (a, b) => {  //箭头函数
return a + b;
};
let functionExpressionSum = function(a, b) {  //函数表达式
return a + b;
};
console.log(arrowSum(5, 8)); // 13
console.log(functionExpressionSum(5, 8)); // 13

箭头函数简洁的语法非常适合嵌入函数的场景:
let ints = [1, 2, 3];
console.log(ints.map(function(i) { return i + 1; })); // [2, 3, 4]
console.log(ints.map((i) => { return i + 1 })); // [2, 3, 4]

如果只有一个参数,那也可以不用括号。只有没有参数,或者多个参数的情况下,才需要使用括号:
// 以下两种写法都有效
let double = (x) => { return 2 * x; };
let triple = x => { return 3 * x; };

// 没有参数需要括号
let getRandom = () => { return Math.random(); };

// 多个参数需要括号
let sum = (a, b) => { return a + b; };

// 无效的写法:
let multiply = a, b => { return a * b; };

箭头函数也可以不用大括号,但这样会改变函数的行为。使用大括号就说明包含“函数体”,可以
在一个函数中包含多条语句,跟常规的函数一样。如果不使用大括号,那么箭头后面就只能有一行代码,
比如一个赋值操作,或者一个表达式。而且,省略大括号会隐式返回这行代码的值:
// 以下两种写法都有效,而且返回相应的值
let double = (x) => { return 2 * x; };
let triple = (x) => 3 * x;

// 可以赋值
let value = {};
let setName = (x) => x.name = "Matt";
setName(value);
console.log(value.name); // "Matt"

// 无效的写法:
let multiply = (a, b) => return a * b;

箭头函数虽然语法简洁,但也有很多场合不适用。
箭头函数不能使用 arguments 、 super 和new.target ,也不能用作构造函数。
此外,箭头函数也没有 prototype 属性。

判断一个对象是否为函数

1、Object.prototype.toString 方法
function isFun(obj) {
    retrun Object.prototype.toString.call(obj) === '[object Function]';
}
var e = function() {};
isFun(e);    //true

2、 typeof方法
if(typeof 函数名 === "function")
				alert("is function");

创建函数

1、函数声明的方式定义
function sum (num1, num2) {
return num1 + num2;
}   //这里没有分号

2、函数表达式(匿名函数,function关键字后面没有标识符)
let sum = function(num1, num2) {
return num1 + num2;
};

3、箭头函数
let sum = (num1, num2) => {
return num1 + num2;
};

4、使用Function构造函数
	这个构造函数接收任意多个字符串参数,最后一个参数始终会被当成函数体,
	而之前的参数都是新函数的参数。来看下面的例子:
let sum = new Function("num1", "num2", "return num1 + num2"); // 不推荐

我们不推荐使用这种语法来定义函数,因为这段代码会被解释两次:
第一次是将它当作常规ECMAScript 代码,
第二次是解释传给构造函数的字符串。
这显然会影响性能。不过,把函数想象为对象,把函数名想象为指针是很重要的。而上面这种语法很好地诠释了这些概念。

this

1、在标准函数中, this 引用的是把函数当成方法调用的上下文对象,这时候通常称其为 this 值(在
网页的全局上下文中调用函数时, this 指向 windows )。来看下面的例子:
window.color = 'red';
let o = {
color: 'blue'
};

function sayColor() {
console.log(this.color);
}
sayColor(); // 'red'
o.sayColor = sayColor;
o.sayColor(); // 'blue'

定义在全局上下文中的函数 sayColor() 引用了 this 对象。这个 this 到底引用哪个对象必须到
函数被调用时才能确定。因此这个值在代码执行的过程中可能会变。如果在全局上下文中调用
sayColor() ,这结果会输出 "red" ,因为 this 指向 window ,而 this.color 相当于 window.color 。
而在把 sayColor() 赋值给 o 之后再调用 o.sayColor()this 会指向 o ,即 this.color 相当于
o.color ,所以会显示 "blue"2、在箭头函数中, this 引用的是定义箭头函数的上下文。下面的例子演示了这一点。在对 sayColor()
的两次调用中, this 引用的都是 window 对象,因为这个箭头函数是在 window 上下文中定义的:
window.color = 'red';
let o = {
color: 'blue'
};
let sayColor = () => console.log(this.color);
sayColor(); // 'red'
o.sayColor = sayColor;
o.sayColor(); // 'red'
在事件回调或定时回调中调用某个函数时, this 值指向的并非想要的对象。
此时将回调函数写成箭头函数就可以解决问题。这是因为箭头函数中的 this 会保留定义该函数时的上下文:
function King() {
this.royaltyName = 'Henry';
// this 引用 King 的实例
setTimeout(() => console.log(this.royaltyName), 1000);
}

function Queen() {
this.royaltyName = 'Elizabeth';
// this 引用 window 对象
setTimeout(function() { console.log(this.royaltyName); }, 1000);
}
new King(); // Henry
new Queen(); // undefined

立即调用的函数表达式

obj.action.say()执行之后,此时这个函数里的this指向的是action对象,
原因是因为say函数是通过action对象直接调用的
(function() {
// 块级作用域
})();
使用 IIFE 可以模拟块级作用域,即在一个函数表达式内部声明变量,然后立即调用这个函数。
这样位于函数体作用域的变量就像是在块级作用域中一样。
(function () {
for (var i = 0; i < count; i++) {
console.log(i);
}
})();
console.log(i); // 抛出错误
// console.log() 会出错,因为它访问的变量是在 IIFE 内部定义的,在外部访问不到
下面展示了两种不同的块级作用域形式:
// 内嵌块级作用域
{
let i;
for (i = 0; i < count; i++) {
console.log(i);
}
}
console.log(i); // 抛出错误

// 循环的块级作用域
for (let i = 0; i < count; i++) {
console.log(i);
}
console.log(i); // 抛出错误
IIFE 用途的一个实际的例子,就是可以用它锁定参数值。比如:
let divs = document.querySelectorAll('div');
// 达不到目的!
for (var i = 0; i < divs.length; ++i) {
divs[i].addEventListener('click', function() {
console.log(i);
});
}

这里使用 var 关键字声明了循环迭代变量 i ,但这个变量并不会被限制在 for 循环的块级作用域内。
因此,渲染到页面上之后,点击每个 <div> 都会弹出元素总数。这是因为在执行单击处理程序时,迭代变量的值是循环结束时的最终值,即元素的个数。而且,这个变量 i 存在于循环体外部,随时可以访问。
以前,为了实现点击第几个 <div> 就显示相应的索引值,需要借助 IIFE 来执行一个函数表达式,传入每次循环的当前索引,从而“锁定”点击时应该显示的索引值:

let divs = document.querySelectorAll('div');
for (var i = 0; i < divs.length; ++i) {
divs[i].addEventListener('click', (function(frozenCounter) {
return function() {
console.log(frozenCounter);
};
})(i));
}

而使用 ECMAScript 块级作用域变量,就不用这么大动干戈了:
let divs = document.querySelectorAll('div');
for (let i = 0; i < divs.length; ++i) {
divs[i].addEventListener('click', function() {
console.log(i);
});
}

这样就可以让每次点击都显示正确的索引了。这里,事件处理程序执行时就会引用 for 循环块级作用域中的索引值。
这是因为在 ECMAScript 6 中,如果对 for 循环使用块级作用域变量关键字,在这里就是 let ,那么循环就会为每个循环创建独立的变量,从而让每个单击处理程序都能引用特定的索引。
但要注意,如果把变量声明拿到 for 循环外部,那就不行了。下面这种写法会碰到跟在循环中使用var i = 0 同样的问题:
let divs = document.querySelectorAll('div');
// 达不到目的!
let i;
for (i = 0; i < divs.length; ++i) {
divs[i].addEventListener('click', function() {
console.log(i);
});
}

函数调用

1、作为函数调用,函数为全局对象
function myFunction(a, b) {
    return a + b;
}
add(10,20);           //30
window.add(10, 20);    // 30

2、作为方法调用
var myObject = {
    firstName:"John",
    lastName: "Doe",
    fullName: function () {
        return this.firstName + " " + this.lastName;
    }
}
myObject.fullName();         // 返回 "John Doe"

var myObject = {
    firstName:"John",
    lastName: "Doe",
    fullName: function () {
        return this;
    }
}
myObject.fullName();          // 返回 [object Object] (所有者对象)

var bj=10;
var obj={
    name:"八戒",
    age:500,
    say:function(){
        console.log(this);//是obj这个对象
        console.log(this.bj);//undefined
        console.log(this.name)//八戒
    },
    action:{
        name:"悟空",
        age:1000,
        say:function(){
            console.log(this);//是action这个对象
            console.log(this.bj);//undefined
            console.log(this.name)//悟空
        }
    }
}
obj.say();//obj.say()执行之后,此时这个函数里的this指向的是obj对象,原因是因为say函数是通过obj直接调用的。
obj.action.say();//obj.action.say()执行之后,此时这个函数里的this指向的是action对象,原因是因为say函数是通过action对象直接调用的
window.obj.action.say();

var bj=10;
var obj={
    name:"八戒",
    age:500,
    say:function(){
        return function(){
            console.log(this);//window
            console.log(this.bj);//10
            console.log(this.age);//undefined
        }
    }
}
obj.say()();//obj.say()执行之后返回的是一个函数,并没有执行,再加一个括号就是执行返回的那个匿名函数。

3、使用构造函数调用函数
// 构造函数:
function myFunction(arg1, arg2) {
    this.firstName = arg1;
    this.lastName  = arg2;
}
 
// This    creates a new object
var x = new myFunction("John","Doe");
x.firstName;                             // 返回 "John"

4、作为函数方法调用函数
function myFunction(a, b) {
    return a * b;
}
myObject = myFunction.call(myObject, 10, 2);     // 返回 20

function myFunction(a, b) {
    return a * b;
}
myArray = [10, 2];
myObject = myFunction.apply(myObject, myArray);  // 返回 20
  • b. CSS面试题

     i. 了解层叠上下文: 层叠上下文如何创建,主要的表现是什么
    

    层叠上下文

     层叠上下文(stacking context),是HTML中一个三维的概念。
     在CSS2.1规范中,每个盒模型的位置是三维的,分别是平面画布上的X轴,Y轴以及表示层叠的Z轴。
    

    创建层叠上下文

     1. HTML中的根元素<html></html>本身j就具有层叠上下文,称为“根层叠上下文”。
     2. 普通元素设置position属性为非static值并设置z-index属性为具体数值,产生层叠上下文。
    
<style>
  div {  
    position: relative;  
    width: 100px;  
    height: 100px;  
  }  
  p {  
    position: absolute;  
    font-size: 20px;  
    width: 100px;  
    height: 100px;  
  }  
  .a {  
    background-color: blue;  
    z-index: 1;  
  }  
  .b {  
    background-color: yellow;  
    z-index: 2;  
    top: 20px;  
    left: 20px;  
  }  
  .c {  
    background-color: red;  
    z-index: 3;  
    top: -20px;  
    left: 40px;  
  }
</style>

<body>  
  <div>  
    <p class="a">a</p>  
    <p class="b">b</p>  
  </div> 

  <div>  
    <p class="c">c</p>  
  </div>  
</body> 

在这里插入图片描述

<style>
  div {
    width: 100px;
    height: 100px;
    position: relative;
  }
  .box1 {
    z-index: 2;
  }
  .box2 {
    z-index: 1;
  }
  p {
    position: absolute;
    font-size: 20px;
    width: 100px;
    height: 100px;
  }
  .a {
    background-color: blue;
    z-index: 100;
  }
  .b {
    background-color: yellow;
    top: 20px;
    left: 20px;
    z-index: 200;
  }
  .c {
    background-color: red;
    top: -20px;
    left: 40px;
    z-index: 9999;
  }
</style>

<body>
  <div class="box1">
    <p class="a">a</p>
    <p class="b">b</p>
  </div>

  <div class="box2">
    <p class="c">c</p>
  </div>
</body>

在这里插入图片描述

主要表现

  • 普通元素的层叠水平优先由层叠上下文决定,因此,层叠水平的比较只有在当前层叠上下文元素中才有意义。

  • 当相互层叠的元素都是层叠上下文元素,直接比较z-index的值,值大的处在值小的元素上方。

  • 当元素处在同一层叠上下文内时可按下图进行层叠判断。
    在这里插入图片描述

  • 当元素的层叠等级一致、层叠顺序也相同的时候,在DOM流中处于后面的元素会覆盖前面的元素。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值