JavaScript基础原理

[TOC]es6

1. 七种内置类型

基本类型: null,undefined,boolean,number(浮点类型),string,symbol(es6)。
对象:Object。
复制代码

类型转换

  • typeof:
typeof 1 // 'number'
typeof '1' // 'string'
typeof undefined // 'undefined'
typeof true // 'boolean'
typeof Symbol() // 'symbol'
typeof b // b 没有声明,可是还会显示 undefined
typeof []  // 'object'
typeof {}  // 'object typeof null // 'object' typeof console.log // 'function' 复制代码
  • valueOf面试

    对象在转换基本类型时,首先会调用 valueOf 而后调用 toString。而且这两个方法你是能够重写的。浏览器

let a = {
    valueOf() {
    	return 0
    toString() {
    return '1';
  },
// Symbol.toPrimitive ,该方法在转基本类型时调用优先级最高。
  [Symbol.toPrimitive]() {
    return 2;
  }
}
1 + a // => 3
'1' + a // => '12'
复制代码
  • 比较运算符
若是是对象,就经过 toPrimitive 转换对象
若是是字符串,就经过 unicode 字符索引来比较
复制代码

四则运算

只有当加法运算时,其中一方是字符串类型,就会把另外一个也转为字符串类型。
其余运算只要其中一方是数字,那么另外一方就转为数字。
而且加法运算会触发三种类型转换:将值转换为原始值,转换为数字,转换为字符串。
复制代码
1 + '1' // '11'
2 * '2' // 4
[1, 2] + [2, 1] // '1,22,1'
// [1, 2].toString() -> '1,2'
// [2, 1].toString() -> '2,1'
// '1,2' + '2,1' = '1,22,1'

// 对于加号须要注意这个表达式 'a' + + 'b'
'a' + + 'b' // -> "aNaN"
// 由于 + 'b' -> NaN
复制代码

冷知识

  • NaN 属于 number 类型,而且 NaN 不等于自身。
  • undefined 不是保留字,可以在低版本浏览器被赋值 let undefined = 1

2. 实例对象

new

  • 在调用 new 的过程当中会发生以上四件事情
// 新生成了一个对象
// 连接到原型
// 绑定 this
// 返回新对象

function new() {
    // 建立一个空的对象
    let obj = new Object()
    // 得到构造函数
    let Con = [].shift.call(arguments)
    // 连接到原型
    obj.__proto__ = Con.prototype
    // 绑定 this,执行构造函数
    let result = Con.apply(obj, arguments)
    // 确保 new 出来的是个对象
    return typeof result === 'object' ? result : obj
}
复制代码
  • 执行优先级
function Foo() {
    return this;
}
Foo.getName = function () {
    console.log('1');
};
Foo.prototype.getName = function () {
    console.log('2');
};

new Foo.getName();   // -> 1
new Foo().getName(); // -> 2

// new Foo() 的优先级大于 new Foo
复制代码
new (Foo.getName());
(new Foo()).getName();

// 对于第一个函数来讲,先执行了 Foo.getName() ,因此结果为 1;
// 对于后者来讲,先执行 new Foo() 产生了一个实例,
// 而后经过原型链找到了 Foo 上的 getName 函数,因此结果为 2。
复制代码

this

  • 通用规则 new有最高优先级,利用 call,apply,bind 改变 this,优先级仅次于 new。
function foo() {
	console.log(this.a)
}
var a = 1
foo()

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

// 以上二者状况 `this` 只依赖于调用函数前的对象,优先级是第二个状况大于第一个状况

// 如下状况是优先级最高的,`this` 只会绑定在 `c` 上,不会被任何方式修改 `this` 指向
var c = new foo()
c.a = 3
console.log(c.a)

// 还有种就是利用 call,apply,bind 改变 this,这个优先级仅次于 new
复制代码
  • 箭头函数实际上是没有 this 的,这个函数中的 this 只取决于他外面的第一个不是箭头函数的函数的 this。在这个例子中,由于调用 a 符合前面代码中的第一个状况,因此 this 是 window。而且 this 一旦绑定了上下文,就不会被任何代码改变。

冷知识

  • instanceof 能够正确的判断对象的类型,由于内部机制是经过判断对象的原型链中是否是能找到类型的 prototype。

3. 执行上下文

  1. 全局执行上下文
  2. 函数执行上下文
  3. eval 执行上下文

属性 VO & AO

变量对象 (缩写为VO)就是与执行上下文相关的对象,它存储下列内容:bash

  1. 变量 (var, VariableDeclaration);
  2. 函数声明 (FunctionDeclaration, 缩写为FD);
  3. 函数的形参
  • 只有全局上下文的变量对象容许经过VO的属性名称间接访问(由于在全局上下文里,全局对象自身就是一个VO(稍后会详细介绍)。在其它上下文中是不可能直接访问到VO的,由于变量对象彻底是实现机制内部的事情。当咱们声明一个变量或一个函数的时候,同时还用变量的名称和值,在VO里建立了一个新的属性。

激活对象是函数上下文里的激活对象AO中的内部对象,它包括下列属性:闭包

  1. callee — 指向当前函数的引用;
  2. length —真正传递的参数的个数;
  3. properties-indexes(字符串类型的整数)
  • 属性的值就是函数的参数值(按参数列表从左到右排列)。 properties-indexes内部元素的个数等于arguments.length. properties-indexes 的值和实际传递进来的参数之间是共享的。(译者注:共享与不共享的区别能够对比理解为引用传递与值传递的区别)

属性 this&做用域链

b() // call b
console.log(a) // undefined

var a = 'Hello world'

function b() {
	console.log('call b')
}
复制代码
  • 以上众所周知由于函数和变量提高的缘由。一般提高的解释是说将声明的代码移动到了顶部。可是更准确的解释应该是:在生成执行上下文时,会有两个阶段。第一个阶段是建立的阶段(具体步骤是建立 VO),JS解释器会找出须要提高的变量和函数,而且给他们提早在内存中开辟好空间,函数的话会将整个函数存入内存中,变量只声明而且赋值为 undefined,因此在第二个阶段,也就是代码执行阶段,咱们能够直接提早使用。app

  • 在提高的过程当中,相同的函数会覆盖上一个函数,而且函数优先于变量提高异步

b() // call b second

function b() {
	console.log('call b fist')
}
function b() {
	console.log('call b second')
}
var b = 'Hello world'
复制代码
  • 对于非匿名的当即执行函数须要注意如下一点
var foo = 1
(function foo() {
    foo = 10
    console.log(foo)
}()) // -> ƒ foo() { foo = 10 ; console.log(foo) }
// 内部独立做用域,不会影响外部的值
复制代码

一个面试题

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

for ( var i=1; i<=5; i++) {
	setTimeout( function timer() {
		console.log( i );
	}, i*1000 );
}
// 由于 setTimeout 是个异步函数,全部会先把循环所有执行完毕,这时候 i 就是 6 了,因此会输出一堆 6。
复制代码

解决办法post

第一种使用闭包学习

for (var i = 1; i <= 5; i++) {
  (function(j) {
    setTimeout(function timer() {
      console.log(j);
    }, j * 1000);
  })(i);
}
复制代码

第二种就是使用 setTimeout 的第三个参数

for ( var i=1; i<=5; i++) {
	setTimeout( function timer(j) {
		console.log( j );
	}, i*1000, i);
}
// 第三个参数及之后的参数均可以做为func函数的参数,例:
function a(x, y) {
    console.log(x, y) // 2 3
}
setTimeout(a, 1000, 2, 3)
复制代码

第三种就是使用 let 定义 i 了

for ( let i=1; i<=5; i++) {
	setTimeout( function timer() {
		console.log( i );
	}, i*1000 );
}
复制代码

由于对于 let 来讲,他会建立一个块级做用域,至关于

{ // 造成块级做用域
  let i = 0
  {
    let ii = i
    setTimeout( function timer() {
        console.log( i );
    }, i*1000 );
  }
  i++
  {
    let ii = i
  }
  i++
  {
    let ii = i
  }
  ...
}
复制代码

4. 深浅拷贝

浅拷贝

  • 经过 Object.assign
let a = {
    age: 1
}
let b = Object.assign({}, a)
a.age = 2
console.log(b.age) // 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 = JSON.parse(JSON.stringify(a))
a.jobs.first = 'native'
console.log(b.jobs.first) // FE
复制代码

该方法也是有局限性的:会忽略 undefined,忽略函数,不能解决循环引用的对象

let obj = {
  a: 1,
  b: {
    c: 2,
    d: 3,
  },
}
obj.c = obj.b
obj.e = obj.a
obj.b.c = obj.c
obj.b.d = obj.b
obj.b.e = obj.b.c
let newObj = JSON.parse(JSON.stringify(obj)) // 会报错
console.log(newObj)
复制代码
  • 若是你的数据中含有以上三种状况下,经过 lodash 的深拷贝函数,或者使用 MessageChannel
function structuralClone(obj) {
  return new Promise(resolve => {
    const {port1, port2} = new MessageChannel();
    port2.onmessage = ev => resolve(ev.data);
    port1.postMessage(obj);
  });
}

var obj = {a: 1, b: {
    c: b
}}
// 注意该方法是异步的
// 能够处理 undefined 和循环引用对象
const clone = await structuralClone(obj);
复制代码
文章为学习笔记,整理自面谱InterviewMap。复制代码
相关文章
相关标签/搜索