• JS 高级


    目录

    一、对象与 JSON 的区别?

    1、怎么让对象对自身进行深拷贝

    二、怎么判断js类型

    通过 typeof 判断。

    用 constructor 构造函数的方式,判断某个实例对象是不是这个构造函数创建的。

    通过Object.prototype.toString.call()。最全面的

    通过 instanceof 进行判断

    三、 this指向的问题

    四、改变 this 指向的方法

     五、new 的关键字用什么作用

    六、什么是构造函数?

    1、Function内置的构造函数和Object内置的构造函数他们之间的原形链的关系什么?

    2、JS 面向对象的内置构造函数

    Object:用于创建对象

    Function:创建一个方法

    Array:用于构造数组

    Number:

    String:

    Boolean

    七、原型、原型对象、原型链

    八、继承

    1、原型链继承

    2、构造函数继承(借助 call)

    3、组合继承(前两种的组合)

    4、原型式继承(Object.create)

    5、寄生式继承 

     6、寄生组合式继承

    九、JS的执行机制

    十、闭包

    1、什么是闭包 

    2、闭包的作用

    3、闭包用途

    4、使用闭包的注意点 

    5、经典面试题 



    一、对象与 JSON 的区别?

    • JSON 是对象的一个严格的子集
    • JOSN 中的key 必须加 " "
    • JSON 不能赋值
    • 使用JSON.parse() 会把JSON数据中值为undefined的数据过滤掉

    1、怎么让对象对自身进行深拷贝

    1. function ss(ob) {
    2. let obj = {}
    3. for (let a in ob) {
    4. if (ob[a] === ob) return Object.assign({}, ob)
    5. }
    6. }
    7. var dd = { name: '搜索', age: 16 }
    8. // 通过它触发改变
    9. Object.defineProperty(dd, 'age', {
    10. get() { return this }
    11. })
    12. let aa = ss(dd)
    13. console.log('aa', aa);
    14. console.log('判断', aa === dd);

    二、怎么判断js类型

    • 通过 typeof 判断。

      1. console.log(typeof 2); // number
      2. console.log(typeof '字符'); // string
      3. console.log(typeof true); // boolean
      4. console.log(typeof null); // object
      5. console.log(typeof undefined); // undefined
      6. console.log(typeof Symbol(3)); // symbol
      7. console.log(typeof function(){}); // function
      8. console.log(typeof NaN); // number
      9. console.log(typeof {name:'老六',age:66}); // object
      10. console.log(typeof ['1',2,'3']); // object

      用来判断基本数据类型,难以判断函数除外的复杂数据类型

    • 用 constructor 构造函数的方式,判断某个实例对象是不是这个构造函数创建的。

      1. let a = 2
      2. let b = '字符'
      3. let c = true
      4. let obj = {name:'老六',age:66}
      5. let arr = ['1',2,'3']
      6. let fun = function(){}
      7. console.log(a.constructor == Number); // true
      8. console.log(b.constructor == String); // true
      9. console.log(c.constructor == Boolean); // true
      10. console.log(obj.constructor == Object); // true
      11. console.log(arr.constructor == Array); // true
      12. console.log(fun.constructor == Function); // true

      原理:因为构造函数创建的时候,会有一个prototype属性指向原型对象,而原型对象中会有一个constructor属性指回来构造函数

    • 通过Object.prototype.toString.call()。最全面的

      1. console.log(Object.prototype.toString.call(2)); // [object Number]
      2. console.log(Object.prototype.toString.call('字符')); // [object String]
      3. console.log(Object.prototype.toString.call(true)); // [object Boolean]
      4. console.log(Object.prototype.toString.call(null)); // [object Null]
      5. console.log(Object.prototype.toString.call(undefined)); // [object Undefined]
      6. console.log(Object.prototype.toString.call(Symbol(3))); // [object Symbol]
      7. console.log(Object.prototype.toString.call(function(){})); // [object Function]
      8. console.log(Object.prototype.toString.call(NaN)); // [object Number]
      9. console.log(Object.prototype.toString.call({name:'老六',age:66})); // [object Object]
      10. console.log(Object.prototype.toString.call(['1',2,'3'])); // [object Array]

      必须是Object原型对象上的toString方法才能判断,其他原型对象上的不能判断。

      并且进行数据类型判断,我们需要借助apply方法或者call方法或者bind方法。

      例如:Array原型对象上的toString方法是判断不了的,因为它重写了toString方法。

      注意:判断不了自定义构造函数创建出来的实例对象

    • 通过 instanceof 进行判断

      1. console.log(function(){} instanceof Function); // true
      2. console.log(new Date instanceof Date); // true
      3. console.log({name:'老六',age:66} instanceof Object); // true
      4. console.log(['1',2,'3'] instanceof Array); // true

      instanceof:检查一个对象(引用类型)是否为构造函数(类)的实例

      可以用来判断内置对象

    三、 this指向的问题

    • 方法() 调用 this指向window
    • 一个函数通过对象打点调用,this指向这个对象
    • 事件处理函数,谁触发this就指向谁
    • 定时器里面的匿名函数this指向window
    • 一个方法在数组里面通过下标的方式去调用,this指向数组,

    总结:谁调用 this就指向谁

    四、改变 this 指向的方法

    手写实现

    call():可以调用函数,同时改变this的指向,只能传字符串形式参数

    apply():可以调用函数,同时改变this指向,只能传数组形式参数

    bind() :不能调用函数,它是重新定义一个函数并返回,只能传字符串形式参数

    1. function fun(a,b){
    2. console.log('this',this);
    3. console.log(a,b);
    4. // 调用fun(obj,'普通'),返回undefined,因为window身上没有age
    5. console.log(this.age);
    6. }
    7. var obj = {
    8. name: '小明',
    9. age:5
    10. }
    11. fun(obj,'普通') // this 指向window
    12. fun.call(obj,3,333) // this 指向obj
    13. fun.apply(obj,[6,666]) // this 指向obj
    14. fun.bind(obj)(9,999) // this 指向obj

     五、new 的关键字用什么作用

    • 在函数内部(内存)创建了一个局部变量,是一个空的对象

    • 构造函数中的this(上下文)指向这个空的对象

    • 在这新对象中添加一个__proto__属性,指向构造函数的原型对象prototype

    • 所有的语句执行完毕,函数将自动return这个新对象(this),如果是引用类型,就返回这个引用类型的对象(值)

     手写流程:

    1. // 手写new
    2. function newFunc(Func,...args) {
    3. // 1.创建一个新对象
    4. let newObj = {}
    5. // 2.将新对象和构造函数通过原型链连接
    6. newObj.__proto__ = Func.prototype
    7. // 3.将构造函数的this绑定到新对象上
    8. const result = Func.apply(newObj,args)
    9. // onsole.log(result) // 值为undefined
    10. // 4.根据返回值类型判断,,如果是引用类型直接返回值,否则返回this(创建的新对象)
    11. return result instanceof Object ? result : newObj
    12. }
    13. // 测试
    14. function Person(name, age) {
    15. this.name = name;
    16. this.age = age;
    17. // 若没有return 相当于 return undefined
    18. // return {sex:'男'} // 返会引用类型
    19. }
    20. // 添加往Person原型对象上添加一个sayName方法
    21. Person.prototype.sayName = function (){
    22. console.log('名字:',this.name);
    23. }
    24. const person1 = newFunc(Person, 'Tom', '18')
    25. console.log(person1);
    26. person1.sayName()

    六、什么是构造函数?

    • 当一个函数被new调用的时候,这个函数就是一个构造函数
    • 构造函数就是一个普通的函数,里面可以写任何的语句,只不过this指向的是一个对象

    • 构造函数里面写了return的语句会发生两种不同的处理方式。如下:

    1. 如果return 基本数据类型(number null boolean string undefined)的话,程序会被打断执行
    2. 如果return 引用数据类型(Array Date Function Object。。。),那么return的值会覆盖掉原来的值

    1、Function内置的构造函数和Object内置的构造函数他们之间的原形链的关系什么?

    • 任何的函数都是Function 的实例对象
    • Object也是Function new出来的实例对象
    • Function 自己也是自己的实例对象
    • 任何对象都是Object的实例对象。

    2、JS 面向对象的内置构造函数

    • Object:用于创建对象

    字面量形式创建的对象其实都是Object new的实例对象。

    • Function:创建一个方法

    字面量创建的函数都是Function new出来的实例对象

    • Array:用于构造数组

    任何的数组的字面量的形式的对象,都是Array new出来的实例对象

    • Number:

    任何字面量创建number类型都是Number new出来的实例对象

     注意:使用 Number 构造函数创建的对象,也可以进行计算,但有时候有坑

    1. var a = new Number(9)
    2. console.log(a);
    3. if(a) alert('success')
    4. else alert('fail')
    • String:

    字符串有很多方法,这些方法都是定义在String的prototype上面(原形上面)

    • Boolean

    Var  a=true

    Var a=new Boolean(true)

    总结: 所有的内置的对象,其实都是内置的构造函数

    七、原型、原型对象、原型链

    Prototype:每一个构造函数都有属性,指向一个空的对象(原型)

    Prototype 不需要去定义,天生就有

    People.prototype 是People 构造函数的原型

    People.prototype 是小明或者小红的原型对象

    原型链:任何一个构造函数都有一个属性叫做prototype指向了一个对象,当这个构造函数被new出来的时候,它的每一个实例的__proto__属性 也指向这个对象

     __proto__:原型链查找的功能,当小明或者小红没有这个属性或者方法的时候,她就会沿着原型链往上找,直到确定原型对象是否有这个属性或者方法。

    Constructor属性:任何一个构造函数的prototype身上都有一个 constructor属性指向他的构造函数

    1. function person(name, age, gender) {
    2. this.name = name;
    3. this.age = age;
    4. this.gender = gender;
    5. }
    6. person.prototype.toString = function () {
    7. console.log('aaa');
    8. return "person[" + this.name + "," + this.age + "," + this.gender + "]"
    9. }
    10. var a = new person("猪八戒", 29, "男");
    11. console.log(a); //一个实例化对象,a
    12. a.toString() // 打印 aaa
    13. console.log(a.__proto__.constructor); // 打印 person这个函数

    instanceof运算符:用来检测一个对象是不是某一个构造函数的实例

    console.log(对象 instanceof 构造函数);

     总结:函数的prototype指向谁,函数new出来的玩意的__proto__就指向谁

    面试题:

    八、继承

    1、原型链继承

    缺点:多个实例化对象共享一个原型对象,一个发送改变,另一个也随着改变

    1. function Person() {
    2. this.name = 'xiangming'
    3. this.age = [1, 2, 3]
    4. }
    5. Person.prototype.shuohua = function () {
    6. console.log("说话")
    7. }
    8. function obj() {
    9. this.type = '老六'
    10. }
    11. obj.prototype = new Person()
    12. var a = new obj()
    13. console.log(a);
    14. a.shuohua()

    2、构造函数继承(借助 call)

    优点:解决原型链继承不能传递参数给父类

    缺点:父类定义方法只能定义在父类的构造函数里面,不能定义在原型上

    总结:只能继承父类的实例属性和方法,不能继承原型属性或者方法。

    1. function Person(age){
    2. this.name = '小明'
    3. this.age = age
    4. this.sayName = function(){
    5. return this.name
    6. }
    7. }
    8. // 不能定义在外面
    9. // Person.prototype.sayName = function(){
    10. // return "this.name"
    11. // }
    12. function Chinese(name,age){
    13. Person.call(this,666)
    14. }
    15. var xiaoming = new Chinese(666)
    16. console.log(xiaoming);
    17. console.log(xiaoming.sayName()); // 小明

    3、组合继承(前两种的组合)

    原型链继承与构造函数继承的组合

    优点:能够解决原型链继承与构造函数继承的问题

    缺点:构造函数多执行了一遍,另外进行了一次性能开销

    1. function Person() {
    2. this.name = '老六';
    3. this.play = [1, 2, 3];
    4. }
    5. // 往Person原型上添加方法
    6. Person.prototype.getName = function () {
    7. return this.name;
    8. }
    9. function Child3() {
    10. // 第二次调用 Person()
    11. Person.call(this);
    12. this.type = 'child3';
    13. }
    14. // 第一次调用 Person()
    15. Child3.prototype = new Person();
    16. // 手动挂上构造器,指向自己的构造函数
    17. Child3.prototype.constructor = Child3;
    18. var s3 = new Child3();
    19. var s4 = new Child3();
    20. s3.play.push(4);
    21. console.log(s3.play, s4.play); // 不互相影响
    22. console.log(s3.getName()); // 正常输出'老六'
    23. console.log(s4.getName()); // 正常输出'老六'
    24. console.log(Child3.prototype.constructor);

    4、原型式继承(Object.create)

    Object.create()方法,这个方法接收两个参数:一是用作新对象原型的对象、二是为新对象定义额外属性的对象(可选参数)。可以实现浅拷贝的作用

    缺点:

    • 多个实例化对象共享一个原型对象,一个发送改变,另一个也随着改变,存在篡改的可能
    • 父类定义方法只能定义在父类的构造函数里面,不能定义在原型上
    • 拷贝后的对象(子类)不能直接添加方法,需要添加到原型对象上
    1. let Person={
    2. name : 'xiangming',
    3. age : [1,2,3],
    4. getName: function () {
    5. console.log(this.name);
    6. }
    7. }
    8. let obj1 = Object.create(Person)
    9. obj1.age.push(4)
    10. let obj2 = Object.create(Person)
    11. obj2.age.push('44')
    12. obj1.str=function(){
    13. console.log('我是新增的方法');
    14. }
    15. console.log('obj',obj1.age); //[1,2,3,4,'44']
    16. console.log('obj',obj2.age); //[1,2,3,4,'44']
    17. console.log('obj',obj1.__proto__); // Person
    18. obj1.getName() // xiangming
    19. obj2.str() // 报错。需要添加到原型对象上 obj1.__proto__.str
    20. //【Object.create的实现原理?】
    21. function obj_create(o){
    22. function F(){} //构造函数
    23. F.prototype = o; // 把F的原型替换为传递过来的Person
    24. return new F()
    25. }
    26. let Person={
    27. name : 'xiangming',
    28. age : [1,2,3]
    29. }
    30. let AA = obj_create(Person) // AA 是F的实例化对象
    31. console.log(AA); // 可实现原型链的向上查找功能

    5、寄生式继承 

    寄生式继承:使用原型式继承可以获得一份目标对象的浅拷贝,然后利用这个浅拷贝的能力再进行增强,添加一些方法。

    优缺点和原型式继承一样

    1. function obj_create(o) {
    2. function F() { } //构造函数
    3. F.prototype = o; // 把F的原型替换为传递过来的Person
    4. return new F()
    5. }
    6. function obj(o) {
    7. var chone = obj_create(o)
    8. chone.sayHi = function () {
    9. console.log('hi')
    10. }
    11. return chone
    12. }
    13. var Person = {
    14. name: "jill",
    15. frindes: ["sk", "xiaoming", "xiaogang"],
    16. age: function () {
    17. console.log(this.name);
    18. }
    19. }
    20. var A = obj(Person)
    21. console.log(A);
    22. A.sayHi()
    23. A.age()

     6、寄生组合式继承

    结合第四种中提及的继承方式,解决普通对象的继承问题的 Object.create 方法,在前面这几种继承方式的优缺点基础上进行改造,得出了寄生组合式的继承方式,这也是所有继承方式里面相对最优的继承方式

    九、JS的执行机制

    • 同步任务:代码从上到下顺序执行
    • 异步任务:又分为 宏任务 与 微任务
    1. 宏任务:script(整体代码)、setTimeout、setInterval、UI交互事件、postMessage、Ajax
    2. 微任务:Promise.then catch finally、MutaionObserver、process.nextTick(Node.js 环境)

     运行机制:所有的同步任务都是在主进程的执行中形成一个执行栈,主进程之外还有一个”任务队列(异步任务队列)“,在这个队列中先执行宏任务,在清空(执行)当前宏任务中的所有微任务,然后进行下一个tick形成循环。

    十、闭包

    闭包前我们需要清楚自执行函数、函数作用域、内存回收机制、作用域继承 

    • 自执行函数:可以无需调用,自动执行函数,传参方便
      1. /* 特性:1.自执行函数是很自私的,它的内部可以访问全局变量。
      2. 2.但是除了自执行函数自身内部,是无法访问它的。
      3. 3.自执行函数无需给函数名,因为根本没办法在其他地方调用,它本身只会执行一次 */
      4. function s1(a1,b1){
      5. return sum1 = a1 + b1
      6. }
      7. (function s2(a2,b2){
      8. console.log('内部',s2) // 打印s2函数
      9. return sum2 = a2 + b2
      10. ;})()
      11. console.log(s1) // 打印s1函数
      12. console.log(s2) //报错:autoFunc.html:38 Uncaught ReferenceError: s2 is not defined
      13. /* 总结:1.能够实现作用域的绝对隔离和函数命名冲突
      14. 2.主要用于闭包和创建独立的命名空间两个方面
      15. 3.自执行函数将某些代码包裹起来可以实现块级作用域的效果,减少全局变量的数量
      16. 4.自执行函数执行结束后变量就会被内存释放掉,从而也会节省了内存。 */
    • 函数作用域:函数要执行时就会在内存里面创建一个独立作用域————封闭的盒子。
      1. /*在函数执行完毕,这个独立作用域就会删除。
      2. 有一种情况下这个封闭的盒子是不会删除的,那就是“闭包”,*/
      3. // 函数执行
      4. function fn(){
      5. var a = 1
      6. }
      7. // 函数执行完毕
    • 内存回收机制:把用不到的内容空间,系统会自动清理回收提供给其它程序使用,
      1. /*内部函数引用外部的函数的变量,外部函数执行完毕,作用域也不会删除。
      2. 从而形成了一种不删除的独立作用域。*/
      3. function fn(){
      4. // b的独立作用域是不删除的,因为被内部函数引用了
      5. let b = 2
      6. return function(){
      7. // 内部函数引用了外部函数的变量 b
      8. console.log(b)
      9. }
      10. }

      某一个变量或者对象被引用,因此在回收的时候不会释放它,因为被引用代表着被使用,回收机制不会对正在引用的变量或对象进行回收的。

    • 作用域继承:在子作用域中可获取父作用域中的东西,但父作用域获取不到子作用域的东西

    1、什么是闭包 

    1. function fn(){
    2. // b的独立作用域是不删除的,因为被内部函数引用了
    3. let b = 2
    4. return function(){
    5. // 内部函数引用了外部函数的变量 b
    6. console.log(b)
    7. }
    8. }

    在一个函数里边再定义一个函数。这个内部函数一直保持有对外部函数中作用域的访问(小盒子可以一直访问大盒子但大盒子不能访问小盒子)。

    函数执行,形成一个独立作用域,保护里边的私有变量不受外界的干扰,除了保护私有变量外,还可以存储一些内容,这样的模式叫做闭包。

    2、闭包的作用

     通过一系方法,将函数内部的变量(局部变量)转化为全局变量

    3、闭包用途

    闭包可以用在许多地方。它的最大用处有两个,一个是前面提到的可以读取函数内部的变量,另一个就是让这些变量的值始终保持在内存中

    1. function f1() {
    2. var n = 999;
    3. nAdd = function () {
    4. alert(n += 1);
    5. }
    6. function f2() {
    7. alert(n);
    8. }
    9. return f2;
    10. }
    11. var result = f1();
    12. result(); //999
    13. nAdd();//1000
    14. result(); //1000

    注意:上面代码,首先在nAdd前面没有使用var关键字,因此nAdd是一个全局变量,而不是局部变量。 其次,nAdd的值是一个匿名函数(anonymous function),而这个匿名函数本身也是一个闭包,所以nAdd相当于是一个操控者,可以在函数外部对函数内部的局部变量进行操作。

    4、使用闭包的注意点 

    • 由于闭包会使得函数中的变量都被保存在内存中,内存消耗很大,所以不能滥用闭包,否则会造成网页的性能问题,在IE中可能导致内存泄露。解决方法是,在退出函数之前,将不使用的局部变量全部删除。
    • 闭包会在父函数外部,改变父函数内部变量的值。所以,如果你把父函数当作对象(object)使用,把闭包当作它的公用方法(Public Method),把内部变量当作它的私有属性(private value),这时一定要小心,不要随便改变父函数内部变量的值。

    5、经典面试题 

    1. for (var i = 1; i < 5; i++) {
    2. setTimeout(function () {
    3. console.log(i); // 5 5 5 5
    4. }, 0);
    5. }

    由于JavaScript是单线程的,setTimeout 是异步任务,JS会将其放入到任务队列中,待同步任务执行完毕后,才执行任务队列中的异步任务。

    因为setTimeout函数也是一种闭包,往上找它的父级作用域链(window),因为变量 i 是用 var 声明的全局变量,会被挂载到 window 上,所以变量i的值变成了i = 5,最后执行setTimeout时输出4个5

     解决方案:

    • 使用立即执行函数锁定参数值,把每次循环的索引传入,从而锁定索引值
      1. for (var i = 1; i < 5; i++) {
      2. (function (i) {
      3. setTimeout(function () {
      4. console.log(i);
      5. }, 0);
      6. })(i);
      7. }
    •  使用 let 声明(块级作用域)
      1. // 使用块级作用域变量关键字,会为每次循环创建独立的变量,从而每次打印都会有正确的索引值。
      2. for (let i = 1; i <= 5; i++) {
      3. setTimeout(function () {
      4. console.log(i);
      5. }, 0);
      6. }

  • 相关阅读:
    安卓端GB28181设备接入模块如何实现实时位置订阅(MobilePosition)
    数智未来 持续创新 | 易趋受邀出席CIAS 2022中国数智汽车峰会
    GB/T 8323.2塑料 烟生成 第2 部分:单室法测定烟密度试验方法
    ⭐每天一道leetcode:21.合并两个有序链表(简单;双指针)
    golang优化命令执行
    Network Address Translation,网络地址转换技术
    制造企业为什么要部署数字化工厂系统
    Spring Cloud(Finchley版本)系列教程(三) 服务消费者(Feign)
    C-13 循环语句while
    Shopee首届跨境品牌峰会落幕,升级全链路赋能品牌出海劲增!
  • 原文地址:https://blog.csdn.net/m0_55557411/article/details/127606611