//作业1 四种创建对象方式,以及优缺点
/*1.js基本的创建对象方式 好处:简单,方便 弊端:无法量产*/
/*2.利用函数来创建对象 工厂模式 好处:可以量产对象 弊端:通过工厂模式创建出来的对象,无法明确对应类型*/
/*3.构造函数模式:来模拟类!!! 利用js this指向的问题 创建一个学生类 构造函数模式的好处: 1.明确了类型!!!弊端:共用的方法,占据内存!!*/
/*4.原型模式:在创建构造函数的过程中,将共有的方法放入构造函数的原型里*/
//作业2 构造函数 长方形类(有参数)
// 属性 width
// height
// 原型方法:输出面积
// 创建两个不同的长方形对象,分别设置宽度和高度 ,然后输出对应面积
function rectangle(width, height) {
this.width = width
this.height = height
}
rectangle.prototype.calculate = function () {
sums = this.width * this.height
return `${sums}m`
}
let sums = new rectangle(60, 50)
let sum = sums.calculate()
let sum1 = new rectangle(70, 80)
console.log("长方形面积:" + sum, "长方形面积1:" + sum1.calculate())
//作业3 坦克类(有参数)
// 属性:name
// color
// hp:100
// number:100 子弹数量
// 原型方法:攻击()参数:传入敌方坦克 对方血量减少,自己子弹减少
// 展示基本信息() 剩余血量以及剩余子弹
// 效果:创建两个不同的tank对象 舒克和贝塔, 调用舒克的攻击方法攻击贝塔三次,然后输出舒克的基本信息以及贝塔的基本信息
function tank(color, name) {
this.color = color
this.name = name
this.hp = 100
this.number = 100
}
tank.prototype.attack = function (tank) {
//第一个参数参入敌方坦克 第2个传入自己
if ("hp" in tank) {
tank.hp--
this.number--
} else {
alert('攻击对象要有hp属性')
}
}
tank.prototype.showinfo = function () {
console.log(
` 颜色:${this.color},名字:${this.name},血量:${this.hp},子弹:${this.number}`
)
}
let tank1 = new tank("blue", "贝塔")
let tank2 = new tank("red", "舒克")
for (let i = 0
tank2.attack(tank1)
}
tank1.showinfo()
tank2.showinfo()
//作业4
//题目:4.实现一个 交换牌和展示牌功能
// 人类,创建一个人,
// 左手:牌的信息 null
// 右手:牌的信息 null
// 抓牌(左手右手同时抓)方法
// 展示牌:左手是什么 右手是什么
// 交换牌:左右手牌的交换
// 构造函数: 人
// 属性: 左手: 字符串 什么花色得什么牌
// 右手: 字符串
// 原型方法:抓牌
// 展示牌
// 交换牌
/*
思路:通过两个数组定义卡牌
对象:人 左手牌 右手牌
*/
/*function people(LeftBoard,RightBoard){
this.LeftBoard=LeftBoard
this.RightBoard=RightBoard
}
people.prototype.ScratchCard=function(){//抓牌 左手牌 右手牌
const Board=[1,2,3,4,5,6,7,8,9]
const Color=['梅花','红桃','方块','黑桃']
let LefthandIndex =Math.floor(Math.random() * Board.length)
let LeftColorIndex=Math.floor(Math.random() * Color.length)
let RighthandIndex =Math.floor(Math.random() * Board.length)
let RightColorIndex=Math.floor(Math.random() * Color.length)
this.LeftBoard=`${Color[LeftColorIndex]} ${Board[LefthandIndex]} `
this.RightBoard=`${Color[RighthandIndex]} ${Board[RightColorIndex]}`
}
people.prototype.ShowBoard=function(){//展示
console.log("左手:"+this.LeftBoard,"右手:"+this.RightBoard)
}
people.prototype.exchange=function(LeftBoard,RightBoard){//交换
this.LeftBoard=RightBoard
this.RightBoard=LeftBoard
}
let boy=new people("null","null")
boy.ScratchCard()
boy.ShowBoard()
boy.exchange(boy.LeftBoard,boy.RightBoard)
boy.ShowBoard()
boy.exchange(boy.LeftBoard,boy.RightBoard)
boy.ShowBoard()
function People(name) {
this.name = name
this.lefthand = ""
this.righthand = ""
}
People.prototype.grab = function () {
//抓牌 给左手牌赋值一张给右手牌赋值一张
let c1 = new Card()
this.lefthand = c1.color + c1.size
let c2 = new Card()
while (true) {
if (c2.color + c2.size == c1.color + c2.size) {
c2 = new Card()
} else {
break
}
this.righthand = c2.color + c2.size
}
}
People.prototype.showCard = function () {
//展示牌
console.log("左手为" + this.lefthand, "右手为" + this.righthand)
}
People.prototype.ChangeCard = function () {
//交换卡牌
let card = this.lefthand
this.lefthand = this.righthand
this.righthand = card
}
//定义一个牌的构造函数
function Card() {
let sizes = [
"2",
"3",
"4",
"5",
"6",
"7",
"8",
"9",
"10",
"J",
"Q",
"K",
"A",
]
let colors = ["黑桃", "红桃", "梅花", "方片"]
this.size = sizes[parseInt(Math.random() * sizes.length)]
this.color = colors[parseInt(Math.random() * colors.length)]
}
var p1 = new People("赌神")
p1.grab()
p1.showCard()
p1.ChangeCard()
p1.showCard()
面试题
1.三栏布局方式两边固定中间自适应
margin 负值法:左右两栏均左浮动,左右两栏采用负的 margin 值。中间栏被宽度为
100%的浮动元素包起来
自身浮动法:左栏左浮动,右栏右浮动,中间栏放最后
绝对定位法:左右两栏采用绝对定位,分别固定于页面的左右两侧,中间的主体栏用
左右 margin 值撑开距离。
flex 左右固定宽 中间 flex:1
2.css 都有哪些标签选择器?
标签选择器(div p)
类选择器( class="header")
ID 选择器(id="header")
全局选择器(*)
伪类选择器(:after, :before)
继承选择器(div p)
后代选择器(A>B)
任意选择器(A,B)
3.JavaScript 中什么是基本数据类型什么是引用数据类型?以及各个数据类型是如何存储的
基本数据类型有:
Number; String;Boolean;Null;Undefined;Symbol(ES6 新增数据类型);
bigInt
引用数据类型统称为 Object 类型,细分的话有
Object;Array;Date;Function;RegExp
基本数据类型的数据直接存储在栈中;而引用数据类型的数据存储在堆中,在栈中保
存数据的引用地址,这个引用地址指向的是对应的数据,以便快速查找到堆内存中的
对象。
顺便提一句,栈内存是自动分配内存的。而堆内存是动态分配内存的,不会自动释放。
所以每次使用完对象的时候都要把它设置为 null,从而减少无用内存的消耗
4.在 JS 中为什么 0.2+0.1>0.3?
因为在 JS 中,浮点数是使用 64 位固定长度来表示的,其中的 1 位表示符号位,11 位
用来表示指数位,剩下的 52 位尾数位,由于只有 52 位表示尾数位。
而 0.1 转为二进制是一个无限循环数 0.0001100110011001100......(1100 循环)
小数的十进制转二进制方法:
https://jingyan.baidu.com/article/425e69e6e93ca9be15fc1626.html
要知道,小数的十进制转二进制的方法是和整数不一样的,推荐看一看
由于只能存储 52 位尾数位,所以会出现精度缺失,把它存到内存中再取出来转换成十
进制就不是原来的 0.1 了,就变成了 0.100000000000000005551115123126,而为
什么 02+0.1 是因为
// 0.1 和 0.2 都转化成二进制后再进行运算
0.00011001100110011001100110011001100110011001100110011010 +
0.0011001100110011001100110011001100110011001100110011010 =
0.0100110011001100110011001100110011001100110011001100111
// 转成十进制正好是 0.30000000000000004
5.那为什么 0.2+0.3=0.5 呢?
// 0.2 和 0.3 都转化为二进制后再进行计算
0.001100110011001100110011001100110011001100110011001101 +
0.0100110011001100110011001100110011001100110011001101 =
0.10000000000000000000000000000000000000000000000000001 //尾数为大
于 52 位
// 而实际取值只取 52 位尾数位,就变成了
0.1000000000000000000000000000000000000000000000000000 //0.5
0.2 和 0.3 分别转换为二进制进行计算:在内存中,它们的尾数位都是等于 52 位的,
而他们相加必定大于 52 位,而他们相加又恰巧前 52 位尾数都是 0,截取后恰好是
0.1000000000000000000000000000000000000000000000000000 也就是 0.5
6. 那既然 0.1 不是 0.1 了,为什么在 console.log(0.1)的时候还是 0.1 呢?
在 console.log 的时候会二进制转换为十进制,十进制再会转为字符串的形式,在转
换的过程中发生了取近似值,所以打印出来的是一个近似值的字符串
7.判断数据类型有几种方法?
typeof
缺点:typeof null 的值为 Object,无法分辨是 null 还是 Object
instanceof
缺点:只能判断对象是否存在于目标对象的原型链上
constructor
Object.prototype.toString.call()
一种最好的基本类型检测方式 Object.prototype.toString.call()
string 、boolean 、 number 、 undefined 、 array 、 function 、 object 、
date 、 math 数据类型。
缺点:不能细分为谁谁的实例
// -----------------------------------------typeof
typeof undefined // 'undefined'
typeof '10' // 'String'
typeof 10 // 'Number'
typeof false // 'Boolean'
typeof Symbol() // 'Symbol'
typeof Function // ‘
function'
typeof null // ‘
Object’
typeof [] // 'Object'
typeof {} // 'Object
// -----------------------------------------instanceof
function Foo() { }
var f1 = new Foo()
var d = new Number(1)
console.log(f1 instanceof Foo)
console.log(d instanceof Number)
console.log(123 instanceof Number)
型
// -----------------------------------------constructor
var d = new Number(1)
var e = 1
function fn() {
console.log("ming")
}
var date = new Date()
var arr = [1, 2, 3]
var reg = /[hbc]at/gi
console.log(e.constructor)
console.log(e.constructor.name)
console.log(fn.constructor.name) // Function
console.log(date.constructor.name)// Date
console.log(arr.constructor.name) // Array
console.log(reg.constructor.name) // RegExp
//-----------------------------------------Object.prototype.toString.call()
console.log(Object.prototype.toString.call(undefined))
Undefined]"
console.log(Object.prototype.toString.call(null))
console.log(Object.prototype.toString.call(123))
console.log(Object.prototype.toString.call("abc"))
console.log(Object.prototype.toString.call(true))
function fn() {
console.log("ming")
}
var date = new Date()
var arr = [1, 2, 3]
var reg = /[hbc]at/gi
console.log(Object.prototype.toString.call(fn))
console.log(Object.prototype.toString.call(date))
console.log(Object.prototype.toString.call(arr))
console.log(Object.prototype.toString.call(reg))
8.instanceof 原理
instanceof 原理实际上就是查找目标对象的原型链
function myInstance(L, R) {//L 代表 instanceof 左边,R 代表右边
var RP = R.prototype
var LP = L.__proto__
while (true) {
if(LP == null) {
return false
}
if(LP == RP) {
return true
}
LP = LP.__proto__
}
}
console.log(myInstance({},Object))
9.为什么 typeof null 是 Object?
因为在 JavaScript 中,不同的对象都是使用二进制存储的,如果二进制前三位都是 0
的话,系统会判断为是 Object 类型,而 null 的二进制全是 0,自然也就判断为
Object
这个 bug 是初版本的 JavaScript 中留下的,扩展一下其他五种标识位:
000 对象
1 整型
010 双精度类型
100 字符串
110 布尔类型
10.==和===有什么区别?
===是严格意义上的相等,会比较两边的数据类型和值大小
数据类型不同返回 false
数据类型相同,但值大小不同,返回 false
==是非严格意义上的相等,
两边类型相同,比较大小
两边类型不同,根据下方表格,再进一步进行比较。
Null == Undefined ->true
String == Number ->先将 String 转为 Number,在比较大小
Boolean == Number ->现将 Boolean 转为 Number,在进行比较
Object == String,Number,Symbol -> Object 转化为原始类型
11.手写 call、apply、bind?
call 和 apply 实现思路主要是:
判断是否是函数调用,若非函数调用抛异常
通过新对象(
context)来调用函数
给 context 创建一个 fn 设置为需要调用的函数
结束调用完之后删除 fn
bind 实现思路
判断是否是函数调用,若非函数调用抛异常
返回函数
判断函数的调用方式,是否是被 new 出来的
new 出来的话返回空对象,但是实例的__proto__指向_this 的 prototype
完成函数柯里化
Array.prototype.slice.call()
call:
Function.prototype.myCall = function (context) {
// 先判断调用 myCall 是不是一个函数
// 这里的 this 就是调用 myCall 的
if (typeof this !== 'function') {
throw new TypeError("Not a Function")
}
// 不传参数默认为 window
context = context || window
// 保存 this
context.fn = this
// 保存参数
let args = Array.from(arguments).slice(1) //Array.from 把伪数组对象转为数
组
// 调用函数
let result = context.fn(...args)
delete context.fn
return result
}
apply:
Function.prototype.myApply = function (context) {
// 判断 this 是不是函数
if (typeof this !== "function") {
throw new TypeError("Not a Function")
}
let result
// 默认是 window
context = context || window
// 保存 this
context.fn = this
// 是否传参
if (arguments[1]) {
result = context.fn(...arguments[1])
} else {
result = context.fn()
}
delete context.fn
return result
}
bind:
Function.prototype.myBind = function(context){
// 判断是否是一个函数
if(typeof this !== "function") {
throw new TypeError("Not a Function")
}
// 保存调用 bind 的函数
const _this = this
// 保存参数
const args = Array.prototype.slice.call(arguments,1)
// 返回一个函数
return function F () {
// 判断是不是 new 出来的
if(this instanceof F) {
// 如果是 new 出来的
// 返回一个空对象,且使创建出来的实例的__proto__指向_this 的 prototype,
且完成函数柯里化
return new _this(...args,...arguments)
}else{
// 如果不是 new 出来的改变 this 指向,且完成函数柯里化
return _this.apply(context,args.concat(...arguments))
}
}
}
12.字面量创建对象和 new 创建对象有什么区别,new 内部都实现了什么,手写一个 new
字面量:
字面量创建对象更简单,方便阅读
不需要作用域解析,速度更快
new 内部:
创建一个新对象
使新对象的__proto__指向原函数的 prototype
改变 this 指向(指向新的 obj)并执行该函数,执行结果保存起来作为 result
判断执行函数的结果是不是 null 或 Undefined,如果是则返回之前的新对象,如果不
是则返回 result
手写 new
// 手写一个 new
function myNew(fn, ...args) {
// 创建一个空对象
let obj = {}
// 使空对象的隐式原型指向原函数的显式原型
obj.__proto__ = fn.prototype
// this 指向 obj
let result = fn.apply(obj, args)
// 返回
return result instanceof Object ? result : obj
}
13.什么是作用域,什么是作用域链?
规定变量和函数的可使用范围称作作用域
14.每个函数都有一个作用域链,查找变量或者函数时,需要从局部作用域到全局作用域依次查找,这些作用域的集合称作作用域链。什么是执行栈,什么是执行上下文
执行上下文分为:
全局执行上下文
创建一个全局的 window 对象,并规定 this 指向 window,执行 js 的时候就压入栈底,
关闭浏览器的时候才弹出
函数执行上下文
每次函数调用时,都会新创建一个函数执行上下文
执行上下文分为创建阶段和执行阶段
创建阶段:函数环境会创建变量对象:arguments 对象(并赋值)、函数声明(并赋
值)、变量声明(不赋值),函数表达式声明(不赋值);会确定 this 指向;会确定
作用域
执行阶段:变量赋值、函数表达式赋值,使变量对象编程活跃对象
eval 执行上下文
执行栈:
首先栈特点:先进后出
当进入一个执行环境,就会创建出它的执行上下文,然后进行压栈,当程序执行完成
时,它的执行上下文就会被销毁,进行弹栈。
栈底永远是全局环境的执行上下文,栈顶永远是正在执行函数的执行上下文
只有浏览器关闭的时候全局执行上下文才会弹出
15.什么是闭包?闭包的作用?闭包的应用?
函数执行,形成私有的执行上下文,使内部私有变量不受外界干扰,起到保护和保存
的作用
作用:
保护
部的变量,使变量不会被垃圾回收机制回收
应用:
设计模式中的单例模式
for 循环中的保留 i 的操作
防抖和节流
函数柯里化
缺点:
会出现内存泄漏的问题