js面试(二)

527 阅读36分钟

一.函数

1.1 箭头函数

  • 1.箭头函数没有this,他的this由所在上下文中的this指定的;
  • 2.箭头函数没有arguments对象,但是,他可以访问外围函数的 arguments 对象;
  • 3.箭头函数不能通过new创建,没有构造原型。
  • 4.箭头函数一般作为函数参数使用,或者用于让他的this指向外围函数时使用。

1.2 回调函数与递归函数

  • 回调函数是一个作为参数传给另一个 JavaScript 函数的函数。这个回调函数会在传给的函数内部执行。
  • 递归函数递归函数就是在函数中自己调用自己, 一定程度上可以实现循环的功能,每次调用递归函数都会开辟一块新的存储空间, 所以性能不是很好.

1.3 函数内部

1.arguments

  • arguments,实参的集合。通过下标获取参数的每一位;通过length获取实参的个数;集合是类数组,可以使用下标,但是没有数组中的各种方法。

2.默认参数和剩余参数(es6)

  • 默认参数 从 ES6 开始, 可以直接在形参后面通过=指定默认值
     function getSum(a = "你好", b = "前端") {
        console.log(a, b);
      }
      getSum(); //你好 前端
      getSum("hello", "world"); //hello world

* 剩余参数-----剩余参数语法允许将不确定数量的参数表示为数组。

      function name(a, b, c, ...arr) {
        console.log(a, b, c, arr); //1 2 3 [4, 5,6,7,78]
      }
      name(1, 2, 3, 4, 5, 6, 7, 78);

1.4 函数的this指向-函数执行时确定的

this取值是在函数执行是确定的
普通函数 ://window
普通函数的call方法fn.call({name:1})://{name:1}
普通函数的bind方法fn.bind({name:1})://{name:1}
         const fn2= fn1.bind({name:1})
          fn2()//bind会返回一个新的函数执行
对象的方法执行,this指向该对象 obj.say()
setTimeout(function(){console.log(this)},2000)function里面的this指向window
setTimeout(()=>{},2000)指向外面的对象
class中的指向实例本身

二、变量

2.1 变量创建

ES6之前创建变量用的是var
ES之后创建变量用的是let/const
三者区别
第一:关于

var定义的变量,没有块的概念,可以跨块访问, 不能跨函数访问。
let定义的变量,只能在块作用域里访问,不能跨块访问,也不能跨函数访问。
const用来定义常量,使用时必须初始化(即必须赋值),只能在块作用域里访问,而且不能修改。(对于const声 明的引用类型,const仅保证指针不发生改变,修改对象的属性不会改变对象的指针,所以是被允许的。)
第二:(变量提升)var可以先使用,后声明;let必须先声明后使用。
第三:(重复声明)var是允许在相同作用域内重复声明同一个变量的,而let与const不允许这一现象。
第四:在全局上下文中,基于let声明的全局变量和全局对象GO(window)没有任何关系 ; var声明的变量会和GO有映射关系
第五:解决暂时性死区: 暂时性死区是浏览器的bug:检测一个未被声明的变量类型时,不会报错,会返回undefined
如:console.log(typeof a) //undefined
而:console.log(typeof a)//未声明之前不能使用
let a
第六:let /const/function会把当前所在的大括号(除函数之外)作为一个全新的块级上下文,应用这个机制,在开发项目的时候,遇到循环事件绑定等类似的需求,无需再自己构建闭包来存储,只要基于let的块作用特征即可解决

2.2 变量提升

当浏览器开辟出供代码执行的栈内存后,代码并没有自上而下立即执行,而是继续做了一些事情:把当前作用域中所有带var、function关键字的进行提前的声明和定义 =>变量提升机制 【预解析】

  • 带var的只是提前声明,没有赋值,默认值是undefined。
  • 带function的声明加赋值。
  • 不带var的a=3表示给全局设置window.a=3和在全局作用域下var a=3是一样的;
  • 在变量提升阶段,遇到大括号、判断体等,不论条件是否成立,都要进行变量提升,而在高版本浏览器中,函数只声明、不赋值。
  • ES6语法中,存在块级作用域,就是let /const/function会把当前所在的大括号(除函数之外)作为一个全新的块级上下文。 因为要兼容ES3和ES6,带function a(){}的在全局下声明过,也在私有下处理过,遇到此行代码,私有下不会再处理,但是浏览器会把当前代码之前所有对a的操作,映射给全局一份,以此兼容ES3,但他后面的代码和全局没有任何关系了。梨子如下:
   var a = 0;
      if (true) {
        a = 1;
        function a() {}
        a = 11;
        function a() {}//这前面的对a的操作映射一份给全局,后面的留给自己
        console.log(a); //11
      }
      console.log(a); //11

三、堆栈内存及垃圾回收机制

栈内存:浏览器在计算机内存中分配出一块内存供代码执行的环境栈(ECStack),也称栈内存 ;

基本数据类型都是存到栈里面的。
引用数据类型指针存到栈内存 堆内存:浏览器会把内置的属性和方法放到一个单独的内存中, 引用数据类型是先开辟一个堆内存,把东西存进去,最后把地址放到栈中供代码关联使用; 垃圾回收机制:项目中,如果存在大量不被释放的内存,会造成内存泄漏。浏览器有自己的一套垃圾回收机制,不定时去查找不被占用的内存,然后销毁他。而我们在开发过程中,为了减少内存消耗,要尽量减少闭包的使用,及时用空指针对象解除不被占用的堆。

js 中存在多种作用域(全局,函数私有的,块级私有的),代码执行前首先会形成自己的执行上下文(执行环境栈),然后把上下文进栈,进栈后,在当前上下文再依次执行代码; 全局执行器上下文(EC(G))进栈(ECStack)执行,执行完代码就会把形成的上下文释放(出栈),当页面关闭全局上下文出栈; 函数执行:

  • 函数执行的时候,形成一个全新的私有上下文EC(FN),共字符串代码执行

  • 进栈执行,从上面进去,把全局往下压

  • 私有上下文有私有变量对象 AO(FN),在私有上下文中创建的对象会放到这里来;

  • 代码执行之前还需要:

    • 1.初始化作用域链(scopeChain):<EC(FN),EC(G)>
    • 2.初始化 this 指向:window
    • 3.初始化实参集合:arguments
    • 4.形参赋值
    • 5.变量提升
    • 6.代码执行

四.作用域和作用域链

  • 创建函数的时候,已经声明了当前函数的作用域==>当前创建函数所处的上下文。若果是在全局下创建的函数就是[[scope]]:EC(G)
  • 函数执行的时候,形成一个全新的私有上下文EC(FN),共字符串代码执行(进栈执行)
  • 初始化作用域链(scopeChain):<EC(FN),EC(G)>从自己执行时所在的上下文(函数执行形成的私有上下文),到当前函数创建的时候所在的上下文(是当前函数的作用域)。日后在私有上下文代码执行的时候,遇到一个变量,我们首先看是否是自己的私有变量,是则操作自己的,不是就按照作用域链找上级上下文的...直到找到全局为止。

五.闭包

  • 如果一个函数的作用域(A)有权访问另一个函数作用域(B),那么我们就可以认为被访问的函数(B)是闭包。通常是嵌套函数中实现的。
  • 闭包的作用:延伸了变量的作用范围,fn外面的作用域可以访问fn里面的作用域.
//Closure (father)
 function father(name) {
        function son() {
            console.log(name);
        }
        son();
  }
 father("蔡徐坤");
  • 常见的闭包,函数作为参数,此时要特别注意函数时在哪创建的就用哪的值
  • 常见的闭包,函数作为返回值,当调用它的时候看return 在哪个函数里面就优先使用里面的值,也经常用于封装接口,只暴露api,不暴露数据。

六.对象

对象是一组属性的无序集合,任何一个对象都是由0到多组键值对(属性名:属性值)组成的,并且属性名不能重复。

字面量和构造函数创建对象。

6.1 new Object()执行原理

当我们new Object()系统做了什么事情?

1.会在构造函数中自动创建一个空对象,这个对象就是当前类的实例;
2.把构造函数中的this赋值给空对象
3.设置原型链,空对象指向构造函数的原型对象
4.执行构造函数内部的代码(给空对象添加属性)
5.会在构造函数的最后自动添加return this; 返回原始值忽略,返回对象则正常处理

6.2 自定义类(构造函数)

  • 构造函数特点: 1.函数体内使用this关键字,代表了所要生成的对象实例。 2.生成对象,必须使用new 关键字实例化。
  • 不管是自定义类还是内置类,都是函数数据类型的;typeof Array是"function"
  • js是基于构造函数(constructor)和原型链(prototype)。
//构造函数里面放置对象实例的私有属性和方法,他们可以批量创造,都带有this
      function Person(myName, myAge) {
        this.name = myName;
        this.age = myAge;
      }
//原型里面放置公有属性和方法,此构造函数创建的每个实例都可以通过原型链查找
      Person.prototype = {
        say: function () {
          console.log("hello world");
        },
      };
      let obj1 = new Person("蔡徐坤", 23);
      obj1.say();
      let obj2 = new Person("毛不易", 29);
      obj2.say();
      console.log(obj1.say === obj2.say); // true
  • 基于instanceof 可以检测当前对象是否属于某个类的实例,返回布尔值:实例 instanceof 类;
  • 每一个对象都有很多属性和方法,在自己堆内存中存储的都是自己的私有属性和方法,基于__proto__原型链查找类prototype原型上的都是共有的属性和方法。
  • 通过对象.hasOwnProperty(属性)检测是否为私有属性,是则返回true。
  • 判断某一个对象是否有某个属性用“in”,只要类中或者原型对象中有, 就会返回true
  • isPrototypeOf用于判断 一个对象是否是另一个对象的原型

6.3 原型和原型链

  • 函数数据类型:普通函数、类
  • 对象数据类型:普通对象、数组对象、正则对象、日期对象、实例对象、函数对象、类的prototype
  • 每一个对象都有原型链属性,原型链的属性值是原型,原型的属性值是对象,浏览器默认的原型有一个constructor属性,值是当前类本身。
  • 再来一遍,每一个实例对象都有原型链属性(__proto__),指向她的构造函数的原型对象(prototype);构造函数的原型对象的__proto__指向Object内置类的原型对象(prototype),即可看成所有构造函数都是Object的实例对象,内置原型类Object的原型对象指向一个空指针对象null。

原型prototype(原型属性)

  • 1 . 函数数据类型都具备prototype(原型属性),并且属性值是一个对象;
  • 2 . 在prototype对象中会存储当前类的公共属性和方法;
  • 3 . 在prototype的堆内存中,若果是浏览器默认为其开辟的堆内存,会存在一个内置的属性constructor构造函数,属性值就是当前类本身。即:
function Fn() {}
 f = new Fn();
 console.log(Fn === Fn.prototype.constructor);//true

原型链__proto__

  • 每一个对象(普通对象、prototype、实例、函数等)都具备内置属性:__proto__(原型链属性),属性值是当前实例所属类的原型(即prototype这个堆)

  • (对象基类)Object.prototype也是一个对象,他也有__proto__,他是指向基类属性值为null。

//实例对象的原型链`__proto__`指向构造函数的Fn.prototype原型对象
console.log(f.__proto__ === Fn.prototype);//true
//构造函数的原型对象的`__proto__`指向Object内置类的原型对象,即可看成所有构造函数都是Object的实例对象
console.log(Fn.prototype.__proto__ === Object.prototype);//true
console.log(Array.prototype.__proto__ === Object.prototype);//true
console.log(Function.prototype.__proto__ === Object.prototype);//true
console.log(String.prototype.__proto__ === Object.prototype); //true

原型链查找机制

它是一种基于__proto__向上查找的机制,当我们操作实例的某个属性或者方法的时候,首先找到自己空间中私有的属性或者方法:
1.找到了,则结束查找,使用自己的私有即可;
2.没有找到 则基于__proto__ 找所属类的prototype上的公有属性和方法,如果找到了就用这个公有的;
3.如果没找到,基于原型上的__proto__继续向上查找,一直找到Object:prototype的原型为止,如果在没有,操作的属性或者方法不存在。

内置原型上扩展方法

对于一个对象来说,他的属性和方法存在可枚举的特点:即在for...in...循环时是否可以遍历到,内置原型上自己扩展的方法是可枚举的。若果我们只想枚举实例中的属性和方法,可以用hasOwnProperty过滤掉原型上的方法。

 let obj = {
        name: "蔡徐坤",
        age: 22,
      };
      Object.prototype.fun = function () {
        console.log("我是蔡徐坤");
      };
      for (let key in obj) {
        //在遍历对象上的属性和方法时,也会遍历原型上添置的属性和方法
        console.log(key); // name age fun
        //过滤掉原型上的属性和方法
        if (obj.hasOwnProperty(key)) {
          console.log(key); //name age
        }
      }

6.4 Object.create方法

Object.create(xxx)创建一个空对象,会把当前的xxx作为这个对象的原型链指向===相当于创建某个类的空实例

      // 首先创建一个空对象,把这个空对象的原型链改写为传入的参数(参数必须为对象或者null)
      a = Object.create({ name: "zs" });
      console.log(a, a.__proto__); //{}  {name: "zs"}

6.5-对象关系梳理

  • 每一个对象(普通对象、prototype、实例、函数等)都具备内置属性:__proto__(原型链属性),属性值是当前实例所属类的原型(即prototype这个堆)
  • 函数数据类型(一般指构造函数)都具备prototype(原型属性),并且属性值是一个对象;在这个对象里面存储公共数据和方法,prototype对象若果是浏览器默认为其开辟的堆内存,会存在一个内置的属性constructor构造函数,属性值就是当前类本身。即:
  • 创建一个构造函数Person,这个构造函数里面有原型属性prototype,指向一个原型对象Person.prototype;
  • 通过构造函数创建一个实例对象,有原型链属性__proto__,指向所属类的原型对象Person.prototype;
  • 原型对象是啥?用于存放公用的属性和方法;原型对象里面有原型链__proto__属性,指向构造函数所属类的原型对象(Object.prototype),里面存储原型方法。原型对象还有一个constructor属性,指向当前原型对象所属类。 image.png

七、继承

  • 让子类的实例同时也具备父类中的私有属性和公共方法。

  • 子类的实例能够用子类私有的和原型上共有的

  • 父类的实例能够用父类私有的和原型上共有的

方法一:原型继承-想继承谁就把自己所属类的原型对象重指向谁的实例。

  • 创建构造函数A和构造函数B,B的实例如果想继承A的原型方法,直接让B构造函数的原型对象指向A构造函数的实例。
  • 优点:
  • 继承了父类的模板,又继承了父类的原型对象
  • 缺点:
  • 如果要给子类的原型上新增属性和方法,就必须放在Child.prototype = new Parent()这样的语句后面(顺序问题)
  • 来自原型对象的所有属性都被共享(共有的)了,这样如果不小心修改了原型对象中的引用类型属性,那么所有子类创建的实例对象都会受到影响(这点从修改child1.colors可以看出来)
  • 创建子类时,无法向父类构造函数传参数(这点从child1.name可以看出来)

instanceof基于原型链查找是否有某个类的原型对象

  • a instanceof B
  • 实例对象a instanceof 构造函数B
  • 检测a的原型链(proto)上是否有B.prototype,有则返回true,否则返回false。

isPrototypeOf()

  • 这个方法属于Object.prototype上的方法,isPrototypeOf()的用法和instanceof相反。
  • 它是用来判断指定对象object1是否存在于另一个对象object2的原型链中,是则返回true,否则返回false。
console.log(Child.prototype.isPrototypeOf(child1));//true

方法二:call 继承-让父类私有的变成子类私有的,不能继承父类公有的

在子类构造函数中,把父类当做普通方法执行(没有父类实例,父类原型上的那些东西也就和它没关系了),并且用call改变this指向。

Parent.call(this);
CALL 继承(只能继承父类中私有的,让父类私有的变成子类私有的;不能继承父类中公共的)

function Parent() {
  this.x = 100;
}
function Child() {
  Parent.call(this); // this.x=100:
  this.y = 200;
}
let c1 = new Child();
console.log(c1);//{x:100,y:200}

方法三:寄生组合式继承(CALL+另类原型继承)

第一步:让子类原型通过原型链指向父类原型,这样父类公有方法就变成子类公有方法了。

Child.prototype.__proto__=Parent.prototype
兼容写法:Child.prototype=Object.create(Parent.prototype);这句话的含义是:创建一个空对象指向父类的原型,然后让子类的原型指向空对象 第二步 在子类构造函数中,把父类当做普通方法执行,并让this指向子类实例。 Parent.call(this) 第三步让原型的constructor正常指回,指向所属类。 Child.prototype.constructor = Child;

function Parent() {
  this.x = 100;
}
function Child() {
  Parent.call(this);
  this.y = 200;
}

Child.prototype = Object.create(Parent.prototype);
// 重定向了Child.prototype,重新指向Child
Child.prototype.constructor = Child;

方法四:ES6 类与继承

4.1 class类

ES6的类可以包含构造函数方法、实例方法、获取函数、设置函数和静态类方法,但都不是必须的。
实际上 class只是一个语法糖 是构造函数的另一种写法;
class不存在变量提升 (由于继承有关 必须确保子类在父类之后定义)。

class Person{
}
console.log(typeof Person)                             //funciton
console.log(Person.prototype.constructor === Person)   //true
class Student {
    //1.constructor里面定义的方法和属性是实例对象自己的,私有的
        constructor(name, number) {
          this.name = name;
          this.number = number;
          this.gender = "semale";
        }
    //2.而constructor外没写static关键字的是原型属性和方法
        sayHi() {
          console.log(`姓名 ${this.name} ,学号 ${this.number}`);
          console.log("姓名:" + this.name, "学号:" + this.number);
        }
    //3。写了static的属性和方法是类的属性方法,把类当成普通对象调用
        static say() {
          console.log("hello");
        }
        
        
      }
      const luozhi = new Student("洛枳", 12345);
      console.log(luozhi); //{name: "洛枳", number: 12345, gender: "semale"}
      luozhi.sayHi();
      /*
        姓名 洛枳 ,学号 12345
        姓名:洛枳 学号:12345
      */

4.2 类的继承

  • 先定义父类,再定义子类
  • 在子类后面添加 extends 并指定父类的名称 class Child extends Parent{}
  • 在子类的 constructor 构造函数中通过 super() 方法借调父类的构造函数,
class Parent {
  constructor() {
    this.x = 100;
  }
  getX() {
    return this.x;
  }
}
// 注意:继承后,【如果子类有constructor,】一定要在constructor第一行加上super
class Child extends Parent {
  constructor() {
    // 类似于我们之前的CALL继承。super(100,200):相当于把Parent中的constructor执行,传递了100和200
    super();
    this.y = 200;
  }
  getY() {
    return this.y;
  }
}
let c1 = new Child();
console.log(c1);

// Child();ES6中创建的就是类,不能当做普通函数执行,只能new执行
补充案例
function wjl(name, age) {
  // name、age通过参数传进来的,是每一个实例对象私有的属性。
  // height、house不是通过参数传进来的,是所有实例对象共有的属性。
  this.name = name;
  this.age = age;
  this.height = 188;
  this.house = function () {
    return "兰博基尼";
  };
}

wjl.prototype.money = 888;
var obj = new wjl("王健林", 12); // obj是wjl的实例对象, 参数 '王健林'、12是obj这个实例对象私有的,不是wjl所有的实例对象共有,这里的 '王健林'、12 和 wsc的实例对象o 没有关系
console.log("obj-------", obj); // wjl {name: "王健林", age: 12, height: 188, house: ƒ}

function wsc(names, ages) {
  this.names = names;
  this.ages = ages;
  wjl.call(this); // 继承wjl,而不是wjl的实例
}

var o = new wsc("王思聪", 22, 33, 55);
console.log("o----", o); // wsc {names: "王思聪", ages: 22, name: undefined, age: undefined, height: 188, …}

// 【'王健林'、12是obj实例的私有属性,不是wjl类的所有实例继承的属性。如果在wjl类中写 this.height = 188,而不是传进来的参数,那么this.height = 188就是wjl类的所有实例继承的属性、属性值,就像wjl类的this.house。】
console.log(o.house, o.name, o.age); // 函数 undefined undefined
console.log(o.house, o.names, o.ages); // 函数 王思聪 22

八.对象的方法

8.1 获取对象类型----对象是由哪些类创造出来的呢?

1. constructor-获取的是对象直接所属的类

let arr = new Array();
console.log(arr.constructor.name); // Array
let p = new Person();
// console.log(typeof p); // object
console.log(p.constructor.name); // Person

2 instanceof-判断 "对象" 是否是指定构造函数的 "实例"

只要 构造函数的原型对象出现在实例对象的原型链中都会返回true

      let arr = new Array();
      console.log(arr instanceof Array); //true
      console.log(arr instanceof Object); //true

      class Person {}
      let p = new Person();

      console.log(p instanceof Person); //true
      console.log(p instanceof Array); //false
      console.log(p instanceof Object); //true

3 isPrototypeOf---判断 一个对象是否是另一个对象的原型,也就是原型对象是否在实例对象的原型上。

只要调用者在传入对象的原型链上都会返回true

      class Person {
        name = "lnj";
      }
      let p = new Person();
      console.log(Person.prototype.isPrototypeOf(p)); // 判断Person原型对象是否是实例对象p的原型,返回true

8.2 判断对象属性

1 in判断一个对象是否拥有某个属性

判断某一个对象类中或原型中是否拥有某一个属性可以用“in”,只要类中或者原型对象中有, 就会返回true

2 hasOwnProperty判断是否是私有属性

判断某一个对象自身是否拥有某一个属性可以用hasOwnProperty

特点: 只会去类中查找有没有, 不会去原型对象中查找

    class Person{
            name = null;
            age = 0;
        }
    Person.prototype.height = 0;
    let p = new Person();
    console.log("name" in p); // true
    console.log("height" in p); // true
    console.log(p.hasOwnProperty("name")); // true
    console.log(p.hasOwnProperty("height")); // false

8.3对象属性增删改查、遍历

      class Person {
        constructor(name, age) {
          this.name = name;
          this.age = age;
        }
      }
 // 1.创建实例对象
      let p = new Person("蔡徐坤", "22");
      console.log(p); //{name: "蔡徐坤", age: "22"}

//2.给对象增加属性(增加没有的属性)
      p.sex = "男";
      p["hobby"] = "singing,dance";
      console.log(p); // {name: "蔡徐坤", age: "22", sex: "男", hobby: "singing,dance"}
      
//3.给对象删除属性(删除已有的属性)
      delete p.sex;
      console.log(p); // {name: "蔡徐坤", age: "22", hobby: "singing,dance"}
      
//4.给对象修改属性(对操作已有的属性)
      p.name = "蔡小葵";
      p["age"] = 23;
      console.log(p); // {name: "蔡小葵", age: 23, hobby: "singing,dance"}
      
// 5.查询属性
      console.log("我是" + p.name, "我的爱好是" + p["hobby"]); //我是蔡小葵 我的爱好是singing,dance
      
// 6.对象属性遍历(for...in...)
      for (let key in p) {
        console.log(key + ":" + p[key]);
        /*
         name:蔡小葵
         age:23
         hobby:singing,dance
        */
      }

九、搞定this

this取值是在函数执行是确定的,他是执行主体。

1.在浏览器环境中,全局上下文中的 this,就是 window。

2.块级上下文中的this由所在上及上下文的this决定。

3.事件绑定方法中的this(3种情况)

//1.dom0中el.onckick=函数
    div.onclick = function () {
        console.log(this); // div
      };

//2.直接在元素上绑定并执行,则指向window
<li onclick="btn()">1</li>

//3.使用 addEventListener 绑定事件, this 指向 该元素
 <div id="aa" ></div>
document.getElementById("aa").addEventListener("click", function () {
  console.log(this); //  <div id="aa"></div>
});

4.普通函数执行中的this---看方法执行前是否有“点”

// 1.自执行函数中的this指向window

// 2普通函数执行,this指向window,成员访问方法执行this指向`.前面的`

function func(){
	console.log(this);
}
let obj = {
	func:func
}

// 普通函数执行
func();            // this:window
// 成员访问方法执行
obj.func();        // this:obj

[].slice()方法中this->当前空数组。
Array.prototype.slice()方法中的this->Array.prototype

5.构造函数执行的this

  • 构造函数体中的 this 指向的就是当前类的实例。
  • 换句话说,类中凡是不带 this 的代码统统都与它创建的实例无关,充其量也就是这个函数私有上下文中的私有变量而已。
  • 原型上的方法中的this指向要看前面是否有点

6.ES6 中的箭头函数中的this

箭头函数没有自己的 this,它的 this 继承自上级上下文中的 this===>这一点和块级上下文类似。

      let obj = {
        func: function () {
          console.log(this);
        },

        sum: () => {
          console.log(this);
        },
      };
      obj.func(); // this:obj 对象成员访问
      obj.sum(); // this:window
      obj.sum.call(obj); // this:window 箭头函数没有this,改也没用

7. 回调函数中的 this

  • 回调函数中的 this 一般指向 window,但也有例外
      let obj = {
        i: 0,
        func() {
          console.log("func", this); //obj,对象成员访问调用
          setTimeout(function () {
            console.log("定时器里面的", this); //window
          }, 1000);
          setTimeout(() => {
            console.log("定时器里面的", this); //obj
          }, 2000);
        },
      };
      obj.func();

8.改写this

call/apply/bind 方法强制手动改变函数中的 this 指向,这三个方法都在函数类的原型对象 Function.prototype 上,因此所有的函数都能基于原型链proto找到并调用这三个方法 ;

8.1 call

函数执行时在函数名后面加.call(),接受参数第一个是this的指向,第二及后面的是实参。

8.2 apply

与 call 方法相同,只不过传递函数实参以数组的方式

8.3 bind

bind 的语法与 call 相同,作用不同。 它是预先修改 this,预先存储参数,而函数不被立即执行; 也就是说,call / apply 都是把函数立即执行的,并且改变 this。

//用call方法
      function func(name, age) {
        console.log(this); //obj

        console.log(name, age); //10,20还没点就打印了,相当于把执行结果赋值给点击事件
      }
      obj = {};
      document.body.onclick = func.call(obj, 10, 20);
      //  可以直接接一个函数,在函数里面进行,func调用

//用bind方法
      function func(name, age) {
        console.log(this); //obj

        console.log(name, age); //10,20还没点就打印了,相当于把执行结果赋值给点击事件
      }
      obj = {};
      document.body.onclick = func.bind(obj, 10, 20);
      //只有点击的时候才会去触发func.bind()
      /*
    bind函数不会立即执行,而是预先修改this,并预先存储需要传递的参数。后续需要时(事件触发)再执行函  数。
      */

8.4call,apply应用

1.把类数组转化为数组
  • 类数组为对象,但有一些数组的方法,用起来像数组,有索引,length,和可迭代性;

  • 常见的有:函数实参集合,dom 元素集合,dom 节点集合 把类数组转化为数组就可以用数组原型上的方法 类数组转化为数组

//方法一
Array.from(arguments);

//方法二:不定参数参数传给函数
...arguments

//方法三:借用数组原型上的方法操作类数组


  • 如果我们让Array原型上的内置方法执行,并且让方法中的this变为我们要操作的类数组,那么就相当于我们在“借用数组原型上的方法操作类数组”,让类数组也和数组一样可以调用这些方法实现具体的需求:
      function toArray() {
      //把数组原型上的slice方法的this改成arguments,
        let args = [].slice.call(arguments);
        console.log(args); //[1, 2, 3, 4, 5, 9, 6, 7]
      }
      toArray(1, 2, 3, 4, 5, 9, 6, 7);
      function toArray() {
        // 借用array.prototype.forEach,让forEach中的this指向arguments
        [].forEach.call(arguments, (item) => {
          console.log(item);
        });
      }
      toArray(1, 2, 3, 4, 5, 9, 6, 7);

十、DOM

9.1.事件与事件绑定

  • 事件是浏览器默认具备的行为,不论是否基于JS绑定方法,事件都是存在的。只要相关的行为触发( 比如点击 ),相关的事件( 点击事件 ) 就会触发。
  • 事件绑定是指:给元素的事件行为绑定方法,当事件触发时,方法执行。

9.1.1 事件分类

1、鼠标事件
  • 点击事件--click,dblclick: 单击,双击鼠标。
  • 按下事件--(1).鼠标按下触发的事件:mousedown;(2).鼠标在一个节点移动触发mousemove;(3).释放按下的鼠标键时触发:mouseup;(按下事件执行完了就触发click)
  • 移入/移出事件--
    • mouseenter:鼠标光标从元素外部首次移动到元素范围之内时触发,这个事件不冒泡,而且在光标移动到后代元素上不会重复触发。通常和mouseleave搭配使用。
    • mouseleave:鼠标离开一个节点时触发,离开父节点不会触发这个事件(详见后文)。
    • mouseover:鼠标进入一个节点时触发,进入子节点会再一次触发这个事件(详见后文)。
    • mouseout:鼠标离开一个节点时触发,离开父节点也会触发这个事件(详见后文)。

image.png

2、键盘事件
  • 键盘事件:当用户通过键盘在页面上执行操作时触发
  • keydown:用户按下键盘上的任意键时触发,返回键码keyCode
  • keypress:用户按下键盘上的字符键时触发,而且按住不放的话,会重复触发此事件,按下esc也会触发,更适用于使用 charCode(字符码,ASCII码)
  • keyup:用户释放键盘上的键时触发input 文本框内容输入中
3、用户界面事件-

当用户与页面的上的元素交互时发生,但不一定与用户操作有关的事件。

  • load事件:当页面加载完毕时在window上触发.

  • unload事件:当页面完全卸载时在window上触发,等等。当一个页面切换到另一个页面时就会触发,经常利用这个事件来清除引用,减少内存的泄露

  • select:当用户选择文本框(input或textarea)中的一或多个字符时触发

  • resize:当窗口或框架的大小发生改变时在window上触发

  • scroll:当用户滚动带滚动条的元素中的内容时,在该元素上触发

4、焦点事件--当元素获得焦点或失去焦点时触发

blur:失去焦点时触发,这个事件不会冒泡

focus:获得焦点时触发,不冒泡

focusin:在获得焦点时触发,但他冒泡,DOM3新增

focusout:在失去焦点时触发,冒泡

5、移动端-小程序事件

touchstart: 当手指触摸屏幕的时候出发

touchmove: 当手指在屏幕移动的时候

touchend: 手指离开屏幕的时候触发

touchcancel:  当被迫中止滑动的时候触发(弹消息,来电等等);

tap:手指触摸后离开(点击)

longtap: 手指触摸后后,超过350ms离开

事件绑定的写法同组件的属性,以 key、value 的形式。

key 以bind或catch开头,然后跟上事件的类型,如bindtap, catchtouchstart

6、表单事件(表单获取与提交)

1.表单获取通过id,forms、name获取

2.提交表单submit

<input>的type设为submit

<button>的type设为submit

<input>的type设为image

获取表单元素,给表单元素绑定submit事件,然后将提交的button的 type属性值设为submit

7、音频视频事件

9.1.2 事件绑定

1 DOM0级事件绑定
开始绑定:el.onxxx = function(){}
移除绑定:el.onxxx = null
//事件作为元素的私有属性,因此只能绑定一个,
2.DOM2级事件绑定-绑定到浏览器的事件池中
事件绑定:el.addEventListener('事件类型如click',function (){},[false/true])
//最后面的默认false,在冒泡阶段执行
使用内置的addEventListener和removeEventListener进行事件绑定 / 移除绑定

9.2.事件对象与事件传播机制

1.事件对象

  • 给某个元素绑定一个事件,系统给事件函数传入一个对象,可以在事件函数里获取一些属性。
鼠标事件
      box.onclick = function (e) {
        //获取目标元素与事件源
        console.log(e.target); //box或者box2 表示事件发生的目标元素,就是当前点击区域的最内侧的元素
        console.log(e.currentTarget); //box 表示事件源,事件是由谁注册的,currentTarget就是谁

        //与坐标相关的属性
        e.clientX  //坐标原点是浏览器的左(视窗)
        e.pageX    //距离页面距离,包括可视区的和滚轮卷去的
        e.offsetX  // 距离target点击元素的距离,
        e.screenX  //  坐标原点是当前显示器屏幕的左上角(包括了菜单栏之类的)
      };
键盘事件-- e.keyCode获取对应键码值
取消默认行为--ev.preventDefault();

默认行为:

  • a标签我们点击的时候会实现页面跳转;
  • 文本框输入的时候可以输入内容
  • 部分情况下文本框存在输入记忆
  • 鼠标按右键出现右键菜单 阻止默认行为:在绑定的事件中中传入ev.preventDefault();或者最后return false
document.oncontextmenu = function (ev) {
    // 禁止右键菜单
      ev.preventDefault();
      // return false; 这也是阻止默认行为
    }; 

2.事件传播机制(事件捕获,目标阶段及冒泡阶段)

    1. 事件的默认传播机制有三种:事件捕获,目标阶段及冒泡阶段:
  • 捕获阶段:事件触发时,浏览器会从最外层层级结构一直向里层查找,直到找到当前触发事件的目标元素为止。(目的是为冒泡阶段提供传播的路径 => ev.path)
  • 目标阶段:触发目标事件的相关行为 【目标】
  • 冒泡阶段不仅目标事件的相关行为被触发,其所有祖先元素的相关事件行为都会被触发(在这个过程中,哪一个元素的事件行为绑定了方法,方法都会被触发执行,而且顺序是由里层向外层传播) 【冒泡】

9.3.DOM增删改查

1.获取dom元素(三种)

  • 通过id获取
  • 通过class名称/name名称/标签名称
  • 通过选择器获取(重点记住这两种方法)
//1.通过id获取指定元素, 返回标签对象, 找不到就返回Null;注意点
document.getElementById("box");

//2.通过class名称/name名称/标签名称
document.getElementsByClassName("father");//返回数组,各项时对象
document.getElementsByName("test");
document.getElementsByTagName("div"); 

//3.通过选择器获取(重点记住这两种方法)
//querySelector只会返回根据指定选择器找到的第一个元素
document.querySelector("#box");
document.querySelector(".father");
document.querySelector("div>form");

//querySelectorAll会返回指定选择器找到的所有元素
  • 获取指定元素子元素:(el.children)
let oDiv = document.querySelector("div");

// children获取所有的子元素
    console.log(oDiv.children);
    
//firstElementChild获取指定元素的第一个子元素
    console.log(oDiv.firstElementChild);
    
// lastElementChild获取指定元素中最后一个子元素
    console.log(oDiv.lastElementChild);


//通过子元素获取父元素parentElement
      let item = document.querySelector(".item");
      console.log(item.parentElement); 
      
//获取相邻上一个元素previousElementSibling
      console.log(item.previousElementSibling); 

// 获取相邻下一个元素nextElementSibling
      console.log(item.nextElementSibling); 

2.元素节点增删改查(createElement、)

// 1.创建节点
      let oSpan = document.createElement("span");

// 2.添加节点(appendChild方法会将指定的元素添加到最后作为最后一个儿子;)
      let oDiv = document.querySelector("div");
      oDiv.appendChild(oSpan);
    
// 3.插入节点(insertBefore)
      // 获取第一个p,然后在p前面加一个span
      let oP = document.querySelector("p");
      oDiv.insertBefore(oSpan, oP);

// 4.删除节点(通过父元素删除子元素)
      console.log(oSpan.parentNode); 
      oSpan.parentNode.removeChild(oSpan);

3.元素属性操作-

  • 获取DOM元素对象,通过.语法找到标签属性,可以直接赋值修改,
  • 若果是自定义的标签属性获取和修改需要用getAttribute和setAttribute。
  • 如何删除元素属性oImg.alt = ""; , oImg.removeAttribute("alt");

4.元素内容操作(innerHTML、innerText)

  • 1.innerHTML获取的内容包含标签, innerText/textContent获取的内容不包含标签
  • 2.设置直接赋值就可以了

5.操作元素样式

      let oDiv = document.querySelector("div");
// 第一种方式:className设置类名
      oDiv.className = "box";

// 第二种方式style.属性名
      oDiv.style.width = "300px";
      oDiv.style.height = "300px";
      oDiv.style.backgroundColor = "blue";

//  行内样式的属性通过style属性获取到;
      console.log(oDiv.style.width);
      
// CSS设置的属性值必须通过getComputedStyle方法来获取;
      getComputedStyle方法接收一个参数, 这个参数就是要获取的元素对象;
      getComputedStyle方法返回一个对象, 这个对象中就保存了CSS设置的样式和属性值;
     
      let style = window.getComputedStyle(oDiv);
      console.log(style.width);

定时器

  • setTimeout用于多少秒后调用这个回调函数,调用完了之后就结束了。
  • setInterval用于每隔多少秒调用这个函数。

事件委托

事件委托,通俗的说就是将元素的事件委托给它的父级或者更外级的元素处理,它的实现机制就是事件冒泡。 事件委托的优点:

  • 1.只需要将同类元素的事件委托给父级或者更外级的元素,不需要给所有的元素都绑定事件,减少内存占用空间,提升性能。

  • 2.动态新增的元素无需重新绑定事件

  • 需要注意的点:事件委托的实现依靠的冒泡,因此不支持事件冒泡的事件就不适合使用事件委托。不是所有的事件绑定都适合使用事件委托,不恰当使用反而可能导致不需要绑定事件的元素也被绑定上了事件。

十一、BOM

BOM浏览器对象模型,把浏览器看成对象,顶级对象是 window,他的标准是浏览器厂商制定的,我们学习BOM 主要是学习一些浏览器窗口交互的对象。

windows对象:(四个知识点)

1.人机交互-系统对话框

  • alert():确定提示框。
  • confirm():选择提示框。
  • prompt():输入提示框。

2.窗口加载事件DOMContentLoaded

  • window.onload ,当文档内容完全加载完成会触发该事件(包括图像、脚本文件、CSS 文件等),就调用的处理函数。 onload 是等页面内容全部加载完毕,再去执行处理函数。可以把js写在里面,用addEventListener 可以绑定多个没有限制

  • DOMContentLoaded ,仅当 DOM 加载完成,事件触发,不包括样式表,图片,flash 等等。执行速度比onload快。

3.调整窗口大小事件resize

window.onresize 是调整窗口大小加载事件,当触发时就调用的处理函数。 注意 ∶ 只要窗口大小发生像素变化,就会触发这个事件。 我们经常利用这个事件完成响应式布局。使用 window.innerWidth 获取当前屏幕的宽度

4.定时器

window 对象包含 4 个定时器专用方法,说明如下表所示,使用它们可以实现代码定时执行,或者延迟执行,使用定时器可以设计演示动画。

  • setTimeout(回调函数,延时时间(毫秒)); 倒计时结束之后调用函数
  • clearTimeout(定时器名字): 清除 setTimeOut 的定时器;
  • setInterval(回调函数,间隔时间): 每间隔多少时间就执行一次回调函数
  • clearInterval(定时器名字): 清除 setInterval 的定时器;

注意:setTimeout()更像是延时器,延时多少秒后执行,执行完就废了;setInterval()更像是定时器,每隔一段时间反复执行,直到你清除。

  • setTimeout() 受 JavaScript 任务队列的影响,只有前面没有任务时,才会按时延迟执行动作。
  • setInterval() 方法不受任务队列的限制,它只是简单的每隔一定的时间就重复执行一次动作,如果前面任务还没有执行完毕,setInterval() 可能会插队按时执行动作。

navigator浏览器对象

navigator 对象存储了与浏览器相关的基本信息,如名称、版本和系统等。通过 window.navigator 可以引用该对象,并利用它的属性来读取客户端基本信息。

  • 最常用的是 userAgent,该属性可以返回由客户机发送服务器的 user-agent 头部的值。可以获取当前终端的信息。navigator.userAgent.

location 对象

location 属性用于获取或设置窗体的 URL,并且可以用于解析 URL。
获取query参数用search关键词 修改 location:每次修改 location 的 hash 以外的任何属性,页面都会以新URL重新加载,并且在浏览器的历史记录中生成一条新的记录,可以通过浏览器的回退按钮导航到前一个页面,但是使用 replace 打开的页面,不能返回到前一个页面

window.location = 'http://www.maxiaofei.com'

location.search = '?name=mafei&age=18'

location.hostname = 'www.baidu.com'

location.pathname = 'web/html/a.html'

location.port = '1234'


// 尝试在浏览器控制台输入如下代码,浏览器将不支持回退
location.replace('http://www.baidu.com')

//重新加载
location.reload() //重新加载页面(有可能会从缓存中加载)
location.reload(true): 重新加载(从服务器重新加载)

history对象

history 对象保存着用户上网的历史记录,从窗口被打开的那一刻算起,因为 history 是 window 对象的属性,因此每个浏览器窗口,每个标签乃至每个框架,都有自己的 history对象与特定的 window 对象关联

//跳转指定页面
history.go('maixaofei.com') //向前或者向后寻找指定字符串页面,没有找到则无响应
history.go(3)	//向前跳转三个记录
history.go(-1)	//向后跳转一个记录

//向前跳转一个
history.forward()

//向后跳转一个页面
history.back()

//获取历史记录数,如果是打开的第一个页面,则 history.length == 0,
//可以用该属性来判断当前打开的网页是不是该窗口打开的首个网页
history.length

其他

image.png

元素可视区client

Element.clientTop 和 Element.clientLeft 获取距可视区域左上角的偏移值(实际上就等于元素上下 border 值) 四舍五入(整数)后的结果;

Element.clientWidth 和 Element.clientHeight 获取当前元素可视区域的宽高(padding + 元素内容区域的宽高,不包含滚动条)

元素偏移量 offset(块级元素,只读属性)

  • Element.offsetLeft 和 Element.offsetTop 是一个只读属性,返回当前元素相对于父级元素左上角的偏移量
  • Element.offsetWidth 和 Element.offsetHeight 是一个只读属性,返回一个元素布局的宽高(元素布局包括: border、滚动条、padidng、内容块), 该属性返回的值为一个四舍五入的整数.

offset 与 style 区别

offset:

  • offset 可以得到任意样式表中的样式值
  • offset 系列获得的数值是没有单位的
  • offsetWidth 包含 padding+border+width·
  • offsetWidth 等属性是只读属性,只能获取不能赋值·

所以,我们想要获取元素大小位置,用 offset 更合适

style:

  • style 只能得到行内样式表中的样式值
  • style.width 获得的是带有单位的字符串
  • style.width 获得不包含 padding 和 border 的值
  • style.width 是可读写属性,可以获取也可以赋值

所以,我们想要给元素更改值,则需要用 style 改变·

scroll(用于对可滚动元素进行求值)

  • Element.scrollTop 和 Element.scrollLeft用于获取或设置元素被卷起的宽高(子元素顶部或左侧到当前元素可视区域顶部或左侧的距离)
  • Element.scrollWidth 和 Element.scrollHeight 是只读属性, 表示元素可滚动区域的宽高; 实际上又等于 clientHeight/clientWidth + 未显示在屏幕中内容的宽高;