# 12.知识点整理②

# 1.面试题:箭头函数.call()/复合箭头函数的this指向问题

var bar={name:'bar'}
var foo={
    name:'foo',
     say2:()=>{
         console.log(this.name)
        },
     say3: function(){
           return function(){
          console.log(this.name)}
        }, 
     say4:function(){
         return()=>{
             console.log(this.name)
            }
        } 
}
    foo.say2()//undefined  1
    foo.say2.call(bar)//undefined   2
    foo.say3()()//undefined   3
    foo.say3().call(bar)//bar   4
    foo.say3.call(bar)()//undefined   5
    foo.say4()()//foo   6
    foo.say4().call(bar)//foo   7
    foo.say4.call(bar)()//bar   8
//非严格模式上面的所有undefined都变成window

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26

这样想call表示的是,如2中,将bar对象的作用域带来了,在bar对象的作用域下调用say2函数;

# 2.call方法的实现

Function.prototype.imitateCall = function (context) {
    // 赋值作用域参数,如果没有则默认为 window,即访问全局作用域对象
    context = context || window    
    // 绑定调用函数(.call之前的方法即this,前面提到过调用call方法会调用一遍自身,所以这里要存下来)
    context.invokFn = this    
    // 截取作用域对象参数后面的参数
    let args = [...arguments].slice(1)
    // 执行调用函数,记录拿取返回值
    let result = context.invokFn(...args)
    // 销毁调用函数,以免作用域污染
    Reflect.deleteProperty(context, 'invokFn')
    return result
}
1
2
3
4
5
6
7
8
9
10
11
12
13

测试代码:

function Product(name, price) {
	this.name = name
	this.price = price
}

function Food(name, price) {
	Product.imitateCall(this, name, price)
	this.category = 'food'
}

let tempFood = new Food('cheese', 5)

console.log('输出:' + tempFood.name, tempFood.price, tempFood.category)
// 输出:cheese 5 food
1
2
3
4
5
6
7
8
9
10
11
12
13
14

# 3.apply方法的实现

使用过的人应该都知道,apply 和 call 的功能完全一致,区别唯有使用上的一丝丝差别

Function.prototype.call = function(context, args1, args2, args3 ...)

Function.prototype.apply = function(context, [args1, args2, args3 ...])
1
2
3

很明显了吧,唯参数形式不同而已,依此只需要稍微改动 imitateCall 方法即可模拟出我们的 imitateApply方法

Function.prototype.imitateApply = function (context) {
    // 赋值作用域参数,如果没有则默认为 window,即访问全局作用域对象
    context = context || window
    // 绑定调用函数(.call之前的方法即this,前面提到过调用call方法会调用一遍自身,所以这里要存下来)
    context.invokFn = this
    // 执行调用函数,需要对是否有参数做判断,记录拿取返回值
    let result
    if (arguments[1]) {//arguments[1]表示第一个参数
        result = context.invokFn(...arguments[1])
    } else {
        result = context.invokFn()
    }
    // 销毁调用函数,以免作用域污染
    Reflect.deleteProperty(context, 'invokFn')
    return result
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

其中:

Reflect.deleteProperty(target, propertyKey)
1
target
1

删除属性的目标对象。

propertyKey
1

需要删除的属性的名称。

# 4.bind方法的实现

bind()方法创建一个新的函数,在bind()被调用时,这个新函数的 thisbind的第一个参数指定,其余的参数将作为新函数的参数供调用时使用

这同样是 MDN 上给出的解释,意思应该已经很明显了,和call 方法类似,调用是都是将内部的this作用域对象替换为第一个参数,不过需要注意开始和结尾,调用 bind 方法时会创建一个新的函数返回待调用

代码实现:

Function.prototype.imitateBind = function (context) {
    // 获取绑定时的传参
	let args = [...arguments].slice(1),//..arguments表示全部参数
        // 定义中转构造函数,用于通过原型连接绑定后的函数和调用bind的函数
        F = function () {},
        // 记录调用函数,生成闭包,用于返回函数被调用时执行
        self = this,
        // 定义返回(绑定)函数
        bound = function () {
            // 合并参数,绑定时和调用时分别传入的
            let finalArgs = [...args, ...arguments]
            
            // 改变作用域,注:aplly/call是立即执行函数,即绑定会直接调用
            // 这里之所以要使用instanceof做判断,是要区分是不是new xxx()调用的bind方法
            return self.call((this instanceof F ? this : context), ...finalArgs)
        }
    
    // 将调用函数的原型赋值到中转函数的原型上
    F.prototype = self.prototype
    // 通过原型的方式继承调用函数的原型
    bound.prototype = new F()
    
    return bound
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

总结:

既然 call/applybind的功能如此相似,那什么时候该使用 callapply,什么时候使用 bind 呢?其实这个也没有明确的规定,一通百通而已,只要知其理,相互转化何其简单,主要的区别无非就是 call/apply 绑定后是立即执行,而 bind 绑定后是返回引用待调用

就像这样:

const people = {
    age: 18
};
 
const girl = {
    getAge: function() {
        return this.age;
    }
}
 
console.log('输出:' + girl.getAge.bind(people)());  // 输出:18
console.log('输出:' + girl.getAge.call(people));    // 输出:18
console.log('输出:' + girl.getAge.apply(people));   // 输出:18
1
2
3
4
5
6
7
8
9
10
11
12
13

# 5.如何判断一个对象是否为空{}

我们想要判断对象是否为空,像基本类型那样判断是不可以的,==={} ?这样是错误的, 因为只是比较引用地址是否相同,所以可以采取下面的方法来进行判断

  • 1.根据for...in遍历对象,如果存在则返回true,否则返回false
for ( let i in obj) {
	return true;
}
return false
1
2
3
4
  • 2.利用JSON自带的JSON.stringify()方法来判断,大概思路就是转化为字符串’{}'来进行判断
if (JSON.stringify(obj) === '{}') {
	return true;
}
return false;
1
2
3
4
  • 3.利用ES6中Object.keys()来进行判断 (推荐) Object.keys()方法会返回一个由一个给定对象的自身可枚举属性组成的数组。 如果我们的对象为空,他会返回一个空数组。
Object.keys(obj).length === 0 ? '空' : '不为空'
1

# 6.让元素水平垂直居中的五种方法

使用 flex 弹性布局

首先将父元素设置为 display:flexjustify-content: centeralign-items: center; 其次将父元素高度设置为 height:100vh,根据css3 的规范,1vh 等于视口高度的1%1vw 等于视口宽度的1%),那么 100vh 就是适口高度的 100%,即占满整个屏幕。

    #father{
        display: flex;
        justify-content: center;
        align-items: center;
        background: rgba(0,0,0,0.7);
        height:100vh;
    }
1
2
3
4
5
6
7

使用 transform 变形

同样父元素高度设置为 height:100vh; 将子元素的宽和高设置为百分数,如宽设置为 80%,则需要向X 轴偏移10%;那么 translateX10/80 = 0.125,即 12.5%;如果高设置为 60%,则需要向 Y 轴偏移 20%,那么 translateY20/60 = 33%,即子元素需要设置 transfrom:translate(12.5%,33%)

 #father{      
        background: rgba(0,0,0,0.7);
        height:100vh;
    }
    #son{
        width: 80%;
        height: 60%;
        background: white;
        border-radius: 30px;
        transform: translate(12.5%,33%);
    }

1
2
3
4
5
6
7
8
9
10
11
12

使用 position 定位

  • 将父元素设置为 positon:fixed,然后上下左右都为 0;使其填满整个屏幕;
  • 子元素也设置为 positon:fixed,然后上下左右都为0margin设置为auto,实现水平垂直居中。
    #father{      
        background: rgba(0,0,0,0.7);
        positon:relative;
        left:0;
        right:0;
        top:0;
        bottom:0;
    }
    #son{
        width: 90%;
        height: 60%;
        background: white;
        border-radius: 30px;
        margin:auto;
        positon:absolute;
        left:0;
        right:0;
        top:0;
        bottom:0;
    }

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

使用 transform 与 position 结合

将父元素设置为positon:fixed,然后上下左右都为 0;使其填满整个屏幕; 子元素也设置为 positon:fixed,然后上下各设为 50%;即位置到达中心点,但是元素也有高宽度,所以整体就偏移了,应当上下都回退25%的距离,即设置为 transform:translate(-50%,-50%)

    #father{
        background: rgba(0,0,0,0.7);
        position:relative;
        left:0;
        right:0;
        top:0;
        bottom:0;
    }
    #son{
        width: 90%;
        height: 60%;
        background: white;
        border-radius: 30px;
        position:absolute;
        left:50%;
        top:50%;
        transform: translateY(-50%,-50%);
    }

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

使用JavaScript方法

let HTML = document.documentElement,
    winW = HTML.clientWidth,
    winH = HTML.clientHeight,
    boxW = box.offserWidth,
    boxH = box.offsetHeight;
box.style.position = "absolute",
box.style.left = (winW - boxW) / 2 + "px";
box.style.top = (winH - boxH) / 2 + 'px';
1
2
3
4
5
6
7
8

还有一种方法是:使用table-cell,可以是可以平时一定不要用,会造成回流,浪费性能

body{
	display: table-cell;
	vertical-align: middle;
	text-align: center;
    /*要求有固定宽高*/
    width: 200px;
    height: 200px;
}
/*但是它是控制文本居中的,此时要把盒子变为文本,即行内元素inline-block*/
.box{
	display: inline-block;
}
1
2
3
4
5
6
7
8
9
10
11
12

总结:

  • 以上四种方法中除了第二种,只使用 transform属性,是需要依赖子元素高宽之外,其他方法均不受子元素宽高影响。
  • 想要实现这种水平垂直居中,就要想办法让父元素填充整个屏幕,比如设置 height:100vh或者设置 position:fixed;left:0;right:0;top:0;bottom:0
  • 使用弹性布局时,垂直水平居中的要点在父元素。将父元素设置为 display:flex; justify-content: center;align-items: center。
  • 不使用弹性布局时,垂直水平居中的要点在子元素,设置子元素transform 属性的 translate 使其水平垂直居中。

# 7.行内元素和块级元素的具体区别是什么?inline-block是什么?

一,行内元素与块级元素的区别:

  • 1.行内元素与块级元素直观上的区别二、行内元素与块级元素的三个区别

    行内元素会在一条直线上排列(默认宽度只与内容有关),都是同一行的,水平方向排列。

    块级元素各占据一行(默认宽度是它本身父容器的100%(和父元素的宽度一致),与内容无关),垂直方向排列。块级元素从新行开始,结束接着一个断行。

  • 2.块级元素可以包含行内元素和块级元素。行内元素不能包含块级元素,只能包含文本或者其它行内元素。

  • 3.行内元素与块级元素属性的不同,主要是盒模型属性上:行内元素设置width无效,height无效(可以设置line-height),margin上下无效,padding上下无效

二、行内元素和块级元素转换

display:block; (字面意思表现形式设为块级元素)

display:inline; (字面意思表现形式设为行内元素)

三、inline-block

inline-block 的元素(如inputimg)既具有 block 元素可以设置宽高的特性,同时又具有 inline 元素默认不换行的特性。当然不仅仅是这些特性,比如 inline-block 元素也可以设置 vertical-align(因为这个垂直对齐属性只对设置了inline-block的元素有效) 属性。 HTML 中的换行符、空格符、制表符等合并为空白符,字体大小不为 0 的情况下,空白符自然占据一定的宽度,使用inline-block 会产生元素间的空隙。(这句话下面会用例子解释)

# 8.手写验证电话号码的正则表达式

# 正则表达式

正则表达式的作用:

  • 匹配
  • 替换
  • 提取

test()正则对象方法,用于检测字符串是否符合该规则,该对象会返回truefalse,其参数是测试字符串。

validatePhone = (str) => {
    //校验手机号,号段主要有(不包括上网卡):130~139、150~153,155~159,180~189、170~171、176~178。14号段为上网卡专属号段
    let regs = /^((13[0-9])|(17[0-1,6-8])|(15[^4,\\D])|(18[0-9]))\d{8}$/;
    if(value.length == 0){
        return false
    }else{
        return regs.test(str)
   }
}

1
2
3
4
5
6
7
8
9
10

边界符:

  • ^ :以它开头
  • $:以它结尾
  • [abc]:包含a或者b,或者c
  • ^[abc]$:表示三选一,只包含a,或者只包含b,或者只包含c才返回true
  • /^[a-z]$/:任何26个英文字母中的一个都返回true;
  • /^[a-zA-Z0-9_-]$/:任何26个英文字母中大写或者小写或者0-9,或者小短线-,或者下划线_,的一个都返回true;
  • 中括号表示多选一
  • /^[a-zA-Z0-9_-]$/:如果中括号里面有^ (这里打不出就不写进去了)表示取反,即不能包含这些

量词符

量词 说明
* 重复零次或者更多次
+ 重复一次或者多次
? 重复零次或一次
{n} 重复n次
{n,} 重复n次或更多次
{n,m} 重复n到m次
  • /^a*$/:表示a要出现 >= 0
  • /^a+$/:表示a要出现 >= 1
  • /^a?$/:表示a要出现 0 || 1
  • /^a{3}$/:表示a要重复3次;
    • /^abc{3}$/:表示只需要c重复3次;
  • /^a{3,}$/:表示a要重复大于3次(包含3次);
  • /^a{3,10}$/:表示a要重复大于等于3次,小于等于10次;

/^[a-zA-Z0-9_-]{6,16}$/:表示输入这些字符或数字6~16次之间都是合法的;大括号中中间不能有空格

小括号,表示优先级

  • /^(abc){3}$/:表示要abc重复3次;

预定义类

预定义类 说明
\d 匹配0-9之间的任一数字,相当于[0-9]
\D 匹配0-9以外的字符,相当于[^0-9]
\w 匹配任意的字母,数字和下划线,相当于A-Za-z0-9_
\W 除所有字母,数字和下划线以外的字符,相当于A-Za-z0-9_
\s 匹配空格(包括换行符、制表符、空格符等),相当于[\t\r\n\v\f]
\S 匹配非空格字符,相当于[^\t\r\n\v\f]

案例:座机号码验证

全国座机号码有两种格式:010-12345678 或者 0530-1234567

正则表达式中的或者:|

let reg = /^\d{3}-\d{8}|\d{4}-\d{7}$/
//正则表达式有多种写法,比如上面的写法也可以是下面这样的
let reg2 = /^\d{3,4}-\d{7,8}$/
1
2
3

正则表达式中的替换

# 9.开启严格模式

  • 为整个脚本(script标签)开启严格模式

只需要在script标签里写就可以了

<script>
	'use strict'
</script>
//方法2:在立即执行函数中
<script>
    (function(){
    'use strict'
})()
 </script>
1
2
3
4
5
6
7
8
9
  • 为某个函数单独开启严格模式
function fun(){
	'use strict'
}//只为fun函数开启严格模式
1
2
3

严格模式的变化:

  • 变量规定
    • 变量不声明就赋值会报错;
    • 不能删除已经定义好的变量
var num = 20
delete num //不行
1
2
  • 严格模式下this的指向问题

    • 以前在全局作用域函数中的this指向window对象;严格模式下全局作用域中函数的thisundefined
    • 严格模式下,如果不加new调用,this会报错
    • 严格模式下,定时器指向的还是window
  • 函数变化

    • 函数里不允许有重名的参数
    • 函数必须生命在顶层,为了与新版本接轨,不能声明在没有作用域的花括号中(比如if的花括号,for的花括号等);但是函数的花括号里可以,因为函数花括号有作用域;

# 10.typeof 共返回6种数据格式:

1、object

2、undefined

3、string

4、number

5、boolean

6、function

**特别注意Array和null返回的都是object **

# 11.js面试题之手写节流函数和防抖函数

函数节流:不断触发一个函数后,执行第一次,只有大于设定的执行周期后才会执行第二次

/* 
	节流函数:fn:要被节流的函数,delay:规定的时间
 */
function throttle(fn, delay){
    // 记录上一次函数出发的时间
    var lastTime = 0
    return function(){
        // 记录当前函数触发的时间
        var nowTime = new Date().getTime()
        // 当当前时间减去上一次执行时间大于这个指定间隔时间才让他触发这个函数
        if(nowTime - lastTime > delay){
            // 绑定this指向
            fn.call(this)
            //同步时间
            lastTime = nowTime
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

函数防抖:不断触发一个函数,在规定时间内只让最后一次生效,前面都不生效

function debounce(fn,delay){
           var timer = null
        //  清除上一次延时器
          return function(){
               clearTimeout(timer)
              //  重新设置一个新的延时器
              timer = setTimeout(() => {
                  fn.call(this)
              }, delay);
          }
       }
1
2
3
4
5
6
7
8
9
10
11

# 12.innerHTML和innerText的区别

innerText属性

document.getElementById('box').innerText; //获取文本内容(如有html 直接过滤掉)
document.getElementById('box').innerText = '<div>Mr.Lee</div>'; //设置文本(如有html会进行转义)
1
2

innerHTML属性

document.getElementById('box').innerHTML; //获取文本(不过滤HTML)
document.getElementById('box').innerHTML = '<b>123</b>'; //可解析成HTML
1
2

# 13.!DOCTYPE html

<!DOCTYPE html>

  • HTML文档声明,告诉浏览器当前页面是HTML5页面,让浏览器用HTML5的标准去解析识别HTML文档

  • 必须放在HTML文档的最前面,不能省略,省略了会出现兼容性问题

最后更新于: 2020年6月13日星期六晚上7点32分