深入了解JavaScript底层原理

栏目: JavaScript · 发布时间: 7年前

内容简介:变量对象 (缩写为VO)就是与执行上下文相关的对象,它存储下列内容:激活对象是函数上下文里的激活对象AO中的内部对象,它包括下列属性:循环中使用闭包解决 var 定义函数的问题
基本类型: 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)就是与执行上下文相关的对象,它存储下列内容:

  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,所以在第二个阶段,也就是代码执行阶段,我们可以直接提前使用。

  • 在提升的过程中,相同的函数会覆盖上一个函数,并且函数优先于变量提升

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。
复制代码

解决办法

第一种使用闭包

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。复制代码

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Iterative Methods for Sparse Linear Systems, Second Edition

Iterative Methods for Sparse Linear Systems, Second Edition

Yousef Saad / Society for Industrial and Applied Mathematics / 2003-04-30 / USD 102.00

Tremendous progress has been made in the scientific and engineering disciplines regarding the use of iterative methods for linear systems. The size and complexity of linear and nonlinear systems arisi......一起来看看 《Iterative Methods for Sparse Linear Systems, Second Edition》 这本书的介绍吧!

随机密码生成器
随机密码生成器

多种字符组合密码

HTML 编码/解码
HTML 编码/解码

HTML 编码/解码

html转js在线工具
html转js在线工具

html转js在线工具