# JS笔试题(一)

# 一、变量提升

1、

console.log(a, b, c);
var a = 12,
    b = 13,
    c = 14;
function fn(a) {
    console.log(a, b, c);
    a = 100;
    c = 200;
    console.log(a, b, c);
}
b = fn(10);
console.log(a, b, c);

结果:undefined undefined undefined 10 13 14 100 13 200 12 undefined 200

分析: 由执行栈执行顺序从上而下可知第一行打印都是undefined

​ 执行fn时,a 为内部参数,声明内部私有a为10;没有b,使用外部b=13;同理c=14;

​ a赋值100;c赋值200; 所以打印 100,13,200;

​ 因为函数执行默认返回undefined,赋值给b, b结果为undefined

​ 此时a仍是12, c因为函数内修改了全局的c,所以c为200

2、

var i = 0;
function A() {
    var i = 10;
    function x() {
        console.log(i);
    }
    return x;
}
var y = A();
y();    // i =10;
function B() {
    var i = 20;
    y();
}
B();  // 10

结果: 10, 10

分析: 函数x创建在了A函数中,在A作用域中i为10;执行函数x的时候,内部没有i,去外部作用域查找i=10; 第二个B执行时,y执行外部作用域仍是A创建的,所以i仍是i=10;

3、

var a=1;
var obj ={
   name:"tom"
}
function fn(){
   var a2 = a;   // a2 = 1;
   obj2 = obj;   // obj2 = obj = {name:'tom'}
   a2 =a;        // a2 = 1 
   obj2.name ="jack";   // obj2.name = "jake"
}
fn();
console.log(a);   // 1
console.log(obj); // {name:'jake'}

结果:1 , { name: Jake}

分析,堆引用的问题

4、

var a = 1;
function fn(a){
    console.log(a) // function a(){}
    var a = 2;
    function a(){}
}
fn(a);

结果为:function a(){}

考察函数作用域内变量提升,首先var a;初始化参数a=1;赋值为2;赋值为函数function a(){}

5、

console.log(a);  // undefined
var a=12;        // 声明  var a;  fn(){...}; 
function fn(){   // 执行  函数作用域中私有变量a变量提升,打印a为undefined
    console.log(a); 
    var a=13;    // 函数内a赋值为13
}
fn();     
console.log(a);  // 函数外EC(G)变量a仍为12

结果为 undefined 、 undefined 、 12 , 分析如上

6、

var foo='hello';       // EC(G)  var foo; 
(function(foo){        // 自闭合函数执行,传入foo = "hello"
   console.log(foo);   // 函数初始化私有变量foo = "hello" , 打印"hello"
   var foo=foo||'world';  // var foo = "hello"  foo赋值操作,
   console.log(foo);     // foo 仍为“hello”
})(foo);
console.log(foo);       // foo 'hello'

结果: “hello”,“hello”,“hello”

7、

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

结果为1 ?

分析: 考察块机作用域的变量提升

老版本浏览器,全局上下文中变量提升的时候,不论条件是否成立,或者是否出现在大括号中(排除函数和对象大括号)对于function来说仍然是声明加定义,

高版本浏览器中,为了保证语意的准确性,条件不成立不赋值,或者执行的时候在全局下,只声明不赋值,

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

8、

# 二、数据类型和基础知识作业

1、

let result = 100 + true + 21.2 + null + undefined + "Tencent" + [] + null + 9 + false;
console.log(result);
// 100 + 0 +21.2 + 0 + NaN + "Tencent" + null + "" + 0 + 9 + false;
// "NaNTencentnull9false"

2、

{}+0?alert('ok'):alert('no');  // "no"
0+{}?alert('ok'):alert('no');  // "ok"

分析

0 + {} // "0[object Object]" {} + 0 // 0

3、

let res = Number('12px');  // NaN
if(res===12){
    alert(200);
}else if(res===NaN){
    alert(NaN);
}else if(typeof res==='number'){     // true 
    alert('number');
}else{
    alert('Invalid Number');
}

结果 : 'number'

4、

let arr = [27.2,0,'0013','14px',123];
arr = arr.map(parseInt);
console.log(arr);

结果为: [27,NaN,1,1,27]

分析

arr.map((item,index)=> {

​ parseInt(item, index)

})

parseInt(27.2, 0) // 27

parseInt(0, 1) // NaN

parseInt('0013', 2) // '001' => 0 * 2^2+0 * 2^1 + 1 * 2^0 => 1

parseInt('14px', 3) // => '1' => 1*3^0 范围3,3进制,取0~2

parseInt( 123, 4) // => 同理: 14^2+2 * 4^1 + 34 ^0 = 3+8+16 = 27

# 三、闭包作用域

1、

var a = 10,
    b = 11,
    c = 12;    
function test(a) {
    a = 1;
    var b = 2;
    c = 3;
}
test(10);
console.log(a, b, c);

结果:10,11,3

考察变量提升,作用域问题,函数内部有自己的参数和私有变量,修改时不影响全局,所以全局a和b未改变,c因为函数内没有,向上查找修改全局c为12,所以结果为10,11,3

2、

var a = 4;
function b(x, y, a) {
    console.log(a);    
    arguments[2] = 10;
    console.log(a);
}
a = b(1, 2, 3);
console.log(a);

结果:3,10,undefined

分析:函数执行返回undefined

3、

var a = 9;    
function fn() {
    a = 0;
    return function (b) {
        return b + a++;
    }
}
var f = fn();
console.log(f(5));    // 5
console.log(fn()(5));  // 5
console.log(f(5));  // 6
console.log(a);  // 2   ?

结果:5、5、6、2

分析: 执行var f = fn时,函数内没有a,所以会修改外部a为0;在此形成闭包,保存了变量 a

4、

var test = (function (i) {
    return function () {
        alert(i *= 2);
    }
})(2);
    
test(5);
    

结果:“4”

5、

var x = 4;
function func() {
    return function(y) {
        console.log(y + (--x));
    }
}
var f = func(5);
f(6);   // 9  x=3
func(7)(8);  // 10  x=2
f(9);  //  10 x=1
console.log(x);  // x =1

结果:9 , 10 , 10 , 1

6、

var x=5,y=6;
function func(){
  x+=y;  
  func=function(y){
    console.log(y+(--x));  
  };
  console.log(x,y); 
}
func(4); 
func(3);  
console.log(x,y); 

分析:

执行func(4) 时,func = function(){} 但是没有执行,此时EC(func)中x=11;y=6,EC(G)x=11; 打印结果为11,6;

执行func(3)时,func = function(y){console.log(y+(--x))} , 此时,打印3+11-1=13; EC(func)^x= EC(G)^x = 10

打印结果为10,6

结果:

11,6

13, 6

10,6

7、

function fun(n, o) {
    console.log(o);
    return {
        fun: function (m) {
            return fun(m, n);
        }
    };
}
var c = fun(0).fun(1);
fun(0).fun(1) = fun(1,0)  // o 为 0
c.fun(2);  // 1
c.fun(3);  // 1

结果: 0,1,1

分析:不会

8、简述你对闭包的理解,以及其优缺点?

理解:

理论上说:闭包是函数运行时的一种机制,函数执行会形成一个全新的私有上下文,可以保护里面的私有变量和外界互不干扰(保护机制)这种就可以称为闭包机制了 ;

**权威书籍中说法:**只要函数内部具有可以访问的外部自由变量的时候(不是函数的参数和内部声明的私有变量的时候),都可以称为闭包——原因是可以通过修改私有变量达到修改外部全局变量的目的(例1) ,所以把闭包定义为函数体内有权访问和修改外部的私有变量的函数称为了闭包,

**实际开发中:**通常认为只要上下文不出栈释放,这样私有变量和他的值不会释放掉,这样的才叫闭包,实际上这种说法比较偏面,但是使用和认知比较广泛。

**作用:**所以综上,闭包的作用是保护内部变量不被干扰,保存私有变量不被释放

// 例1 理论上说的闭包(保护机制), 不受外界干扰 —— 闭包机制
var a1 = 10;
function bar(){ var a1 = 13; console.log(a1)}
bar()
console.log(a1)
//=> 13,10

// 例2 闭包——修改了外部全局变量的例子
var a2 = 1;
function fun(){
  a2 = 10;
  console.log(a2)
}
fun()
console.log(a2)
// => 10, 10
//=> 13,10

// 例3 闭包——常见的闭包场景,函数内嵌套返回一个函数,这时被外部引用,无法释放,达到保存的目的
function func(x){
    var x = x;
    return function FN(){
      console.log(++x)
    }
}
var f = func(10)   // 因为全局f引用了返回的函数FN,所以func执行完不会出栈释放,导致了函数内的变量x不会出栈释放,此时当修改函数FN的时候,因为函数FN内部作用域没有x,向上查找func中的x并且修改为了x+1, 所以每执行一次f(), x 都会累加
console.log(f())  // 11
console.log(f())  // 12

9、 简述let和var的区别?

let是es6中新加入的声明变量的方式,和var相比,特点是,(1)不具有变量提升的特点,意味着不能像var一样在声明之前使用变量了。(2) 不能重复声明 (3) var声明的变量会和window挂钩,意味着全局变量和顶级变量共享这是很不科学的,let新声明的变量不会在window中找到

10 下面代码输出的结果是多少,为什么?如何改造一下,就能让其输出 20 10?

var b = 10;
(function b() {    
    b = 20; 
    console.log(b);
})();
console.log(b);

结果为 function b(){} , 10

因为在函数b自执行形成一个全新的上下文,

修改方式将外部的b传入,或者在内部b前加一个var

11- 闭包的应用

一:单例模式

/*
* 单例设计模式 —— 用单独的实例管理当前事物的特征(类似于实现一个分组的),此时obj1/obj2不仅叫做一个对象,也被称为命名空间
*/
let obj1 = {
  name: 'xxx',
  age:25say(){}
}
let obj2 = {
  name:'xxx',
  age:20,
  say(){}
}
// 项目中,基于闭包管控的单例模式称为高级单例模式,以此来实现早期的模块划分(早期模块化思想)
let module = (function(){
  function query(){}  // 私有方法
  function tools(){}  // 暴露出去使用
  return {
    name:'AREA',
    tools:tools 
  }
})()
module1.tools()

二:惰性思想

/*
*  惰性思想,执行过一遍的东西,如果执行过了,就不在执行了,没必要执行多次
*  window.getcomputestyle获得当前元素所以样式
*/
function getCss(element,attr){
  if('getcomputestyle' in window){
       return window.getcomputestyle(element)[attr]
     }
    return element.currentStyle[attr]
}
getCss(document.body,'margin')
getCss(document.body,'padding')
getCss(document.body,'color')

// 惰性思想
function getCss(element,attr){
  if('getcomputestyle' in window){
       getCss =function getCss(element,attr){
         return window.getcomputestyle(element)[attr]
       }
     }else{
       getCss =function getCss(element,attr){
         return element.currentStyle[attr]
       }
     }
    // 为了第一次也能拿到值
    return getCss(element,attr)
}

11 实现函数fn,让其具有如下功能(百度二面)

let res = fn(1,2)(3);
console.log(res); //=>6  1+2+3
/*
* 方法一:
*/
function fn(a,b){
   return function(c){
      return a+b+c;
  }
}
/*
*  方法二
*  柯里化思想 —— 利用闭包保存机制,把一些信息预先存储下来(预处理思想)
*/
function fn(...outerRest){
  return function(...innerRest){
    let args = outerRest.concat(innerRest)
    return args.reduce((n,item)=>n+item)
  }
} 
let res = fn(1,2,3,4)(3,4,5)

12 实现函数fn,让其具有如下功能(百度二面)

/* 
    在函数式编程当中有一个很重要的概念就是函数组合, 实际上就是把处理数据的函数像管道一样连接起来, 然后让数据穿过管道得到最终的结果。 例如:
    const add1 = (x) => x + 1;
    const mul3 = (x) => x * 3;
    const div2 = (x) => x / 2;
    div2(mul3(add1(add1(0)))); //=>3
    而这样的写法可读性明显太差了,我们可以构建一个compose函数,它接受任意多个函数作为参数(这些函数都只接受一个参数),然后compose返回的也是一个函数,达到以下的效果:
    const operate = compose(div2, mul3, add1, add1)    
    operate(0) //=>相当于div2(mul3(add1(add1(0)))) 
    operate(2) //=>相当于div2(mul3(add1(add1(2))))
    简而言之:compose可以把类似于f(g(h(x)))这种写法简化成compose(f, g, h)(x),请你完成 compose函数的编写
*/
// 函数柯里化 —— 一个接受多个参数的函数转化为接受单一参数的函数的技术。
// 方法一:
function compose(...args){
     return function (x){
       for(let i=0;i<args.length;i++){
            x = args[i](x)
       }
       return x
    }
}
// 方法二:
/*
*  compse:组合函数,把多层函数嵌套调用扁平化
*/
fucntion compose(...funcs){
  // funcs 存储按照顺序执行的函数(数组)=>[fn1,fn3...]
   return function(...args){
      // args 存储第一个函数执行需要传递的实参=> [20]
      if(funcs.length ===0)return args
      if(funcs.length ===1) return funcs[0](...args)
      return funcs.refuce((N,func)=>{
          // 第一次N的值是第一个函数执行的实参,func第一个执行的函数,
          // 第二次N的值,上一次func执行的返回结果...
          return Array.isArray(N)?func(...N):func(N)
      },args)
   }
}
// redux 源码中的compose函数用的是另外思想实现的

# 四、THIS

1、

var num = 10;
var obj = {
    num: 20
};
obj.fn = (function (num) {  
    this.num = num * 3;
    num++;    
    return function (n) {
        this.num += n;
        num++;
        console.log(num); 
    }
})(obj.num);
var fn = obj.fn;  
fn(5); 
obj.fn(10); 
console.log(num, obj.num);

结果: 22, 23,65 ,30

分析:

var fn = obj.fn; => 此时自闭合函数执行,this指的是window,window.num = 60; 函数体内 num = 21 ; 返回了一个函数,被fn 和 obj.fn 引用形成了一个闭包。

fn(5) => 形参传入 n =5 => 此时函数内的this 为window, window.num=65; 当前函数内没有num,上级作用域的num为21; 所以此时,函数内的num修改为了 22; 打印22;

obj.fn(10); => obj.fn 函数内的this为obj, 所以this.num+=10; this.num = 30; 也就是obj 这个对象中的num被修改为了30; 此时函数闭包作用域中的num为22; 此时打印23;

console.log(num, obj.num); => 由上,全局的num修改为了65,obj.num结果为30; 函数闭包作用域中的num为23;

2、

let obj = {
  fn: (function () {
    return function () {    
       console.log(this); 
    }    
  })()    
};    

obj.fn();  // obj => {fn: f()}
let fn = obj.fn; 
fn();  // window

分析

3、

var fullName = 'language';
var obj = {
    fullName: 'javascript',
    prop: {
        getFullName: function () {
            return this.fullName;
        }
    }
};
console.log(obj.prop.getFullName());  // this => obj.prop    undefined
var test = obj.prop.getFullName;  // this => window
console.log(test());  // 'language'

分析

obj.prop.getFullName() 的时候,this 指的是obj.prop,因为 this.fullName相当于obj.prop.fullName,当中没有fullName,所以为undefined,

test = obj.prop.getFullName,test() 此时 getFullName函数中的this,为window对象,所以打印结果为"language"

4、

var name = 'window';
var Tom = {
    name: "Tom",
    show: function () {
        console.log(this.name);
    },
    wait: function () {
        var fun = this.show;
        fun();    
    }
};
Tom.wait();

结果为"window"

分析: Tom.wait(); wait 函数中的this指的是Tom,tom.show() , 如果直接执行,那么name为Tom,但是如果赋给了fun,执行fun意味着window.fun,此时this发生了改变,this指window,所以结果为'window', 如果想打印"Tom", 可以直接在wait函数中执行this.show();

5、

window.val = 1;
var json = {
    val: 10,
    dbl: function () {
        this.val *= 2;
    }
}  
json.dbl();
var dbl = json.dbl;
dbl();    
json.dbl.call(window);
alert(window.val + json.val);

结果 "24"

分析:

json.db1 => db1中的this指的是json这个对象,=> 此时json中的val修改为20;

db1 = json.db1 ; db1() ; => 此时db1中的this指的是window; 所以window.val = 2;

json.dbl.call(window); => 此时this代表window, window.val*=2; window.val = 2 * 2 = 4

alert(window.val + json.val); => "4" + "20" = "24"

6、

(function () {
    var val = 1;
    var json = {
        val: 10,
        dbl: function () {
            val *= 2;
        }
    };
    json.dbl();
    alert(json.val + val);
})();

结果 "12"

分析

考察函数作用域,作用域链和this指向问题

json.db1 => val *= 2; db1中没有val,向上级作用域查找,val 为 1; 所以修改后val 为2

alert(json.val + val); => "10" + "2" = "12"