面试题:涉及的知识点思考

184 阅读3分钟

valueOf() 与 toString()

除 null,undefined, 所有的js 数据类型都有这两个方法。

  • toString(): 将数据类型转化为string类型
  • valueOf(): 将数据类型转化为自身,原来是什么类型,转化后还是什么类型,日期类型除外(日期转化为 UTC 毫秒数,类型为 number类型)

正常情况下和 对象转换时,优先调用 toString(), 有运算操作符和强转数字的情况下,优先调用valueOf().

console.log({}+[]); // [object Object]
//解析: JS代码在()里面被包裹,会变成表达式执行
//primitite-->valueOf()-->toString()
//{}-->[object Object]
//[]-->''

{}+[]; //0
[]+{}; //[object Object]
{}+{}; //[object Object][object Object]
//特别注意: 如果第一个是{} (在{}前面不存在任何操作符)时,后面加上其他的像数组、数字或字符串,这时候, {} 会被解析成 空区块语言,加号运算会直接变为一元正号运算,也就是强制转为数字的运算。这是个陷阱要小心。

console.log([]==false);  //true []会被转换成""
console.log({}==false);  //false {}-->[object Object]
if([]){
    console.log([]==false) //还是会打印,true ,[]在ECMA规范中就是true,C++[]是存在的,因为占用了内存
};

类型隐式转换成 数值类型

('b'+'a'+ +'a'+'b'+'a').toLocaleLowerCase();
//bananba "a'+ +'a'"中间的2个加号必须存在空格,不然就会报错。'a'+ +'a'  => 'a'+ (+'a')
Boolean("0")==Boolean(0); //false "0"是字符串 ,0会变成fasle
console.log(NaN==0); //false
console.log(NaN<=0); //false
console.log(NaN>=0); //false NaN与其他数值进行比较的结果总是不相等的,包括它自身在内
console.log(null<=0); //true   Number(null) = 0
console.log(1+null);  //1

var obj={yd:{name:'xxx'}};
var x =+obj.yd?.name??'超级简单'; // ?? 空值合并操作符,当其左侧为null和undefined时,才会返回右侧的数
console.log(x) // NaN
//+obj.yd 变成一个表达式,让它立即执行 ?. 问的是这里是否存在name属性 

typeof 、 instanceof 、 Object.prototype.toString.call(obj)

  • typeof: 操作符返回一个字符串,表示未经计算的操作数的类型。
typeof null // object
typeof function(){} // function
// 其他 任何对象都返回 object。 因此,typeof 一般用来检测 基本数据类型。
  • instanceof: 用来检测 constructor.prototype 是否存在于参数 object 的原型链上 用法:
object instanceof constructor

// object 实例对象
// constructor 构造函数
  • Object.prototype.toString.call(obj): 返回一个表示该对象的字符串 用法:
var toString = Object.prototype.toString;

toString.call(new Date) // [object Date]
toString.call(null) // [object null]
toString.call(undefined) // [object undefined]
var id='超级简单';
console.log(typeof id); //String  这里会走一下装箱,拆箱过程
console.log(id instanceof String); //false  //这个会直接走原型链

作用域与作用域链

1)作用域:

定义: 简单来说,就是变量和函数可访问的范围。js中一个函数的变量不可以在函数外访问。

2)全局作用域、局部作用域、块级作用域

  • 全局作用域:任何地方都可以访问的变量

    • 函数外定义的变量
    var n = 1;
    
    function () {
       var a = 1; // 局部变量,函数外面无法访问
       return a;
    }
    
    console.log(fn())
    console.log(n)
    console.log(a) // Uncaught ReferenceError: a is not defined
    
    • 未定义直接赋值的变量会自动声明为全局变量
    var n = 1;
    
    function () {
        a = 1;
        return a;
    }
    
    console.log(fn())
    console.log(n)
    console.log(a)
    
    • window对象的属性
  • 局部作用域:只在固定的代码片段中可以访问的变量

  • 块级作用域:ES6引入的块级作用域,letconst都涉及块级作用域。块级作用域允许声明函数只在使用大括号的情况下成立,如果未使用大括号,会报错。

3)作用域链

定义:

当执行函数时,先从函数内部查找变量。

如果内部没找到,则会向创建函数的作用域查找。

依次向上,直到全局作用域为止。

如果全局作用域中也没有找到,则认定该变量未声明(xxx is not defined)

闭包

1)定义:

闭包函数:声明在一个函数中的函数。

闭包:内部函数可以访问到其所在外部函数中声明的参数和变量,即使在其外部函数被返回之后。使用闭包主要是为了设计私有的方法和变量。

2)特点:

  • 让外部访问函数内部变量成为可能
  • 局部变量会常驻在内存中
  • 可以避免使用全局变量,防止全局变量污染
  • 会造成内存泄露(有一块内存空间被长期占用,而不被释放)

3)闭包的创建:

闭包就是创建一个独立的环境,每一个闭包里的环境都是独立,互不干扰。

注意:每次外部函数执行的时候,外部函数的引用地址不同,都会重新创建一个新的地址。

5)案例解释:

案例一:
function funA(){
  var a = 10;  // funA的活动对象之中;
  return function(){   //匿名函数的活动对象;
        alert(a);
  }
}
var b = funA();
b();  //10


案例二:
function outerFn(){
  var i = 0; 
  function innerFn(){
    i++;
    console.log(i);
  }
  return innerFn;
}
var inner = outerFn();  //每次外部函数执行的时候,都会开辟一块内存空间,外部函数的地址不同,都会重新创建一个新的地址
inner();
inner();
inner();
var inner2 = outerFn();
inner2();
inner2();
inner2();   //1 2 3 1 2 3

案例三
var i = 0;
function outerFn(){
  function innnerFn(){
    i++;
    console.log(i);
  }
  return innnerFn;
}
var inner1 = outerFn();
var inner2 = outerFn();
inner1();
inner2();
inner1();
inner2();     //1 2 3 4

案例四
(function() { 
  var m = 0; 
  function getM() { return m; } 
  function seta(val) { m = val; } 
  window.g = getM; 
  window.f = seta; 
})(); 
f(100);
console.info(g());   //100  闭包找到的是同一地址中父级函数中对应变量最终的值

案例五
function fun(n,o) {
    console.log(o);
    return {
         fun:function(m) {
               return fun(m,n);
         }
    };
}
var a = fun(0);  //undefined 执行 fun(0)
a.fun(1);  //0  执行 fun(1, 0)
a.fun(2);  //0  执行 fun(2, 0)
a.fun(3);  //0  执行 fun(3, 0)
var b = fun(0).fun(1).fun(2).fun(3);   //undefined  0  1  2
var c = fun(0).fun(1);  
c.fun(2);  
c.fun(3);  //undefined  0  1  1