关于JavaScript的一些记录

各种浏览到的知识点,只作为自己的整理和积累

1.typeof

let test ;

test = {};
console.log(typeof test)    //object

test = 'object';
console.log(typeof test)    //string

test = ()=>{};
console.log(typeof test)    //function

test = [];
console.log(typeof test)    //object

test = null ;
console.log(typeof test)    //object

2.下面的代码会console出什么?为什么?

(function(){
 var a = b = 3;
})();
如果你没有采用strict模式,输出结果如下: console.log(b); //3 console.log(typeof a); //undefined
console.log(a) //Uncaught ReferenceError: a is not defined

 解析:var a = b = 3

    拆解为:b = 3; var a = b;

    b最终是作为全局变量的,因为b没有用var定义,因此作用域在function外部。

    需要注意的是,在strict 模式(如使用use strict),var a=b=3;将会产生一个运行时error:ReferenceError: b is not defined。因此可以避免这类的错误。(因此,这也是为什么要在你的代码应采用use strict模式)。

3.以下代码的输出是什么?并说明原因。

var myObject = {

    foo: "bar",

    func: function() {

        var self = this;

        console.log("outer func:  this.foo = " + this.foo);//bar

        console.log("outer func:  self.foo = " + self.foo);//bar 

        (function() {

            console.log("inner func:  this.foo = " + this.foo);//undefined

            console.log("inner func:  self.foo = " + self.foo); //bar

        }());

    }

};

myObject.func();

  解析:在outer function里,this和self都指向myObject, 因此都能访问到foo,

       在inner function里,this不再指向myObject,因此,this.foo在inner function里面是undefined,然而,self依然指向myObject,(在ECMA5之前,this在内部function里将指向window,然而,在ECMA5之后,内部function的this将是undefined).

4.将一个JavaScript文件封装在一个function块里的意义,原因是什么?

  在很多流行的JavaScript库(jQuery,Node.js等)将源码文件封装在一个函数中越来越普遍。这种技术会为这个源码文件创建一个封闭的环境,可能最重要的是创建了一个私有的命名空间,因此避免了在不同的JavaScript modules和库中出现潜在的命名冲突。

  这种技术的另一个特点是允许通过别名的方式很容易的引用全局变量。这个经常用到,比如,在jQuery插件中,jQurey 允许你通过jQuery.noConflict(),使得不能通过$引用jQuery命名空间。如果你这么做了,你依然可以像下面代码一样使用$采用这种闭包的技术:

  (function($) { /* jQuery plugin code referencing $ */ } )(jQuery);(没看懂)

5.在JavaScript源码文件中以’use strict’开始的意义和好处是什么?

  a.使得debugging更容易:在一些被忽略或者潜在的错误会产生error或者exceptions,会很快的在你的代码中显示警告,引导你很快的找到对应的源码。

  b.阻止出现意外的全局变量:如果不是在strict模式里,那么赋值给一个没有声明的变量时,会自动的创建一个同名的全局变量,这是JavaScript中最常见的错误之一。在strict模式里,会尝试抛出一个error.

  c.强制排除this错误:在非strict模式里,this引用为null或者undefined时,会自动强制指向全局,这会导致各种错误的引用。在strict模式里,this值为null或者undefined将会抛出error.

  d.不允许重名的属性名或者参数名.在strict模式里,如果定义了如:var object={foo:’bar’,foo:’baz’};或者定义一个重名参数的函数,如:function foo(val1,val2,val1){}.会产生一个error,而这个bug几乎一定会产生,但你可能浪费大量的时间才能找到。

  e.使eval()更加安全。在strict模式和非strict模式里,eval()存在很多不同。在strict模式里,变量和函数在eval()中声明,但语句不在内部块创建,但是在非strict模式里,语句也会在内部块里创建,这也是常见的源码问题。

  f.不正确使用delete会抛出error:delete操作(用于从object中删除一个属性)不能用于没有配置的属性,在非strict模式的代码里删除一个没有配置的属性会失败,但不会有提示,在strict模式里,则会抛出error。

6.考虑一下下面的两个函数,他们将返回同样的值吗?请说明原因

function foo1()
{
  return {
      bar: "hello"
  };
}

function foo2()
{
  return  
  {
      bar: "hello"
  };
}

 console.log(foo1());   //Object {bar:'hello'}

 console.log(foo2());  //undefined

  解析:不仅仅只是对返回不一样的结果奇怪,也要对foo2()没有抛出error特别注意。产生这个结果的原因是分号在JavaScript中的用法(省略分号不是好的做法)。当foo2()的一行语句中只包含return时,会在return语句后面自动的加上一个分号。后面的语句也是合法的,不会抛出error,尽管它不会调用,也不做任何事(仅仅只是一个没有用到的语句块,它定义了一个等同于’hello’字符串的属性bar而已)。

    这也说明了在Javascript中大括号的位置应该放在语句后面的编程风格更符合Javascript的语法要求(有些语言推荐放在新一行的开头)。

7.NaN是什么?它是什么类型?怎样能够可靠的判断一个值是否等于NaN?

  解析:NaN 是 Not a Number 的缩写,JavaScript 的一种特殊数值,其类型是 Number,另外一个是NaN更任何东西比较(即使是自身),结果都为false,如下:

     console.log(NaN === NaN);  // logs "false"。

     用isNaN()判断一个number是否等于NaN是不可靠的,更好的解决方案使用value!==value,这个只有当value等于NaN时才会为true.

     ES6 中,isNaN() 成为了 Number 的静态方法:Number.isNaN().,比使用老的全局函数isNaN()更可靠。

console.log(isNaN(NaN)); //true
console.log(isNaN(23)); //false
console.log(isNaN('ds')); //true
console.log(isNaN('32131sdasd')); //true
console.log(NaN === NaN); //false
console.log(NaN === undefined); //false
console.log(undefined === undefined); //false
console.log(typeof NaN); //number
console.log(Object.prototype.toString.call(NaN)); //[object Number]

8.下面的代码输出结果是什么?并解释原因

console.log(0.1 + 0.2);   //0.30000000000000004
console.log(0.1 + 0.2 == 0.3);   //false

  解析:JavaScript 中的 number 类型就是浮点型,JavaScript 中的浮点数采用IEEE-754 格式的规定,这是一种二进制表示法,可以精确地表示分数,比如1/2,1/8,1/1024,每个浮点数占64位。但是,二进制浮点数表示法并不能精确的表示类似0.1这样 的简单的数字,会有舍入误差。

     由于采用二进制,JavaScript 也不能有限表示 1/10、1/2 等这样的分数。在二进制中,1/10(0.1)被表示为 0.00110011001100110011…… 注意 0011 是无限重复的,这是舍入误差造成的,所以对于 0.1 + 0.2 这样的运算,操作数会先被转成二进制,然后再计算:

     0.1 => 0.0001 1001 1001 1001…(无限循环)
     0.2 => 0.0011 0011 0011 0011…(无限循环)

     双精度浮点数的小数部分最多支持 52 位,所以两者相加之后得到这么一串 0.0100110011001100110011001100110011001100…因浮点数小数位的限制而截断的二进制数字,这时候,再把它转换为十进制,就成了 0.30000000000000004。

对于保证浮点数计算的正确性,有两种常见方式。  

一种是先升幂再降幂:
function add(num1, num2){
 let r1, r2, m;
 r1 = (''+num1).split('.')[1].length;
 r2 = (''+num2).split('.')[1].length;

 m = Math.pow(10,Math.max(r1,r2));   //Math.pow(a,b) = a
b
 return (num1 * m + num2 * m) / m;
}
console.log(add(0.1,0.2)); //0.3
console.log(add(0.15,0.2256)); //0.3756

另一种是使用内置的 toPrecision() 和 toFixed() 方法,注意,方法的返回值字符串。
function add(x, y) {
 return x.toPrecision() + y.toPrecision()
}
console.log(add(0.1,0.2)); //"0.10.2"

9.讨论一下怎样写一个函数isInteger(x),判断x是一个整数。 

Number.isInteger(25)     // true
Number.isInteger(25.0)   // true
Number.isInteger(25.1)   // false
Number.isInteger("15")   // false
Number.isInteger(true)   // false    

  JavaScript能够准确表示的整数范围在 -2^53 到 2^53 之间(不含两个端点),超过这个范围,无法精确表示这个值。ES6 引入了Number.MAX_SAFE_INTEGER 和 Number.MIN_SAFE_INTEGER这两个常量,用来表示这个范围的上下限,并提供了 Number.isSafeInteger() 来判断整数是否是安全型整数。  

function isInteger(x) { return (x^0) === x; } 
function isInteger(x) { return Math.round(x) === x; }//用Math.ceil()或者Math.floor()代替Math.round()也可以。
还可以选择:
function isInteger(x) { return (typeof x === 'number') && (x % 1 === 0); }

  注意:当X值较大时,通常采用——function isInteger(x) { return parseInt(x, 10) === x; }是不正确的,这主要是因为parseInt是基于能正确转化x,一旦x太大,它会转换失败,parrseInt会在转化成数字是先强制转换为字符串,因此,一旦一个数字太大,它转化的字符串是原数字的指数形式(1e+21),因此,parseInt()将会解析1e+21,但是当遇到e是会停止解析了。如下:

String(1000000000000000000000)          //  1e+21
parseInt(1000000000000000000000, 10)    //  1
parseInt(1000000000000000000000, 10) === 1000000000000000000000 // false

10.写一个函数,判断一个字符串(超过80个字符)是否是回文结构(正序和逆序相同)

function isPalindrome(str) {
    str = str.replace(/W/g, '').toLowerCase();
    return (str == str.split('').reverse().join(''));
}

11.考虑一下以下的代码片段

for (var i = 0; i < 5; i++) {
  var btn = document.createElement('button');
  btn.appendChild(document.createTextNode('Button ' + i));
  btn.addEventListener('click', function(){ console.log(i); });   //同循环外打印
  document.body.appendChild(btn);
}
console.log(i) //5

(a)当用户点击“Button4”的时候会打印什么?并解释为什么?

(b)提供一个或多个正确的实现方式。

  (a)无论点击哪个按钮,都将打印5.因为任何按钮在调用onclick方法时,for循环已经完成了,变量i的值变成了5.(var 是函数级作用域,let是块级作用域,当for循环结构执行完时,i=4,因为 i++,是后加加,那么循环结束的时候,i 还是会自加 1 ,所以,执行完是5)

  (b).关键是要抓住在每一次循环for的时候要把i的值传人到最近创建的函数对象中,下面有三个可能的方式解决这个问题:

1.for (var i = 0; i < 5; i++) {
  var btn = document.createElement('button');
  btn.appendChild(document.createTextNode('Button ' + i));
  btn.addEventListener('click', (function(i) {
    return function() { console.log(i); };
  })(i));
  document.body.appendChild(btn);
}

2.你可以将整个btn.addEventListener封装在一个新的匿名函数里
for (var i = 0; i < 5; i++) {
  var btn = document.createElement('button');
  btn.appendChild(document.createTextNode('Button ' + i));
  (function (i) {
    btn.addEventListener('click', function() { console.log(i); });
  })(i);
  document.body.appendChild(btn);
}

3.可以将for循环换成array对象的本地调用方法forEach.
['a', 'b', 'c', 'd', 'e'].forEach(function (value, i) {
  var btn = document.createElement('button');
  btn.appendChild(document.createTextNode('Button ' + i));
  btn.addEventListener('click', function() { console.log(i); });
  document.body.appendChild(btn);
});

12.下面的代码将输出什么,并解释原因?

var arr1 = "john".split('');
var arr2 = arr1.reverse();
var arr3 = "jones".split('');
arr2.push(arr3);
console.log("array 1: length=" + arr1.length + " last=" + arr1.slice(-1));               //"array 1: length=5 last=j,o,n,e,s"
console.log("array 2: length=" + arr2.length + " last=" + arr2.slice(-1));               //"array 2: length=5 last=j,o,n,e,s"

  解析:Arr1和arr2输出结果一样的原因如下:

    A.调用array对象的reverse()方法不仅仅返回转置后的数组,它自身的顺序也转置了。

    B.Reverse()方法返回的是指向array自身的一个引用,因此,arr2仅仅是arr1的一个引用,无论对arr2怎么操作,arr1也会受到影响,arr1,arr2都指向同一个对象。

    C.通过调用array的push()方法,传人另一个array,仅仅只是把传人的array作为一个元素添加到队列的尾部,因此,arr2.push(arr3)是将arr3当作一个元素添加到arr2的队尾,而不是像concat()方法一样,合并两个array。

    D.与Python类似,JavaScript通过输入-1调用slice()是一种指向array队列尾部最后一个元素的方法。

13.下面代码的输出是什么,并解释为什么?(注意:空格很重要,不可省略)

console.log(1 +  "2" + "2");           //"122"
console.log(1 +  +"2" + "2");          //"32"
console.log(1 +  -"1" + "2");          //"02"
console.log(+"1" +  "1" + "2");        //"112"
console.log( "A" - "B" + "2");         //"NaN2"
console.log( "A" - "B" + 2);           //NaN

  解析:主要的问题是JavaScript是一种弱类型的语言,它会在操作执行时自动转化数值类型。数字字符串之前存在数字中的正负号(+/-)时,会被转换成数字,如

console.log(typeof '3'); // string
console.log(typeof +'3'); //number
同样,可以在数字前添加 '',将数字转为字符串
 console.log(typeof 3); // number
 console.log(typeof (''+3)); //string

    (自己的理解===关于加法:数字 + 字符串 = 字符串,带有操作符’+‘或’-‘的元素,转换数值类型;关于减法:数字/字符串-数字/字符串 = 数字,字符串不存在减法,都会转化成数字再运算)。

    A:1+”2”+”2”输出结果为”122”.第一个操作是1+”2”,因为有个操作数是”2”,是字符串,JavaScript会在执行操作过程中转化成字符串的合并,1转化成”1”,1+”2”得到结果为“12”,然后,“12”+“2”,结果为“122”。

    B:1+ +”2”+”2”输出结果为“32”。第一个操作是+”2”,这被看成是一元操作,因此,JavaScript会把”2”的类型转为数字,再赋予+,则为正整数2,然后执行1+2得3,但是当我们执行一个数字和字符串的操作3+”2”时,又将转化成字符串连接操作,得到字符串“32”。

    C:1+ -“1” + “2”输出为“02”.这里首先定义操作优先级,一元操作-高于+,因此”1”转为1,赋予-,转为-1,加上1得结果0,然后与最后的“2”转字符串连接,结果为“02”。

    D:+“1” + “1” +“2”,输出结果为112.首先执行+一元操作得1,然后与后面的“1”,“2”执行字符串连接操作,得结果“112”。

    E:“a”-”b” +”2”输出结果为“NaN2”。因为字符串不存在-操作,因此会将A,B转化成数字,转化失败,得到结果“NaN”,再与“2”做字符串连接操作,得到结果“NaN2”.

    F:”a”-”b”+2,输出结果为NaN,因为字符串不存在-操作,因此会将A,B转化成数字,转化失败,得到结果“NaN”,再与2执行加操作,但是NaN与任何数字操作结果还是NaN。

以下为测试实例
console.log(+'1'+ +2)                  //3
console.log('1'+ +2)                   //12
console.log(+'12' + '34')              //'1234' 
console.log(+'12' + +'34')             //46
console.log(-'12' + '34')              //'-1234' 
console.log(-'12' + 34)                //22 
console.log(-12 + '34')                //'-1234' 
console.log(-12 + 34)                  //22 

console.log('12' - '34')               //-22 
console.log('12' - 34)                 //-22 
console.log(12 - '34')                 //-22 
console.log(12 - 34)                   //-22 

console.log(+'12' - '34') //-22 
console.log(+'12' - 34) //-22 
console.log(+12 - '34') //-22 
console.log(+12 - 34) //-22

console.log(-'12' - '34') //-46 
console.log(-'12' - 34) //-46 
console.log(-12 - '34') //-46 
console.log(-12 - 34) //-46

14.下面的代码,如果队列太长会导致栈溢出,怎样解决这个问题并且依然保持循环部分。

var list = readHugeList();
var nextListItem = function() {
    var item = list.pop();
    if (item) {
        // process the list item...
        nextListItem();
    }
};

  解析:栈溢出主要是因为循环事件,而不是栈。当执行nextListItem时,如果item不是null,在timeout函数中的nextListItem会推入到事件队列中。当事件空闲,则会执行nextListItem,因此,这种方法从开始到结束没有直接进行循环调用,可以不用考虑循环次数。

    为了避免栈溢出,循环部分改为如下代码:

var list = readHugeList();
 
var nextListItem = function() {
    var item = list.pop();
 
    if (item) {
        // process the list item...
        setTimeout( nextListItem, 0);
    }
};

15.什么是闭包?并举例。

   答:闭包是一个内部函数访问外部定义的变量,闭包有三种访问变量的方式。1)在自身域的变量。2)闭包函数域的变量。3)全局变量。

例子:
var globalVar = "xyz";
 
(function outerFunc(outerArg) {
  var outerVar = 'a';
  
  (function innerFunc(innerArg) {
    var innerVar = 'b';
    
    console.log(
      "outerArg = " + outerArg + "
" +                         
      "innerArg = " + innerArg + "
" +
      "outerVar = " + outerVar + "
" +
      "innerVar = " + innerVar + "
" +
      "globalVar = " + globalVar);
    
  })(456);
})(123);

  //    outerArg = 123

  //    innerArg = 456

  //    outerVar = a

  //    innerVar = b

  //    globalVar = xyz

16.下面代码的输出结果是什么?并解释原因

for (var i = 0; i < 5; i++) {
  setTimeout(function() { console.log(i); }, i * 1000 );
}

  答:例子显示的结果不是预想的0,1,2,3和4.而是5,5,5,5和5.原因是每个执行的函数在循环完全执行完成后,i最后的赋值是5.闭包的方式可以解决这个问题,在每个循环中为次的循环变量创建一个唯一域,如下:

for (var i = 0; i < 5; i++) {
(function(x) {
     setTimeout(function() { console.log(x); }, x * 1000 );
    })(i);
}

17.下面代码的输出结果是什么?并解释原因。

console.log("0 || 1 = "+(0 || 1));                  //0 || 1 = 1
console.log("1 || 2 = "+(1 || 2));                  //1 || 2 = 1
console.log("0 && 1 = "+(0 && 1));                  //0 && 1 = 0
console.log("1 && 2 = "+(1 && 2));                  //1 && 2 = 2  

  解析:在JavaScript中,||和&&是逻辑操作,将从左往右进行逻辑判断。||操作符的表达式是X|Y,X先进行评估,判断一个boolean逻辑值,如果为true,则值为X。如果为false,则值为Y。&&操作恰恰相反。(注意:本题关键是0在此处表示false,1在此处表示true)

18.下面代码的执行结果是什么?并解释原因

console.log(false == '0')             //true
console.log(false === '0')            //false

  解析:在JavaScript中,有两个等于操作,其中三等号===更像传统的等于操作,如果表达是两边的类型和值都相等才为true,双等号==会在比较相等时进行强制的值比较,因此更好的编程风格是采用===,同样的应采用!==,而不是!=。

19.下面代码的输出结果是什么?并解释原因。

var a={},
    b={key:'b'},
    c={key:'c'};
 
a[b]=123;
a[c]=456;
console.log(a[b]);   //456

  解析:当我们设置一个object属性时,JavaScript会隐形的将参数值字符串化。在这个例子中,b和c都是objects,他们会转化为”[object,object]”,结果a[b]和a[c]都等于a[”[object,object]”],因此,设置或者引用a[c]等同于设置和引用a[b].

var a={},
    b={key:'b'},
    c={key:'c'};
 console.log('a=',a)                          //{}
 console.log('b=',b)                          //{key:'b'}
 console.log('c=',c)                          //{key:'c'}

a[b]=123; console.log('after add [b],a=',a) //{[object Object]: 123} console.log('a[b]=',a[b]) //123 console.log('a[c]=',a[c]) //123 console.log('a[e]=',a[e]) //Uncaught ReferenceError: e is not defined
a[c]=456; console.log('after add [c],a=',a) //{[object Object]: 456} console.log('a[b]=',a[b]); //456 console.log('a[c]=',a[c]); //456

20.给出以下代码的输出结果,并解释原因?

console.log((function f(n){return ((n > 1) ? n * f(n-1) : n)})(10));

  答:代码块的输出结果是10facorial值,也就是(10!或者 3628800),f()会循环调用自身,直到调用f(1),返回1

         

21.下面代码块的输出是什么?并解释原因。

(function(x) {
    return (function(y) {
        console.log(x);            //1
    })(2)
})(1);

  解析:在JavaScript中,闭包是作为内部函数实现的,也就是一个函数定义在另一个函数内部,闭包的重要特性是内部函数能够访问外部函数的变量。在这个例子中,尽管x没有在内部函数定义,在外部函数里找到了定义的值为1的x变量。

22.下面代码的输出结果是什么,并解释原因?如何修改。

var hero = {
    _name: 'John Doe',
    getSecretIdentity: function (){
        return this._name;
    }
};
var stoleSecretIdentity = hero.getSecretIdentity;  

//将 getSecretIdentity 赋给 stoleSecretIdentity,等价于定义了 stoleSecretIdentity 函数:
 // var stoleSecretIdentity = function (){
 //  return this._name;
 // }
 // stoleSecretIdentity

console.log(stoleSecretIdentity()); console.log(hero.getSecretIdentity());


应修改为:var stoleSecretIdentity = hero.getSecretIdentity.bind(hero);

  答:输出结果为 undefined  JohnDoe。

  解析:第一个为undefined是因为我们直接从hero对象中提取stoleSecretIdentity(),stoleSecretIdentity()的上下文是全局环境(window对象),不存在_name属性。若要输出 John Doe,则要通过 call 、apply 和 bind 等方式改变 stoleSecretIdentity 的this 指向(hero)。

     第二个是调用对象的方法,输出 John Doe。

23.创建一个函数,赋予page的一个dom元素,将访问自身和它所有的子元素(不仅仅是直接子元素)。因为每个元素都要访问到,需要传入一个回调函数。函数的参数如下:一个Dom元素,一个回调函数。

  答:可以采用深度优先搜索算法(Depth-First-Search)。如下:

function Traverse(p_element,p_callback) {
   p_callback(p_element);
   var list = p_element.children;
   for (var i = 0; i < list.length; i++) {
       Traverse(list[i],p_callback);  // recursive call
   }
}

  

原文地址:https://www.cnblogs.com/xtping/p/7610675.html