table {
border-collapse: collapse;
width: 100%;
margin-bottom: 1rem;
}
th, td {
border: 1px solid #ddd;
padding: 8px;
text-align: left;
}
th {
background-color: #f2f2f2;
}
tr:nth-child(even) {
background-color: #f9f9f9;
}
pre {
background-color: #f8f8f8;
padding: 15px;
border-radius: 4px;
overflow-x: auto;
}
1、在控制台中执行以下每行代码的结果是什么?为什么?var a; typeof a; var s = ‘1s’; s++; !!’false’; !!undefined; typeof -Infinity; 10 % “0”; undefined == null; false === “”; typeof “2E+2”; a = 3e+3; a++;
var a; typeof a;
:结果是 “undefined”。
因为声明变量但未初始化时,JavaScript 自动将其初始化为
undefined
,
typeof
对其操作返回 “undefined”。
var s = '1s'; s++;
:结果是
NaN
。
将字符串
'1s'
进行自增操作,无法转换为有效数字,运算结果为
NaN
。
!!'false';
:结果是
true
。
先将字符串
'false'
转换为布尔值,非空字符串为
true
,再取反两次仍为
true
。
!!undefined;
:结果是
false
。
undefined
转换为布尔值是
false
,取反两次后为
false
。
typeof -Infinity;
:结果是 “number”。
-Infinity
是数字类型的特殊值,
typeof
对其操作返回 “number”。
10 % "0";
:结果是
NaN
。
对字符串
"0"
进行取模运算,会尝试将其转换为数字
0
,10 对
0
取模结果是
NaN
。
undefined == null;
:结果是
true
。
在 JavaScript 中,
undefined
和
null
使用
==
比较时结果为
true
。
false === "";
:结果是
false
。
false
是布尔类型,
""
是字符串类型,使用
===
进行严格比较,类型不同结果为
false
。
typeof "2E+2";
:结果是 “string”。
"2E+2"
是字符串,
typeof
对其操作返回 “string”。
a = 3e+3; a++;
:结果是
3001
。
3e+3
表示
3000
,赋值给
a
后自增
1
变为
3001
。
2、执行
var v = v || 10;
后,v 的值是多少?分别尝试先将 v 设置为 100、0 或 null。
var v = v || 10;
当
v
先设为 100 时,执行
var v = v || 10;
后
v
为 100;
当
v
先设为 0 时,执行后
v
为 10;
当
v
先设为
null
时,执行后
v
为 10。
因为
0
和
null
属于假值(falsy),逻辑或运算会返回第二个值
10
,而
100
为真值(truthy),会返回
100
。
3、编写一个小程序,打印出乘法表。提示:使用一个循环嵌套在另一个循环中。
以下是使用 JavaScript 编写的打印乘法表的程序示例:
for (var i = 1; i <= 9; i++) {
var row = '';
for (var j = 1; j <= i; j++) {
row += j + '×' + i + '=' + (i * j) + ' ';
}
console.log(row);
}
这段代码使用了两层嵌套的
for
循环,外层循环控制行数,内层循环控制每行的列数,最终打印出乘法表。
4、以下每行代码在控制台中分别打印出什么内容?> parseInt(1e1); > parseInt(‘1e1’); > parseFloat(‘1e1’); > isFinite(0/10); > isFinite(20/0); > isNaN(parseInt(NaN));
10、1、10、true、false、true
5、以下代码会弹出什么内容?var a = 1; function f() { function n() { alert(a); } var a = 2; n(); } f();
由于变量提升,函数
n
中访问的
a
是函数
f
中的局部变量
a
,在
alert
时
a
已声明但未赋值,所以会弹出
undefined
。
6、以下示例都会弹出“Boo!”,请解释原因。示例1:var f = alert; eval(‘f(“Boo!”)’); 示例2:var e; var f = alert; eval(‘e=f’)(‘Boo!’); 示例3:(function(){ return alert;} )()(‘Boo!’);
在这三个示例中,最终都是调用了
alert
函数并传入了字符串
Boo!
作为参数。
示例1中,将
alert
函数赋值给变量
f
,然后使用
eval
执行
f('Boo!')
,即调用
f
也就是
alert
函数弹出
Boo!
;
示例2里,先把
alert
赋值给
f
,
eval('e=f')
将
f
的值(即
alert
函数)赋给
e
,再调用
e('Boo!')
,同样是调用
alert
弹出
Boo!
;
示例3中,立即执行函数返回
alert
函数,接着调用返回的
alert
函数并传入
Boo!
,所以也会弹出
Boo!
。
7、看这段代码:function F() { function C() { return this; } return C(); } var o = new F(); 这里的函数 C 内部的 this 值是指向全局对象还是对象 o?
this 值指向全局对象。因为函数 C 是在全局作用域中被调用的,而非作为对象的方法调用,也没有使用 `call`、`apply` 或 `bind` 改变 this 指向,所以 this 指向全局对象。
8、执行以下代码片段的结果是什么?function C(){ this.a = 1; return false;}console.log(typeof new C());
输出结果为
object
。因为构造函数中返回非对象值,会按常规行为返回
this
,
new C()
创建的是对象。
9、执行以下代码片段的结果是什么?
let c = [1, 2, [1, 2]];
c.sort();
c.join('--');
console.log(c);
代码先对数组
c
进行排序,由于默认排序是按字符排序,所以排序后为
[1, [1, 2], 2]
。
c.join('--')
虽将数组元素用
--
连接成字符串,但不改变原数组。
最后
console.log(c)
输出排序后的数组
[1, [1, 2], 2]
。
10、更新你的MyString()构造函数,使其包含一个reverse()方法。尝试利用数组有reverse()方法这一特性。
在
MyString
构造函数的原型上添加
reverse()
方法,示例代码如下:
function MyString(str) {
this.value = str;
}
MyString.prototype.reverse = function () {
return this.value.split('').reverse().join('');
};
11、假设 Math 对象不存在。创建一个 MyMath 对象,它还提供以下额外方法:MyMath.rand(min, max, inclusive) — 如果 inclusive 为 true(默认值),则生成一个介于 min 和 max 之间(包含两端)的随机数;MyMath.min(array) — 返回给定数组中的最小数字;MyMath.max(array) — 返回给定数组中的最大数字。
以下是实现该 MyMath 对象的 JavaScript 代码:
const MyMath = {
rand: function(min, max, inclusive = true) {
if (inclusive) {
return Math.floor(Math.random() * (max - min + 1)) + min;
}
return Math.random() * (max - min) + min;
},
min: function(array) {
return Math.min(...array);
},
max: function(array) {
return Math.max(...array);
}
};
以上代码实现了 MyMath 对象及其所需的方法。
12、创建一个名为shape的对象,该对象具有type属性和getType()方法。
以下是创建该对象的代码:
var shape = {
type: '特定类型',
getType: function() {
return this.type;
}
};
这里的
'特定类型'
可替换为你想要的具体类型。
13、定义一个
Triangle()
构造函数,其原型为
shape
。使用
Triangle()
创建的对象应具有三个自有属性
a
、
b
和
c
,分别代表三角形三条边的长度。
Triangle()
shape
Triangle()
a
b
c
以下是符合要求的代码实现:
var shape = {
name: 'Shape',
toString: function () {
return this.name;
}
};
function Triangle(a, b, c) {
this.a = a;
this.b = b;
this.c = c;
}
Triangle.prototype = shape;
14、向原型添加一个名为getPerimeter()的新方法。假设已有Triangle()构造函数,用于创建代表三角形的对象,该对象有a、b、c三个属性代表三角形三边长度,通过此方法可以计算并返回三角形的周长。
可按如下方式向
Triangle
构造函数的原型添加
getPerimeter()
方法:
Triangle.prototype.getPerimeter = function() {
return this.a + this.b + this.c;
};
此方法返回三角形的周长。
15、遍历对象 t,仅显示其自身的属性和方法(不包括原型的属性和方法)。
可以使用
hasOwnProperty()
方法来实现。示例代码如下:
for (var prop in t) {
if (t.hasOwnProperty(prop)) {
console.log(prop + '=' + t[prop]);
}
}
16、让代码 [1, 2, 3, 4, 5, 6, 7, 8, 9].shuffle(); 正常工作
要让这段代码正常工作,需要为数组原型添加一个
shuffle
方法。以下是实现该方法的示例代码:
Array.prototype.shuffle = function() {
let currentIndex = this.length, randomIndex;
// 当还有元素未被处理时
while (currentIndex != 0) {
// 随机选择一个剩余的元素
randomIndex = Math.floor(Math.random() * currentIndex);
currentIndex--;
// 交换当前元素和随机选择的元素
[this[currentIndex], this[randomIndex]] = [this[randomIndex], this[currentIndex]];
}
return this;
};
let result = [1, 2, 3, 4, 5, 6, 7, 8, 9].shuffle();
console.log(result);
上述代码通过 Fisher-Yates 洗牌算法为数组原型添加了
shuffle
方法,实现了数组元素的随机打乱。
17、利用画布进行练习,尝试在画布上绘制几个三角形、正方形和长方形。
以下是示例代码,用于在画布上绘制三角形、正方形和长方形:
// 定义三角形的三个点
var p1 = new Point(100, 100);
var p2 = new Point(300, 100);
var p3 = new Point(200, 0);
// 创建三角形实例并绘制
var t = new Triangle(p1, p2, p3);
t.draw();
// 创建长方形实例并绘制
var r = new Rectangle(new Point(200, 200), 50, 100);
r.draw();
// 创建正方形实例并绘制
var s = new Square(new Point(130, 130), 50);
s.draw();
18、使用画布示例进行练习。尝试不同的操作,例如:为更多形状添加构造函数,如梯形、菱形、风筝形和五边形。如果你想更多地了解画布标签,也创建一个圆形构造函数。它需要重写父类的 draw() 方法。
可以在原有的形状构造函数基础上,为梯形、菱形、风筝形、五边形添加构造函数。若要创建圆形构造函数,需重写父类的
draw()
方法。
以 JavaScript 为例,以下是简单的实现思路:
首先定义父类
Shape
及其
draw()
方法
然后创建圆形构造函数
Circle
并覆盖
draw()
方法
代码示例如下:
function Shape() {
this.draw = function() {
console.log('Drawing a shape');
}
}
function Circle() {
this.draw = function() {
console.log('Drawing a circle');
}
}
对于梯形、菱形、风筝形和五边形,也可类似地创建构造函数,根据各自的特点实现相应逻辑。
19、利用画布示例进行练习。尝试不同的操作,思考是否能想出另一种解决问题的方法并使用另一种继承类型。
可以考虑使用 ES6 中的类继承。ES6 引入了
class
关键字和
extends
语法,使继承的实现更加直观和简洁。示例代码如下:
class Shape {
constructor() {
this.name = 'Shape';
}
toString() {
return this.name;
}
}
class TwoDShape extends Shape {
constructor() {
super();
this.name = '2D shape';
}
}
class Triangle extends TwoDShape {
constructor(side, height) {
super();
this.side = side;
this.height = height;
this.name = 'Triangle';
}
getArea() {
return this.side * this.height / 2;
}
}
此外,还可以使用组合继承,它结合了原型链继承和构造函数继承的优点,既能继承原型上的属性和方法,又能继承构造函数里的属性。
20、利用 JavaScript 进行练习。尝试不同的操作,例如:使用
uber
让子类访问其父类的方法。添加功能,使父类能够跟踪它们的子类,可使用一个包含子类数组的属性。
uber
要实现让父类跟踪子类的功能,可在父类构造函数里添加一个数组属性来存储子类实例。以下是示例代码:
```javascript
function Shape() {
this.children = [];
}
// augment prototype
Shape.prototype.name = 'Shape';
Shape.prototype.toString = function () {
var const = this.constructor;
return const.uber ? this.const.uber.toString() + ', ' + this.name : this.name;
};
function TwoDShape() {
// 当创建 TwoDShape 实例时,将其添加到 Shape 的 children 数组中
Shape.prototype.children.push(this);
}
// take care of inheritance
var F = function () {};
F.prototype = Shape.prototype;
TwoDShape.prototype = new F();
TwoDShape.prototype.constructor = TwoDShape;
TwoDShape.uber = Shape.prototype;
// augment prototype
TwoDShape.prototype.name = '2D shape';
function Triangle(side, height) {
this.side = side;
this.height = height;
// 当创建 Triangle 实例时,将其添加到 TwoDShape 的 children 数组中
TwoDShape.prototype.children.push(this);
}
// take care of inheritance
var F = function () {};
F.prototype = TwoDShape.prototype;
Triangle.prototype = new F();
Triangle.prototype.constructor = Triangle;
Triangle.uber = TwoDShape.prototype;
在上述代码中,
Shape
构造函数里添加了
children
数组,每次创建
TwoDShape
实例时,该实例会被添加到
Shape.prototype.children
数组;每次创建
Triangle
实例时,该实例会被添加到
TwoDShape.prototype.children
数组,这样父类就能跟踪子类了。
##21、作为一个浏览器对象模型(BOM)练习,尝试编写代码实现以下两个功能。功能一:打开一个200x200的弹出窗口,然后将其缓慢逐渐调整为400x400大小。接着,让窗口像发生地震一样移动。你只需要使用一个move*()函数,进行一次或多次setInterval()调用,可能还需要一次setTimeout()/clearInterval()调用以停止整个过程。功能二:将当前日期/时间显示在文档标题中,并像时钟一样每秒更新一次。
# 练习示例代码
## 第一个练习:模拟地震效果
你可以先使用 `window.open()` 打开一个 200x200 的窗口,通过多次调用 `window.resizeBy()` 缓慢调整窗口大小,使用 `window.moveBy()` 和 `setInterval()` 让窗口移动模拟地震,最后用 `setTimeout()` 和 `clearInterval()` 停止。
```javascript
// 打开200x200的窗口
var win = window.open('', '', 'width=200,height=200');
// 逐渐调整窗口大小
var resizeInterval = setInterval(function() {
if (win.outerWidth < 400 || win.outerHeight < 400) {
win.resizeBy(1, 1);
} else {
clearInterval(resizeInterval);
// 开始移动窗口模拟地震
var moveInterval = setInterval(function() {
var x = Math.random() * 10 - 5;
var y = Math.random() * 10 - 5;
win.moveBy(x, y);
}, 100);
// 一段时间后停止移动
setTimeout(function() {
clearInterval(moveInterval);
}, 5000);
}
}, 100);
第二个练习:每秒更新页面标题
使用
setInterval()
每秒更新
document.title
。
setInterval(function() {
var now = new Date();
document.title = now.toLocaleString();
}, 1000);
22、以不同方式实现walkDOM()函数,并且让它接受一个回调函数,而非硬编码console.log()。
以下是实现代码:
function walkDOM(n, callback) {
do {
callback(n);
if (n.hasChildNodes()) {
walkDOM(n.firstChild, callback);
}
} while (n = n.nextSibling);
}
在这个新的
walkDOM
函数中,接受两个参数,
n
是要遍历的节点,
callback
是回调函数。在遍历过程中,使用传入的回调函数处理每个节点,而非硬编码的
console.log()
。
23、使用innerHTML移除内容很简单(document.body.innerHTML = ‘’),但并非总是最佳选择。当被移除的元素上附加了事件监听器时,问题就来了,在IE浏览器中这些事件监听器不会被移除,这会导致浏览器内存泄漏,因为它存储了对不存在事物的引用。实现一个通用函数,该函数在删除DOM节点之前先移除所有事件监听器。你可以遍历节点的属性并检查其值是否为函数。如果是,那么它很可能是像onclick这样的属性。在将元素从树中移除之前,你需要将其设置为null。
以下是实现该功能的示例代码:
function removeNodeWithListeners(node) {
for (let i = 0; i < node.attributes.length; i++) {
const attr = node.attributes[i];
if (typeof node[attr.name] === 'function') {
node[attr.name] = null;
}
}
if (node.hasChildNodes()) {
const children = Array.from(node.childNodes);
for (let child of children) {
removeNodeWithListeners(child);
}
}
node.parentNode.removeChild(node);
}
// 使用示例
// 假设你有一个要移除的节点
const elementToRemove = document.getElementById('yourElementId');
removeNodeWithListeners(elementToRemove);