js经典面试题
/* console.log(a) //undefined
var a = 12
function fn() {
console.log(a) //undefined
var a = 13
}
fn()
console.log(a) //12 */
/* console.log(a) //undefined
var a = 12
function fn() {
//注意:当前作用域里面没有a,就会到上一层作用域里面去寻找a
console.log(a) //12
a = 13
}
fn()
console.log(a) //13 */
/* // console.log(a) //报错
let a = 12 //let定义的变量,不存在提升
function fn() {
// console.log(a) //报错,注意:只要是当前作用域里面的存在的变量,就不会去上层作用域找了
let a = 13 //let定义的变量,不存在提升
}
fn()
console.log(a) */
/*// console.log(a) //报错
a = 12 // 定义变量可以不使用任何关键字,但是在定义该变量之前,不能使用该变量(这是一种不规范写法)
function fn() {
// console.log(a) //报错
let a = 13
}
fn()
console.log(a) */
/* var foo = 1
function bar() {
if (!foo) {
var foo = 10 //这个foo会提升到当前作用域的顶部定义
}
console.log(foo) //10
}
bar() */
/* var foo = 1
function bar() {
if (!foo) {
foo = 10
}
console.log(foo) //1
}
bar() */
/* var n = 0
function a() {
var n = 10 //11 12
function b() {
n++ //11 12
console.log(n) //11 12
}
b()
return b
}
var c = a()
c()
console.log(n) // 0 */
/* var a = 10
var b = 11
var c = 12
function test(a) {
a = 1
var b = 2 //方法里面的作用域b是2
c = 3 //会将外层作用域的c的值换掉
}
test(10)
console.log(a) // 10
console.log(b) // 11
console.log(c) // 3 */
// in关键字,用于检查一个属性是否包含在指定的对象中,如果包含就返回true
/* if (!('a' in window)) {
var a = 10
}
console.log(a)*/
/* var a = 4
function b(x, y, a) {
console.log(a) // 打印形参的值 3
arguments[2] = 10 // 又将形参a改成而来 10
console.log(a) // 10
}
a = b(1, 2, 3) //b方法,没有返回任何内容,默认返回undefined
console.log(a) //undefined */
/* var a = 9
function fn() {
// 1 2
a = 0
return function (b) {
// 5 + 1
// 5 + 0
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 */
/* var ary = [1, 2, 3, 4]
function fn(ary) {
ary[0] = 0 // 修改了原数组中第一位的值
ary = [0] // arr形参重新赋值一个新的数组
ary[0] = 100 // 形参arr再修改第一个位置的值,就跟原数组没关系
return ary
}
var res = fn(ary) // [100]
console.log(ary) // [0,2,3,4]
console.log(res) // [100] */
/* // 10->11
function fn(i) {
// 30
return function (n) {
console.log(n + i++) //41
}
}
var f = fn(10)
f(20) //30
fn(20)(40) //60
fn(30)(50) //80
f(30) //41 */
/* var num = 10 //60 65
var obj = { num: 20 } // 30
// 20 21
obj.fn = (function (num) {
//this->window
this.num = num * 3
num++ // 21
// 10
return function (n) {
this.num += n // 30
num++ //22 23
console.log(num) //22 23
}
})(obj.num)
var fn = obj.fn
console.log(fn) //function(n){this.num +=n;num++;console.log(num)}
fn(5) // 22
obj.fn(10) //23
console.log(num, obj.num) //65 30 */
/* var fullName = 'language'
var obj = {
fullName: 'javascript',
prop: {
getFullName: function () {
return this.fullName
}
}
}
console.log(obj.prop.getFullName()) // undefined
// 将getFullName方法传给test
var test = obj.prop.getFullName
console.log(test()) // language */
rest参数
// 方法的形参前面添加...,就是方法的rest参数
//...xxx 是函数的rest参数,用于接收剩余的实参,注意:通常情况下rest参数放在最后面
//rest参数,解决了函数中arguments对象不是数组类型的缺陷
function fun1(a,b,c,...args){
console.log(a,b,c);
console.log(args);
let arr = args.map(r=>r*2)
console.log(arr);
// console.log(arguments);
}
fun1(100,200,300)
fun1(100)
fun1(10,20,30,40,50,60,70,80)
console.log('------------------------');
// 在定义函数时,可以给参数设置默认值
function fun2(a,b=200,c=300){
console.log(a,b,c);
}
fun2(1,2,3)
fun2(1,2)
fun2(1)
fun2()
展开运算符
let arr1 = [11,22,33]
let arr2 = [44,55,66]
let arr3 = arr1.concat(arr2)
console.log(arr3);
// ...在这里就是展开运算符,在这里,展开运算符用于展开数组中的所有成员。
let arr4 = [...arr1,...arr2]
console.log(arr4);
console.log('---------------------------');
let obj1 = {
a:100,
b:200
}
let obj2 = {
c:300,
d:400,
a:500
}
// ...在这里,用于将对象的所有属性展开,并返回一个全新的对象
let obj3 = {...obj1,...obj2}
console.log(obj3);
结构赋值
//定义变量
let no = 1001
let name = '周杰伦'
let age = 30
let sex = '男'
//定义对象
let stu1 = {
//属性名:属性值
//这里的属性值是上面定义的变量保存的值
no:no,
name:name,
age:age,
sex:sex
}
console.log(stu1);
console.log('-----------------');
//对象的属性名和属性值的标识相同时,可以省略属性值
let stu2 = {
// 是no:no的简写
no,
name,
age,
sex
}
console.log(stu2);
console.log('--------------------------------------');
let stu3 = {
username:'周杰伦',
userage:30,
usersex:'男',
car:{
carName:'奔驰',
carPrice:'100W'
}
}
//过去我们这样写
// let username = stu3.username
// let userage = stu3.userage
let usersex = stu3.usersex
//现在我们这样写(解构赋值)
// usersex:usersex2 表示在解构的时候对变量名进行重命名
let {username,userage,usersex:usersex2} = stu3
console.log(username,userage,usersex,usersex2);
console.log('-----------');
// let {car} = stu3
// let {carName,carPrice} = car
let {car:{carName,carPrice}} = stu3 //这一行代码,最终会编译成下面的两行代码
// let carName = stu3.car.carName
// let carPrice = stu3.car.carPrice
console.log(carName,carPrice);
let arr = [11,22,33,44,55]
// 解构数组中的元素,采用的[]
let [a,b] = arr
console.log(a,b);
原型对象
// 构造函数(类)有原型对象,其实就是构造函数身上的一个自带属性,这个属性是:prototype
// 对象也有原型对象,其实就是对象身上的一个自带属性,这个属性是:__proto__
// 所有同类型的对象身上的原型对象属性,都指向类的原型对象属性。
// 类和对象的原型对象身上挂的方法,对象可以直接使用,不需要经过原型对象。
function Student(name,age,sex){
this.name = name
this.age = age
this.sex = sex
// 如果将方法直接定义在类里面,将来根据这个类创建的每个对象,都要创建自己独立的这些方法
// 如果要创建很多对象,对内存的开销会很大。
/* this.sayHi = function(){
console.log(`Hi!我叫${this.name},今年${this.age}岁,性别是${this.sex}`);
}
this.study = function(time){
console.log(`Hi!我叫${this.name},我每天学习${time}小时`);
}
this.play = function(time){
console.log(`Hi!我叫${this.name},我每天玩${time}小时`);
} */
}
// 我们可以将类的方法,添加到类的原型对象身上
Student.prototype.sayHi = function(){
console.log(`Hi!我叫${this.name},今年${this.age}岁,性别是${this.sex}`);
}
Student.prototype.study = function(time){
console.log(`Hi!我叫${this.name},我每天学习${time}小时`);
}
Student.prototype.play = function(time){
console.log(`Hi!我叫${this.name},我每天玩${time}小时`);
}
let s1 = new Student('张三',20,'男')
let s2 = new Student('李四',22,'女')
let s3 = new Student('王五',24,'男')
// 查了Student类的原型对象
// console.log(Student.prototype);
// 查看三个对象的原型对象 -- 你会发现,长得一样。
// console.log(s1.__proto__);
// console.log(s2.__proto__);
// console.log(s3.__proto__);
s1.sayHi()
s1.study(8)
s1.play(3)
console.log('------------------');
s2.sayHi()
s2.study(6)
s2.play(6)
console.log('------------------');
s3.sayHi()
s3.study(10)
s3.play(1)
class定义类
// ES6之前,定义类型的方式
function Student(name,age,sex){
//定义属性
this.name = name
this.age = age
this.sex = sex
//定义方法
this.sayHi = function(){
console.log(`我是学生,姓名是${this.name},年龄${this.age}岁,性别是${this.sex}`);
}
}
let s1 = new Student('张三',20,'男')
s1.sayHi()
// 从ES6以后,添加了class关键字,定义类型
// 注意:该语法的兼容性不高,ie9以下都不支持,部分其他低版本的浏览器也存在兼容性问题。
class Teacher{
//构造函数(通过构造函数,给属性赋值)
constructor(name,age,sex){
this.name = name
this.age = age
this.sex = sex
}
//定义方法 (将方法定义在类身上)
sayHi = function(){
console.log(`我是老师,姓名是${this.name},年龄${this.age}岁,性别是${this.sex}`);
}
//(将方法定义在原型对象上)
sayHello(){
console.log(`Hello,姓名是${this.name},年龄${this.age}岁,性别是${this.sex}`);
}
}
let t1 = new Teacher('李四',22,'女')
console.log(t1);
t1.sayHi()
t1.sayHello()
ES6的继承方式
// 定义动物类
class Animal{
// 定义动物类的构造函数
constructor(nickName,sex,age){
// 利用构造函数给动物的属性赋值
this.nickName = nickName
this.sex = sex
this.age = age
}
// 定义方法,并将方法添加到原型上
sayHi(){
console.log(`Hi!我叫${this.nickName},今年${this.age}岁,我是${this.sex}`);
}
eat(str){
console.log(`我喜欢吃${str}`);
}
sleep(time){
console.log(`我每天睡${time}个小时`);
}
}
// 创建一个动物对象
let a1 = new Animal('佩奇','女生',6)
a1.sayHi()
a1.eat('蛋糕')
a1.sleep(12)
console.log('-----------------------------------------');
// 定义狗狗类
// class关键字,定义类;extends关键字继承类,
// 采用这种方式,Animal的方法,此刻已经全部继承过来了。
class Dog extends Animal{
constructor(nickName,sex,age,type){
//调用父类的构造函数,而且必须放在子类构造函数的最上方
super(nickName,sex,age)
this.type = type
}
play(){
console.log(`我一只${this.type},我会玩飞盘`);
}
}
let d1 = new Dog('旺财','男生',6,'拉布拉多')
d1.sayHi()
d1.eat('牛肉')
d1.sleep(8)
d1.play()
核心案例
//定义DVD类
function DVD(id,name,count,state,price){
//编号
this.id = id
//名称
this.name = name
//人气值
this.count = count
//状态值
this.state = state
//日租金
this.price = price
}
//DVD计算总租金的方法
DVD.prototype.totalPrice = function(days){
let money = this.price*days //定义变量,用于保存总租金
// 租用天数超过10天,打八折
// 租用天数超过5天,打9折
if(days>=10){
money *= 0.8
}else if(days>=5){
money *= 0.9
}
return money
}
//定义一个dvd管理对象
let dvdManager = {
//定义dvd数组
dvds:[
new DVD(1001,'非诚勿扰',5,false,1.5),
new DVD(1002,'超级战舰',3,true,2),
new DVD(1003,'火星救援',8,true,1),
new DVD(1004,'上海堡垒',2,false,0.5)
],
//查看dvd信息的方法
show:function(){
let str = "编号 DVD名称 人气值 日租金 状态\n"
this.dvds.forEach(d=>{
str+=`${d.id} ${d.name} ${d.count} ¥${d.price} ${d.state?'借出':'未借'}\n`
})
alert(str)
},
//添加dvd信息的方法
add:function(){
//输入dvd的名称
let name = prompt('请输入DVD的名称:')
// 检查输入的dvd是否已经存在了
if(this.getDVDByName(name)) {
alert('您输入的DVD已经存在!')
//使用递归
arguments.callee.call(this)
return //返回当前方法
}
// 输入dvd的日租金
let price = parseFloat(prompt('请输入DVD的日租金:'))
//创建dvd对象
let dvd = new DVD(parseInt(Math.random()*9000)+1000,
name,0,false,price)
//将dvd对象添加到数组中
this.dvds.push(dvd)
alert('添加成功!')
},
//删除dvd的方法
delDVD:function(){
// 输入dvd的名称
let name = prompt('请输入DVD的名称:')
// 根据dvd的名称,获取dvd对象
let dvd = this.getDVDByName(name)
// 判断dvd是否存在
if(dvd){
//判断dvd的状态是否为借出
if(dvd.state){
alert('该DVD已经借出,无法删除!')
}else{
// 根据dvd的名称,找出该dvd的下标
let index = this.dvds.findIndex(d=>d.name===name)
// 根据下标删除
this.dvds.splice(index,1)
alert('该DVD删除成功!')
}
}else{
alert('您输入的DVD不存在!')
}
},
//根据dvd的名称,返回dvd对象
getDVDByName:function(name){
return this.dvds.find(r=>r.name===name)
},
//借出DVD的方法
jieDVD:function(){
// 输入dvd的名称
let name = prompt('请输入DVD的名称:')
// 根据dvd的名称,获取dvd对象
let dvd = this.getDVDByName(name)
// 判断dvd是否存在
if(dvd){
// 判断dvd的状态是否为借出
if(dvd.state){
alert('该DVD已经借出,您可以换别的!')
}else{
//设置该DVD的状态为借出
dvd.state = true
//设置该DVD的借出次数加1
dvd.count++
alert('成功借出!')
}
}else{
alert('对不起!您输入的DVD不存在!无法借出!')
}
},
//归还DVD的方法
huanDVD:function(){
// 输入dvd的名称
let name = prompt('请输入DVD的名称:')
// 根据dvd的名称,获取dvd对象
let dvd = this.getDVDByName(name)
// 判断dvd是否存在
if(dvd){
// 判断dvd的状态是否为借出
if(dvd.state){
dvd.state = false //重新设置dvd的状态为未借
let days = Math.ceil(prompt('请输入租用天数:'))
// 调用dvd计算租金的方法
let money = dvd.totalPrice(days)
alert('归还成功!您需要支付'+money+'元租金')
}else{
alert('对不起!您输入的DVD未借出!无法归还!')
}
}else{
alert('对不起!您输入的DVD不存在!无法归还!')
}
},
//系统主菜单
menu:function(){
let no = prompt('*****迷你DVD管理系统*****\n1.查看DVD 2.租售DVD 3.归还DVD 4.添加DVD 5.删除DVD 0.退出系统')
switch(no){
case '1':
// 调用查看DVD信息的方法
this.show()
break;
case '2':
// 调用借出DVD的方法
this.jieDVD()
break;
case '3':
// 调用归还DVD的方法
this.huanDVD()
break;
case '4':
// 调用添加DVD的方法
this.add()
break;
case '5':
// 调用删除DVD的方法
this.delDVD()
break
default:
alert('成功退出系统!欢迎下次使用!')
return //跳出当前方法
}
// 递归调用菜单方法,并重新指定this的指向。
arguments.callee.call(this)
}
}
// 调用dvd管理对象的主菜单方法
dvdManager.menu()
复习 arguments
//arguments是函数内部的一个对象,该对象可以接收函数的所有参数
// 该对象有一个方法callee方法,该方法指向当前函数本身
例如:
function fn1(){
console.log(arguments);
console.log(arguments.callee===fn1);
}
fn1(100,200,300,400)
结果为:
箭头函数中的this
let lh = {
name: '鹿晗',
age: 20,
//朋友
friend: {
name: '关晓彤',
age: 18,
//车
getCar: function () {
return {
name: '奔驰',
age: 3,
sayHi: function () {
// 方法里面的this,指向方法的调用者,比如:obj.fn,那么fn里面的this指向obj
console.log(`一辆${this.name}车,车龄是${this.age}年`);
},
sayHello: () => {
// 箭头函数中没有this,如果使用了this,会向上一层方法寻找this,如果上面已经没有方法了
// 就指向window对象
console.log(`一辆${this.name}车,车龄是${this.age}年`);
}
}
}
}
}
lh.friend.getCar().sayHi()
lh.friend.getCar().sayHello()
结果为: