前端知识总结之基础知识

文章目录

1.原始类型有哪几种?null 是对象嘛?

原始类型即为基本数据类型:(6种)

  • null
  • undefined
  • string
  • number
  • boolean
  • symbol

基本数据类型存储的都是值,没有函数可以调用(例undefined.toString()

基本数据类型存在的一些bug

  1. number 为浮点类型
    在使用中会遇到某些 Bug,比如 0.1 + 0.2 !== 0.3
  2. string 类型是不可变的
    无论你在 string 类型上调用何种方法,都不会对值有改变。

null 不是对象类型

虽然 typeof null 会输出 object,但是这只是 JS 存在的一个悠久 Bug。在 JS 的最初版本中使用的是 32 位系统,为了性能考虑使用低位存储变量的类型信息,000 开头代表是对象,然而 null 表示为全零,所以将它错误的判断为 object 。虽然现在的内部类型判断代码已经改变了,但是对于这个 Bug 却是一直流传下来。

2. 对象类型和原始类型的不同之处?函数参数是对象会发生什么问题?

基本数据类型为栈存储吗,基本数据类型为堆存储

  • 基本数据类型可以操作保存在变量中实际的值(按值访问)
  • 引用数据类型的值是保存在内存中的对象,实际上是在操作对象的引用而不是实际的对象(按引用访问:当复制保存某个对象的变量时,实际上操作的是对象的引用,但是在为对象添加属性时,操作的是实际的对象)
  1. 如果对象不被销毁或者属性不被删除,则这个属性将一直保存‘’
  2. 不能给基本类型的值添加属性(只能给引用类型值动态添加属性)

函数参数的传递(按值传递)

  • 基本类型值的传递,如同基本类型的变量复制一样
    被传递的值会被赋给一个局部变量,即arguments 对象中一个元素
  • 引用类型值的传递,如同引用类型的变量复制一样
    会把这个值在内存中的地址复制给一个局部变量,因此这个局部变量的变化会反映在函数的外部
    function test(person) {
        person.age = 26
        person = {
            name: 'yyy',
            age: 30
        }

        return person
    }
    const p1 = {
        name: 'yck',
        age: 25
    }
  //  console.log(p1)// {name: "yck", age: 25}
  //  console.log(test(p1))  //{name: "yyy", age: 30}
    const p2 = test(p1)
    console.log(p1) // yck 26
    console.log(p2) // yyy 30
  1. 首先要知道 p1为一个全局对象,所以当被赋值为26 时,则全局的p1.age也会被改了
  2. 当为p1 重新分配一个对象时,就会重新建立引用,则得到p2的值

3. typeof 是否能正确判断类型?instanceof 能正确判断对象的原理是什么?

typeof 并不能准确判断变量到底是什么类型
  1. 对于基本数据类型来说,除了 null 都可以显示正确的类型
typeof 1 // 'number'
typeof '1' // 'string'
typeof undefined // 'undefined'
typeof true // 'boolean'
typeof Symbol() // 'symbol'
  1. typeof 对于对象来说,除了函数都会显示 object
typeof [] // 'object'
typeof {} // 'object'
typeof console.log // 'function'
instanceof 能判断一个对象的正确类型,因为内部机制是通过原型链来判断的
const Person = function() {}
const p1 = new Person()
p1 instanceof Person // true

var str = 'hello world'
str instanceof String // false

var str1 = new String('hello world')
str1 instanceof String // true  string对象

对于原始类型来说,你想直接通过 instanceof 来判断类型是不行的,当然我们还是有办法让 instanceof 判断原始类型的

class PrimitiveString {
  static [Symbol.hasInstance](x) {
    return typeof x === 'string'
  }
}
console.log('hello world' instanceof PrimitiveString) // true

Symbol.hasInstance,是一个能让我们自定义 instanceof 行为的东西,以上代码等同于 typeof ‘hello world’ === ‘string’,所以结果自然是 true 了。这其实也侧面反映了一个问题, instanceof 也不是百分之百可信的。

4. 类型转换

在 JS 中类型转换只有三种情况,分别是:

  • 转换为布尔值
  • 转换为数字
  • 转换为字符串

类型转换规则

1. 转换为boolean

在条件判断时,除了 undefined, null, false, NaN, ‘’, 0, -0,其他所有值都转为 true,包括所有对象。

2. 对象转基本数据类型

调用内置的[[ToPrimitive]]函数,对于该函数来说,算法逻辑一般来说如下:

  • 如果已经是基本数据类型了,就不需要转换了
  • 调用x.valueOf()x.toString()

重写 Symbol.toPrimitive ,该方法在转基本数据类型时调用优先级最高。

let a = {
  valueOf() {
    return 0
  },
  toString() {
    return '1'
  },
  [Symbol.toPrimitive]() {
    return 2
  }
}
1 + a // => 3

在这里总结一下对于引用数据类型的toString和valueof 方法的结果:

引用数据类型toStringtolocalStringvalueof
Array返回数组中每个值的字符串形式拼接成的一个以逗号分隔的字符串创建一个数组值以逗号分隔的字符串返回数组
Date返回带有时区信息的日期和时间按照与浏览器设置的地区响应的格式返回日期和时间不返回字符串,返回日期的毫秒表示
ReqExp返回正则表达式的字面量,与创建正则表达式的方式无关返回正则表达式的字面量,与创建正则表达式的方式无关返回正则表达式本身
function返回函数代码返回函数代码返回函数代码

总结一下基本包装类型的toString和valueof 方法的结果:
基本包装类型
没当读取一个基本类型值的时候,后台就会创建一个对应的基本包装类型的对象,从而能够调用一些方法去操作这些数据。
基本包装类型的实例调用typeof 时返回“object”

引用类型和基本包装类型的区别
对象的生存期

  • 使用new 操作符创建的引用类型的实例,在执行流离开当前作用域的之前都一直保存在内存中
  • 自动创建的基本包装类型对象,只存在于一行代码的执行瞬间,然后立即被销毁(表明不能在运行时为基本类型值添加属性和方法)
基本包装类型toStringtolocalStringvalueof
Boolean"true" "false"字符串true,false 的基本类型值
Number字符串形式的值字符串形式的值数值(基本类型)
String基本字符串值基本字符串值基本字符串值

3. 四则运算符

  • 运算中其中一方为字符串,那么就会把另一方也转换为字符串
  • 如果一方不是字符串或者数字,那么会将它转换为数字或者字符串
1 + '1' // '11' 转换为字符串
true + true // 2
4 + [1,2,3] // "41,2,3" 转换为字符串1,2,3

另外对于加法还需要注意这个表达式'a' + + 'b'

'a' + + 'b' // -> "aNaN"

因为+ 'b'==NaN 所以结果为aNaN
在一些代码中通过 + ‘1’ 的形式来快速获取 number 类型。
那么对于除了加法的运算符来说,只要其中一方是数字,那么另一方就会被转为数字

4 * '3' // 12
4 * [] // 0
4 * [1, 2] // NaN

4. 比较运算符

  1. 如果是对象,就通过 toPrimitive 转换对象
  2. 如果是字符串,就通过 unicode 字符索引来比较
let a = {
  valueOf() {
    return 0
  },
  toString() {
    return '1'
  }
}
a > -1 // true

在以上代码中,因为 a 是对象,所以会通过 valueOf 转换为原始类型再比较值。


装箱转换(把基本类型转换为对应的对象)

使用内置的object 函数,可以在js 内部代码中显式调用装箱能力

    var symbolObject = Object(Symbol("a"));

    console.log(typeof symbolObject); //object
    console.log(symbolObject instanceof Symbol); //true
    console.log(symbolObject.constructor == Symbol); //true

每个对象都有class 属性,这些属性可以通过 Object.prototype.toString 获取

    var symbolObject = Object(Symbol("a"));

    console.log(Object.prototype.toString.call(symbolObject)); //[object Symbol]

在 JavaScript 中,没有任何方法可以更改私有的 class 属性,因此
Object.prototype.toString 是可以准确识别对象对应的基本类型的方法,比instanceof 更加准确

但需要注意的是,call 本身会产生装箱操作,所以需要配合 typeof 来区分基本类型还是对象类型

拆箱转换(规定ToPrimitive函数,对象类型到基本类型)

对象到String 和 Number 的转换都遵循“先拆箱再转换”的规则,通过拆箱转换,把对象变成基本类型,再从基本类型转换为对于的String 和 Number

拆箱转换会尝试调用valueOf 和toString 来获得拆箱后的基本类型,如果 valueOf 和toString 都不存在,或者没有返回基本类型,则会产生错误 TypeError

    var o = {
        valueOf : () => {console.log("valueOf"); return {}},
        toString : () => {console.log("toString"); return {}}
    }

    o * 2
    // valueOf
    // toString
    // TypeError

可以看到这个对象o 在进行o*2 运算的时候,先执行了了valueOf 然后执行了toString 最后抛出了错误,说明这个拆箱转换失败了。

string的拆箱转换会优先调用toString

    var o = {
        valueOf : () => {console.log("valueOf"); return {}},
        toString : () => {console.log("toString"); return {}}
    }

   String(o)
    // toString
    // valueOf
    // TypeError

ES6中还允许对象通过显示指定@@toPrimitive Symbol 来覆盖原有的行为。

    var o = {
        valueOf : () => {console.log("valueOf"); return {}},
        toString : () => {console.log("toString"); return {}}
    }

    o[Symbol.toPrimitive] = () => {console.log("toPrimitive"); return "hello"}


    console.log(o + "")
    // toPrimitive
    // hello

5. 如何正确判断 this?箭头函数的 this 是什么?

function foo() {
  console.log(this.a)
}
var a = 1
foo()

const obj = {
  a: 2,
  foo: foo
}
obj.foo()

const c = new foo()
  • 对于直接调用 foo 来说,不管 foo 函数被放在了什么地方,this 一定是 window
  • 对于 obj.foo() 来说,我们只需要记住,谁调用了函数,谁就是 this,所以在这个场景下 foo 函数中的 this 就是 obj 对象
  • 对于 new 的方式来说,this 被永远绑定在了 c 上面,不会被任何方式改变 this

new 运算接受一个构造器和一组调用参数,实际上做的事情

  1. 以构造器的prototype 属性为原型,创建新对象
  2. 将this 和调用参数传给构造器
  3. 如果构造器返回的是对象,则返回第一步创建的对象

new 这样的行为,在客观上提供了两种方式:

  • 在构造器中添加属性
  • 在构造器中的prototype 的属性中添加属性
  1. 直接在构造器中修改了this ,给this 添加属性:

function c1(){
    this.p1 = 1;
    this.p2 = function(){
        console.log(this.p1);
    }
} 
var o1 = new c1;
o1.p2();



function c2(){
}
c2.prototype.p1 = 1;
c2.prototype.p2 = function(){
    console.log(this.p1);
}

var o2 = new c2;
o2.p2();


  1. 修改构造器中的prototype 属性指向的对象,他是从这个构造器构造出来的所有对象的原型:

箭头函数的this

首先箭头函数其实是没有 this 的,箭头函数中的 this 只取决包裹箭头函数的第一个普通函数的 this。

function a() {
  return () => {
    return () => {
      console.log(this)
    }
  }
}
console.log(a()()())

在这个例子中,因为包裹箭头函数的第一个普通函数是 a,所以此时的 this 是 window。另外对箭头函数使用 bind 这类函数是无效的

如果对一个函数进行多次 bind,那么上下文会是什么呢?

let a = {}
let fn = function () { console.log(this) }
fn.bind().bind(a)() // => ?

可以转换为如下形式:

// fn.bind().bind(a) 等于
let fn2 = function fn1() {
  return function() {
    return fn.apply()
  }.apply(a)
}
fn2()

可以从上述代码中发现,不管我们给函数 bind 几次,fn 中的 this 永远由第一次 bind 决定,所以结果永远是 window。

let a = { name: 'yck' }
function foo() {
  console.log(this.name)
}
foo.bind(a)() // => 'yck'

发生多个规则同时出现的情况,这时候不同的规则之间会根据优先级最高的来决定 this 最终指向哪里。

  1. new
  2. bind
  3. obj.foo()
  4. foo 的调用方式

同时箭头函数的this 一旦被绑定,就不会被任何方式再改变

在这里插入图片描述

6. == 和===有什么区别?

  • 对于 == 来说,如果对比双方的类型不一样的话,就会进行类型转换
  • 对于 === 来说,首先会进行类型判定,如果相同则再进行判定值

假如我们需要对x和y判断是否相同,需要以下流程

  1. 首先会判断两者类型是否相同。相同的话就是比大小了
  2. 类型不相同的话,那么就会进行类型转换
  3. 会先判断是否在对比 null 和 undefined,是的话就会返回 true
  4. 判断两者类型是否为 string 和 number,是的话就会将字符串转换为 number
  5. 判断其中一方是否为 boolean,是的话就会把 boolean 转为 number 再进行判断
  6. 判断其中一方是否为 object 且另一方为 string、number 或者 symbol,是的话就会把 object 转为原始类型再进行判断

对于 [] == ![]

console.log(typeof([]==![]))//boolean

在这里插入图片描述

7. 什么是闭包?

有权访问函数作用域变量的函数

		function out() {
            var num = 1;
           return function  (n){
                return (n + num)
            }
        }
        var a = out()
        console.log(a(1)) //2
        console.log(out()(2))  //3
		function outer() {
            var num = 1;
            function inner () {
                var n = 2;
                alert(n + num)  
            }
            return inner;
        }
        outer()();//3

使用闭包解决 var 定义函数的问题

for (var i = 1; i <= 5; i++) {
  setTimeout(function timer() {
    console.log(i) //结果输出5个6
  }, i * 1000)
}

因为 setTimeout 是个异步函数,所以会先把循环全部执行完毕,这时候 i 就是 6 了,所以会输出一堆 6。

  1. 使用闭包的方式
for (var i = 1; i <= 5; i++) {
  ;(function(j) {
    setTimeout(function timer() {
      console.log(j)  // 12345
    }, j * 1000)
  })(i)
}
  1. 第二种就是使用 setTimeout 的第三个参数,这个参数会被当成 timer 函数的参数传入。
for (var i = 1; i <= 5; i++) {
  setTimeout(
    function timer(j) {
      console.log(j)  //逐步输出12345
    },
    i * 1000,
    i
  )
}
  1. 使用let
for (let i = 1; i <= 5; i++) {
  setTimeout(function timer() {
    console.log(i) //12345
  }, i * 1000)
}

8. 什么是浅拷贝?如何实现浅拷贝?什么是深拷贝?如何实现深拷贝?

浅拷贝解决引用数据类型的问题(只有一层),深拷贝解决值中还存在对象的问题。

浅拷贝 Object.assign

对于引用数据类型会存在下面的问题(解决办法:浅拷贝)

let a = {
  age: 1
}
let b = a
a.age = 2
console.log(b.age) // 2
  1. Object.assign 只会拷贝所有的属性值到新的对象中,如果属性值是对象的话,拷贝的是地址,所以并不是深拷贝。
let a = {
  age: 1
}
let b = Object.assign({}, a)
a.age = 2
console.log(b.age) // 1
  1. 通过展开运算符... 来实现浅拷贝
let a = {
 age: 1
}
let b = { ...a }
a.age = 2
console.log(b.age) // 1

浅拷贝只解决了第一层的问题,如果接下去的值中还有对象的话,那么就又回到最开始的话题了,两者享有相同的地址。要解决这个问题,我们就得使用深拷贝了。

深拷贝JSON.parse(JSON.stringify(object))

拷贝值中存在对象时,需要进行深拷贝

let a = {
      age: 1,
      jobs: {
        first: 'FE'
      }
    }
    let b = a
    a.jobs.first = 'native'
    console.log(b.jobs.first) // native
let a = {
  age: 1,
  jobs: {
    first: 'FE'
  }
}
let b = JSON.parse(JSON.stringify(a))
a.jobs.first = 'native'
console.log(b.jobs.first) // FE

局限性:

  • 会忽略 undefined
  • 会忽略 symbol
  • 不能序列化函数
  • 不能解决循环引用的对象

如果你所需拷贝的对象含有内置类型并且不包含函数,可以使用 MessageChannel

9. 如何理解原型?如何理解原型链?

每个 JS 对象都有 __proto__ 属性,这个属性指向了原型,浏览器在早期为了让我们访问到内部属性 [[prototype]]来实现的一个东西。

原型也是一个对象,并且这个对象中包含了很多函数,所以我们可以得出一个结论:对于 obj 来说,可以通过 __proto__ 找到一个原型对象,在该对象中定义了很多函数让我们来使用。

在这里插入图片描述
在上面的图中我们还可以发现一个 constructor 属性,也就是构造函数原型的 constructor 属性指向构造函数,构造函数又通过 prototype 属性指回原型
Function.prototype.bind() 就没有这个属性。

原型链就是多个对象通过 __proto__ 的方式连接了起来

为什么 obj 可以访问到 valueOf 函数,就是因为 obj 通过原型链找到了 valueOf 函数。

总结:

  • object 是所有对象的爸爸,所有对象都可以通过__proto__找到它
  • Function 是所有函数的爸爸,所有函数都可以通过 __proto__找到它
  • 函数的 prototype 是一个对象
  • 对象的 __proto__ 属性指向原型,__proto__将对象和原型连接起来组成了原型链
  • 原型的 constructor 属性指向构造函数,构造函数又通过 prototype 属性指回原型
  • 3
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值