高级js第一天

185 阅读10分钟
  //作业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; i < 3; i++) {
        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() ;它可以区分 null 、

    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);// true

    console.log(d instanceof Number); //true

    console.log(123 instanceof Number); //false -->不能判断字面量的基本数据类

    型

    // -----------------------------------------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);//ƒ Number() { [native code] }

    console.log(e.constructor.name);//Number

    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)); // "[object

    Undefined]"

    console.log(Object.prototype.toString.call(null)); // "[object Null]"

    console.log(Object.prototype.toString.call(123)); // "[object Number]"

    console.log(Object.prototype.toString.call("abc")); // "[object String]"

    console.log(Object.prototype.toString.call(true)); // "[object Boolean]"

    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));// "[object Function]"

    console.log(Object.prototype.toString.call(date));// "[object Date]"

    console.log(Object.prototype.toString.call(arr)); // "[object Array]"

    console.log(Object.prototype.toString.call(reg));// "[object RegExp]"
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 的操作

    防抖和节流

    函数柯里化

    缺点:

    会出现内存泄漏的问题