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