1. 请写出弹出框的值,并解释为什么?

题目

alert(a);
a();
var a = 3;
function a() {
  alert(10);
}
alert(a);
a = 6;
a();

TIP

这一题是考变量和函数提升的问题;
函数是一等公民,在所有东西之前;
js在执行的时候,把所有的东西都丢到一个匿名函数中执行,相当于js的主入口;

题解

执行顺序:

// 变量提升
var a;
function a() {
  alert(10);
}
alert(a); // function a(){alert(10)}
a();      // 执行a函数,得到10
a = 3;   // a赋值成3
alert(a); // 输出3
a = 6;  // a赋值成6
a();    // 6是数字,不是函数所以会报错

SO, 答案为
function a(){alert(10)}
10
3
报错, a is not a function

温馨提示

function a(){
  alert(1)
}
if(false){
  function a(){
    alert(2)
  }
}
a() //=> alert(1)


if(false){
  function a(){
    alert(1)
  }
}
// 此时a也有提升, 但是没有进入这个if语句。a先定义了,但是并为赋值,所以a是undefined 
a() // a is not a function 

扩展 1-1

var x = 1, y = 0, z = 0;
function add(x) {
  return (x = x + 1);
}
y = add(x);
console.log(y);
function add(x) {
  return (x = x + 3);
}
z = add(x);
console.log(z);

题解

执行顺序:

// 秉持着先定义后执行的定律
var x, y, z;
function add(x) {
  return (x = x + 1);
}
// 此时add函数被覆盖
function add(x) {
  return (x = x + 3);
}
// 给x, y, z 赋值
x = 1, y = 0, z = 0;

y = add(x); // 1+3
console.log(y); // 4
z = add(x); // 1+3 
console.log(z); // 4

温馨提示

// add(x), 形参为x,其实是在构造函数里面var了个新的变量接收外面传进来的,如
function add(x) {
  var _x = x + 3;
  return _x;
}
// 所以add函数里面的x变成了4,跟外面的x无关。

SO, 答案为
4
4


2. 请写出如下输出值,并解释为什么?

this.a = 20;
function go() {
  console.log(this.a);
  this.a = 30;
}
go.prototype.a = 40;
var test = {
  a: 50,
  init: function(fn) {
    fn();
    console.log(this.a);
    return fn;
  }
};
console.log((new go()).a);
test.init(go);
var p = test.init(go);
p();

DANGER

globalThis: 永远指向this;
node里的this和浏览器里的this不一样;
window.window, window.window 都是指向window;
this是调用它,就指向谁,没有人调用的话就指向window;
在严格模式下,如果没有宿主,使用this就会报错;
严格模式只对当前作用域生效;
new的时候,构造函数的优先级比原型链高,不new的话原型链压根没什么用;
原型链解决了内存共享的问题;

题解

this.a = 20;
function go() {
  console.log(this.a);
  this.a = 30;
}
go.prototype.a = 40;
var test = {
  a: 50,
  init: function(fn) {
    fn();
    console.log(this.a);
    return fn;
  }
};

/**
 * 1. 实例化go,先执行go函数,此时构造函数中没有a,那就去原型链上找,所以是40
 * 2. 找到了再赋值成30
 */
console.log((new go()).a); // 40 -> 30 

/**
 * 1. 把go函数作为形参传入,执行该函数。fn() 也就是 go(),此时没有宿主,那么就是window.go(), this指向window,所以为20。 将window下的a 赋值为 30
 * 2. test.init(), test是宿主,所以此时ths.a, this 指向 test,所以为50 
 */
test.init(go); // 20 -> 50

/**
 * 1. 按照上面一样执行,但是此时window下的a 已经被更改成了30 
 * 2. test.init() 一样照旧,指向test,所以还是50 
 */
var p = test.init(go); // 30 -> 50

/**
 * 匿名函数fn,执行fn, this指向window,所以是30
 */
p(); // 30

SO, 答案为
40
30
20
50
30
50
30

扩展 2-1

var num = 1;
function yideng(){
  'use strict'
  console.log(++this.num);
}
function yideng2(){
  console.log(++this.num);
}

(function(){
  'use strict';
  yideng2();
})();

yideng();

/**
 * 严格模式只对当前作用域生效
 * 1. 立即执行函数,执行yideng2(), 2
 * 2. yideng(),严格模式下没有this,所以拿不到num, 会报错
 */
 // 输出结果: 2  Cannot read property 'num' of undefined

温馨提示

++num, 先自增再执行。
num++,先执行再自增。

扩展 2-2

function C1 (name) {
  if (name) this.name = name;
}
function C2 (name) {
  this.name = name;
}
function C3 (name) {
  this.name = name || 'fe';
}
C1.prototype.name = 'yideng';
C2.prototype.name = 'lao';
C3.prototype.name = 'yuan';
console.log((new C1().name) + (new C2().name) + (new C3().name));

/**
 * 构造函数没有的就会从原型链上找
 * 1. new C1().name   -> 构造函数没有,原型链上有, 所以为yideng
 * 2. new C2().name   -> 没有传值进来,这里意味着name为undefined,构造函数有了,就不从原型链上拿。
 * 3. new C2().name   -> 有传进来就用传进来的,没有的话就用fe
 */

// 输出结果: yidengundefinedfe

3. 请写出如下点击li的输出值,并用三种办法正确输出li里的数字

<ul>
  <li>1</li>
  <li>2</li>
  <li>3</li>
  <li>4</li>
  <li>5</li>
  <li>6</li>
</ul>
<script type='text/javascript'>
  var list_li = document.getElementsByTagName('li');
  for(var i = 0; i < list_li.length; i++){
    list_li[i].onclick = function(){
      console.log(i);
    }
  }
</script>

题解

// 把var改成let
for(var i = 0; i < list_li.length; i++){
  list_li[i].onclick = function(){
    console.log(i + 1);
  }
}

// 闭包
var list_li = document.getElementsByTagName('li');
for(var i = 0; i < list_li.length; i++){
  (function(i) {
    list_li[i].onclick = function(){
      console.log(i + 1);
    }
  })(i);
}

// 一步到位,直接更改log  
console.log(this.innerHTML);

4. 请写出输出值,并解释为什么?

function test(m) {
  m = {v: 5}
}
var m = {k: 30};
test(m);
alert(m.v);

/**
 * 引用类型,是按内存地址传递的。
 * test(m),这里相当于是在test函数内部var了个m,赋值给了m,跟window下的m没有关系。window下的m此时还是{k: 30};
 */
// 输出结果: undefined 

5. 请写出代码执行结果,并解释为什么?

function yideng() {
  console.log(1);
}
(function (){
  if (false) {
    function yideng() {
      console.log(2);
    }
  }
  yideng();
})();

题解

/**
 * 立即执行函数,虽然是false,块级作用域函数声明会上浮。此时yideng 已经被声明了。
 * 再执行yideng(), 但是yideng只声明了尚未赋值,所以会报错, undefined is not a function 
 * 变成如下:
 */
(function () {
  var yideng;
  if (false) {
    function yideng() {
      console.log(2);
    }
  }
})

// 输出结果: undefined is not a function 

6. 请用一句话算出0-100之间学生的学生等级,如90-100输出1等生,80-90输出2等生,以此类推。不允许使用if switch等。

这一题只要找到窍门,就很好算; 首先 0-100 之间是 10 等
然后每相差10, 为1等 可以推算出这个等级是 10-Math.floor(n/10)
如果是 100,这个公式就推成 0 等了 这里再判断下边界值的情况 再用个三元运算判断下

题解

function score(num) {
  return 10 - Math.floor(num/10) === 0 ? '1' : 10 - Math.floor(num/10);
}

7. 请用一句话遍历变量a, var a = 'abc'

题解

console.log(Array.from(a));
console.log(a.split(''));
console.log([...a]);
console.log([...new Set(a)]);

8. 请在下面写出Javascript面向对象变成的混合式继承。并写出ES6版本的继承。

有求:汽车是父类,Cruze是子类。父类有颜色,价格属性,有售卖的方法。Cruze子类实现父类颜色是红色,价格是140000,售卖方法实现输出如下语句:将红色的Cruze卖给小王的价格是14万。

题解

// ES6 写法
class Car {
  constructor(color, price) {
    this.color = color || '';
    this.price = price || '';
  }
  sell() {
    console.log(`${this.color}的Cruze卖给了小王价格是${this.price}`);
  }
}

class Cruze extends Car {
  constructor (color, price) {
    super(color, price);
  }
}

let cruze = new Cruze('red', 140000);
cruze.sell();

// ES5 写法
// 定义父类 Car
function Car(color, price){
  this.color = color || '';
  this.price = price || '';
}
Car.color = 'red'

// 实现售卖的方法,把 sell 方法挂到 prototype 上
Car.prototype.sell = function(){
  console.log(`${this.color}的Cruze卖给了小王价格是${this.price}`);
}

// 子类
function Cruze(color ,price){
  // 没有 super,只能通过自己手动绑定 this,相当于执行了 super
  Car.call(this, color ,price) //把 color 和 price 丢给 Car
}
// 子类的静态属性继承
// Object.entries方法返回一个给定对象自身可枚举属性的键值对数组
for (let [key, value] of Object.entries(Car)) {
// console.log(`${key}: ${value}`);
  Cruze[key] = value
}

// 子类继承原型链
// Cruze.prototype = Car.prototype
//  这样是不可取的,原型链按引用传递,在子类会污染父类

// Cruze.prototype = new Car() 
// 这样也是不可取的,new 的时候,执行了一次,再 new 出来使用的时候又执行了一次,因此构造器执行了两次


// var _proto = Object.create(Car.prototype) =>创建一个父类原型副本
// _proto.constructor = Cruze =>修正这个原型副本的指向自己
// Cruze.prototype = _proto =>吧这个原型副本挂到自己的原型链上
// 这种方式可以,但是有点 low =>这样如果别人改了你的原型链,那又挂了
// 因此,再创建的时候,直接把constructor 指向自己,并且直接禁止改变

Cruze.prototype = Object.create(Car.prototype,{
  constructor:{
    value:Cruze,
    writable:false
  },
  sell:{ //子类重载
    value:function(){
      console.log('子类自己的方法')
    }
  },
  fun:{ //子类自己的,
    get(){},
    set(){}
  }
})

const test = new Cruze('red','14')
console.log(test)

9. 考考你的基础怎么样?

var regex = /yideng/g;
console.log(regex.test('yideng'));
console.log(regex.test('yideng'));
console.log(regex.test('yideng'));
console.log(regex.test('yideng'));

// 输出结果: true false true false

TIP

如果正则表达式设置了全局标志,test() 的执行会改变正则表达式 lastIndex属性。连续的执行test()方法,后续的执行将会从 lastIndex 处开始匹配字符串,(exec() 同样改变正则本身的 lastIndex属性值)。
每次匹配到了以后会记录lastIndex, 从lastIndex再开始匹配

扩展 9-1

console.log([, ,].length); // 2, 以最后一个逗号结尾,剩下的不管了

10. 写出代码执行结果,并解释为什么?

var yideng = function yideng() {
  yideng = 1;
  console.log(typeof yideng);
}
yideng();
yideng = 1;
console.log(typeof yideng);

题解

// 1. 普通函数定义的情况下可以被覆盖
function a() {}
var a = function() {}

// 2. 变量名 + 函数名,不能被覆盖
var a = function a () {}

// 3. 变量名 + 函数名,里面又定义了一次函数时,可以被覆盖
var a = function a () {
  function a () {}
}
// 里面定义函数,其实就相当于是在函数内部var a了

SO, 答案为
'function'
'number'

11. 【仔细思考】写出如下代码执行结果,并解释为什么?

var length = 10;
function fn () {
  console.log(this.length);
}
var yideng = {
  length: 5,
  method: function(fn) {
    fn();
    arguments[0]();
  }
};
yideng.method(fn, 1);

/**
 * 1. 执行fn函数, 宿主是window,此时window上已经var了个length了。默认window.length为0,是指iframe的数量。
 * 2. arguments[0], 即arguments.0, 那么宿主是arguments。即 this.length 也就是 arguments.length。
 */
 
// 输出结果: 10 2 
Last Updated:
Contributors: kk