[JS]08.变量提升&类型转换例子

232 阅读5分钟

1. 变量提升例子

1.1 eg1

  • 形成全局执行上下文EC(G)
  • 词法分析,代码执行之前
  • 变量提升(当前上下文中带var、function提前声明定义)
  • var a; var b; var c; var只声明不定义,全局上下文基于var和function,同时也在window中设置对应属性
  • fn = 0x000; 创建堆内存中存放代码串
  • 代码执行阶段(变量提升阶段做过的事情,代码执行阶段不重复执行,会忽略)
console.log(a, b, c);   // undefined undefined undefined
var a = 12,
    b = 13,
    c = 14,
function fn(a) {    // fn变量提升时候已经声明+定义,代码执行阶段跳过此阶段
  /*
    私有上下文
    初始化作用域链:<EC(fn), EC(G)>
    形参赋值:a = 10
    变量提升:无var和function
    代码执行
  */
  console.log(a, b, c);  // a私有的10  b和c到EC(G)中找 13  14
  a = 100;  // 私有 a = 100
  c = 200;  // 全局 c = 200
  console.log(a, b, c);  // 100 13 200
  
  // 函数执行完成后无返回值,默认返回undefined
  // 当前上下文没有被外部占用,出栈释放
}
b = fn(10);  // 先把函数执行,返回结果赋值给b  b = undefined
console.log(a, b, c);  // 12  undefined 200

1.2 eg2

  • 全局上下文下,变量提升
  • var i; A=0X000; [[scope]]:EC(G) 作用域是全局
  • var y; B=0x001; [[scope]]:EC(G)
  • 代码执行 i = 0;
var i = 0;

function A() {
  /*
    EC(A1)
    作用域链<EC(A1), EC(G)>
    形参赋值:无
    变量提升:var i; x = 0x100 [[scope]]:EC(A1) 作用域是A1
    执行 i = 10
  */
  var i = 10;
  function x() {
    console.log(i)
  }
  return x;  // return 0x100  
  
  // 0x100被全局的y占用,不能被释放,闭包,被保留
}

var y = A();  // A()的返回结果给y = 0x100
y();  // 10

function B() {
  var i = 20;
  y();
}
B();  // 10

函数执行,它的上级作用域(上下文)是谁,和函数在哪执行是没有关系的,只和在哪创建有关系,在哪创建的,它的[[scope]]就是谁,也就是它的上下文就是谁

1.3 eg3

  • 变量提升:
    • var a;
    • var obj;
    • fn = 0x000; [[scope]]: EC(G) 不管fn在哪执行,fn的上级上下文永远是全局作用域
  • 代码执行
var a = 1;
var obj = {   // 0bj = 0x001 对象开堆内存
  name: 'tom'
};

function fn() {
  /*
    fn执行形成私有上下文
    作用域链 <EC(fn), EC(G)>
    形参赋值:无
    变量提升:var a2;  // 只是有a2是私有的, a/obj2/obj都是全局的
    代码执行
    没有被占用,释放
  */
  var a2 = a;  // a2 = 1
  obj2 = obj;  // 当前上下文,全局都没有0bj2,给winndow加了obj2,obj2 = 0x001
  a2 = a;   // a2 = 1
  obj2.name = 'jack';  // 0x001.name = 'jack'
}
fn();  // fn执行
console.log(a);  // 1
console.log(obj);  // {name: 'jack'}

1.4 eg4

  • 变量提升
    • var a;
    • fn = 0x000; [[scope]]:EC(G)
  • 代码执行
var a = 1;    // a = 1
function fn(a) {
  /*
    EC(fn)
    初始化作用域链<EC(Fn), EC(G)>
    形参赋值 a = 1
    变量提升 
      var a; (已经有形参a,a变量已经存在AO(Fn)此提升会忽略)
      a = 0x001; [[scope]]:EC(fn) 不会重新声明a, 但是会重新赋值
    代码执行
  */
  console.log(a)  // 0x001
  var a = 2;  // 重新赋值
  console.log(a)  // 2
  function a() {}   // 变量提升已经处理过,代码执行跳过
  console.log(a)  // 2
}
fn(a);  // 执行fn(1)
console.log(a)  // 1 全局的a

1.5 eg5

1.5.1 情况1
  • 变量提升:
    • var a;
    • fn = 0x000; [[scope]]:EC(G)
  • 代码执行
console.log(a);  // undefined
var a = 12;
function fn() {
  /*
    fn执行
    初始化作用域链<EC(FN), EC(G)>
    形参赋值:无
    变量提升: var a; //私有 a
  */
  console.log(a);  // undefined
  var a = 13;   // 私有a = 13
}
fn();  // 执行
console.log(a);  // 12
1.5.2 情况2
  • 变量提升:
    • var a;
    • fn = 0x000; [[scope]]:EC(G)
  • 代码执行
console.log(a);  // undefined
var a = 12;
function fn() {
  /*
    fn执行
    初始化作用域链<EC(FN), EC(G)>
    形参赋值:无
    变量提升:无 没有私有变量
  */
  console.log(a);  // 12
  a = 13;   // 全局a = 13
}
fn();  // 执行
console.log(a);  // 13
1.5.3 情况3
  • 变量提升:
    • fn = 0x000; [[scope]]:EC(G)
  • 代码执行
console.log(a);  // 输出变量首先看是否是自己的私有变量,不是自己私有则找上级,都没有 a is not defined  代码中断执行
a = 12;
function fn() {
  console.log(a);  // 12
  a = 13;   // 全局a = 13
}
fn();  // 执行
console.log(a);  // 13

1.6 eg6

  • 变量提升:
    • var foo;
    • 自执行函数,不参与变量提升,代码运行到才创建
var foo = 'hello';
(
  function (foo) {
    /*
      EC(any)
      初始化作用域链<EC(any), EC(G)>
      形参赋值:foo = 'hello'
      变量提升: var foo, 当前上下文有foo,忽略此步声明
      代码执行
    */
    console.log(foo);   // hello
    var foo = foo || 'world';  // foo = 'hello'
    console.log(foo);   // 'hello'
  }
)(foo);   // 自执行函数执行。传递实参 'hello'
console.log(foo);  // hello
  • || 和 &&
    • A||B:A为真返回A的值,A为假返回B的值,
    • A&&B:A为真返回B的值,A为假,返回A的值
    • ||和&&同时出现的时候,&&的优先级高于||

1.7 eg7

// 
{
  function foo() {}
  foo = 1;
}
console.log(foo);


// ---------------

{
  function foo() {}
  foo = 1;
  function foo() {}
}
console.log(foo);


// ---------------

{
  function foo() {}
  foo = 1;
  function foo() {}
  foo = 2;
}
console.log(foo);

2. 数据类型

2.1 把一个对象数据类型的值,转换为数字/字符串

  • 把一个对象数据类型的值,转换为数字/字符串

    • 首先查找对象的Symbol.toPrimitive属性
    • 如果没有这个属性,紧接着调用对象的 valueOf方法来获取原始值(基本类型值)
    • 如果获取不到原始值,则再调用 toString 转化为字符串或者 Number 转化为数字
  • 场景

    • +运算中,如果左右两边出现字符串(或者是部分对象值),则不是数学运算,会变成字符串拼接
    • alert会把值,隐式转换为字符串输出
    • 字符串拼接,模板字符串中实现的是字符串拼接,遇到对象会转换成字符串
    • 其余的数学运算中,例如 - / * %中会把对象转换成数字
    • == 比较中,也会把对象转换为字符串或者数字
let obj = {
  name: 'this is a obj'
}
let arr = [10, 20, 30]
let time = new Date()
let num = new Number(10)
console.log(obj, arr, time, num)

image.png


console.log(10 + obj)  // '10[object, Object]'
// 1. obj[Symbol.toPrimitive]  => undefined
// 2. obj.valueOf()  => {name: 'xxx'} 不是原始值,原始值是基本类型
// 3. obj.toString() => '[object, Object]'

console.log(10 + num)  // 20
// 1. num[Symbol.toPrimitive]  => undefined
// 2. obj.valueOf()  => 10 是原始值,原始值是基本类型


obj[Symbol.toPrimitive] = function() {
  return 100;
}
console.log(10 + obj)  // 110


// 默认参数hint
obj[Symbol.toPrimitive] = function(hint) {
  console.log(hint)  // => default或者number或者string  类型
  return 100;
}