这是前端最基础的问题,也是在面试中难倒无数同学的经典问题
01. Javascript 数据类型
Javascript 数据类型 = 基本类型 + 引用类型
ES6 之前 5 + 1 = 6 种
ES6 之后 6 + 1 = 7 种
注:基本类型共6种:Number 数值型, String 字符型, Boolean 布尔型, Null 空, Undefind 未定义, Symbol 符号型, 其中Symbol是ES6新增的。
引用类型只有1种:Object 对象,注意:Function 和 Array 都继承于Object。
02. Javascript 类型判断
Javascript 类型判断主要有三种方法
① typeof
最大的问题是判断数组和null等数据类型时,无法获得预期的结果。
② instanceof
用于判断引用类型的继承关系,如:判断Function是否继承于object
最大的问题是不支持基本类型判断
③ Object.prototype.toString.call()
类型判断的最佳实践,使用Object原型上的toString方法,这种方法可以精确的判断各种数据类型。
03. Javascript 函数调用
① 直接调用
function test(msg) {
console.log(msg)
}
test ('Hello word !')
这种方法最为常用,使用括号即可调用函数。
② 方法调用
const f = {
a: 1,
b(){ console.log(this.a) }
}
f.b() // 1
当函数被保存为对象的一个属性时,称为方法。当它被调用时this将绑定到该对象。
③ 构造函数调用
function test() {
this.a = 1
}
const o = new test()
console.log(o.a) // 1
当使用new来调用函数时,会创建一个新对象,函数内部的this会绑定到新对象。
④ call 和 apply 调用
Object.prototype.toString.call({}) // [object Object]
Object.prototype.toString.apply([]) // [object Array]
函数也是一个对象,可以拥有方法,call 和 apply 就是函数的方法,任何函数都包含call 和 apply这两个方法,调用它们可以执行函数并改变this的指向。
04. Javascript 函数调用 bind call 和 apply 的区别
① bind
function test() {
// this默认指向window
console.log(this.a)
}
test.bind({a: 1}) // 返回一个新函数
bind 的作用是改变函数的 this 指向,通过 bind 将 this 绑定到新对象,并返回一个新的函数。
② call
function test(b,c) {
console.log(this.a, b, c)
}
test.call({a: 1}, 2, 3) // 1 2 3
call 的作用是执行函数并改变this指向,既然是执行函数,所以需要传入执行参数,call传参的方式是依次传入,用逗号分隔。
③ apply
function test(b,c) {
console.log(this.a, b, c)
}
test.apply({a: 1}, [2, 3]) // 1 2 3
apply 的作用和 call 类似,只是 传参方式不同, apply 将参数全部放置到一个数组当中。
05. Javascript 变量提升
① 基本概念:变量可以后定义先使用。
a = 2
console.log(a) // 2
var a = 1
console.log(a) // 1
如 后定义了变量 a,但可以在之前使用,是因为变量 a 的定义过程被提前了。
② 函数同样支持变量提升,可以使用后定义的提升
test() // 123
function test() {
console.log('123)
}
注意:字面量定义的函数不支持变量提升
test() // error:test is not a function
var test = function() {
console.log('123)
}
因为 test 变量会进行提升,并且默认值为 undefined。只有执行到赋值语句时,test 才会 变成一个函数。
06. Javascript 作用域
主要分三种,常用的是全局作用域和函数作用域
① 全局作用域
var a = 123
function test() {
console.log(a)
}
test() // 123
全局作用域的变量,在函数中是可以被访问的,是因为作用域链在起作用,函数内部查找作用域没有找到变量 a 后,会到它的父级作用域,即全局作用域去查找。
② 函数作用域
function test() {
var a = 123
}
console.log(a) // undefined
函数作用域的变量,在全局作用域中是无法被访问的,要解决这个问题要通过闭包。
解决方法:
function test() {
var a = 123
return function() {
return {
a
}
}
}
console.log(test()().a) // 123
在函数中返回一个函数会形成一个闭包,通过闭包我们可以访问到函数作用域下的变量。
07. Javascript 异常处理
分为被动触发和主动抛出
① // Uncaught ReferenceError: a is not defined
console.log(a)
上面的代码由于没有定义变量 a , 所以会触发未定义错误,属于被动触发。
② // Uncaught Error: crash
throw new Error('crash')
主动抛出需要使用 throw 关键字,后面需要实例化一个 Error 对象。
③ 如果希望主动捕获异常,可以通过 try catch ,
try {
console.log(a)
} catch(err) {
console.error(err)
}
通过 try 捕获异常,catch 处理异常。
08. Javascript 原型
术语:1. 实例(对象):通过构造函数创建出来的对象叫做实例(对象) 注:实例对象可以有多个
2. 实例化:构造函数创建出来的对象过程
3. 成员:对象的属性和方法的一个统称
一. prototype
① 任何一个函数都有 prototype 属性
function Person() {} // 构造函数
console.dir(Person) // ƒ Person() > prototype: {constructor: ƒ}
② 函数的 prototype 属性值是一个对象,这个对象叫做原型 (或叫做原型对象)
function Person() {} // Person 是函数也是一个对象
console.log(Person.prototype) // {constructor: ƒ} // 这个就是原形对象
③ 作用:通过构造函数创建的实例对象 可以直接访问这个构造函数的 prototype 属性 上的任意成员。
p 原型
function Person() {}
console.log(Person.prototype) // 原型对象
var p = new Person() // 这就是通过构造函数创建的实例对象
// 现在给原型对象添加 .color 属性 值为 lime
o.fn() // 1
该案例中共定义了 3 个 a ,从直觉上来说 o.fn 函数执行后,应该获取 o.a ,即打印 2 ,但实际结果打印了 1 ,是因为 test 创建于全局,所以它的 this 仍然指向 window ,这与它的调用环境无关。