Javascript面试知识点 *Javascript (一):封装 *Javascript(二):构造函数的继承 *Javascript(三):非构造函数的继承 一、内置类型 二、Type of 三、类型转换 3.1 boolean 3.2对象转基本类型 3.3 四则运算符 四、原型 五、instanceof(实例) 六、this 七、闭包 八、深浅拷贝 浅拷贝 深拷贝 九、call、apply、bind使用和区别 十、Map、FlatMap 和 Reduce Map和FlatMap的区别 十一、async 和 await

注:链接大部分是笔记的参考文章,想详细了解的可以点进去看。

http://www.ruanyifeng.com/blog/2010/05/object-oriented_javascript_inheritance.html

function Cat(name,color){
  this.name = name;
  this.color = color;
}
 Cat.prototype.type = "猫科动物";
 Cat.prototype.eat = function(){alert("吃鱼")};
  
let cat1 = new Cat("小黄","black");
let cat2 = new Cat("锋锋","white");
cat1.eat();

*Javascript(二):构造函数的继承

//动物类
function Animal(){
  this.species = "动物";
}
//猫类
function Cat(name,color){
  this.name = name;
  this.color = color;
}

//如何让猫继承动物呢?

一、 构造函数绑定

使用call或apply方法

function Cat(name,color){
  Animal.apply(this,arguments);
  this.name = name;
  this.color = color;
}
var cat1 = new Cat;
alert("cat1.species");//动物

二、 prototype模式

Cat.prototype = new Animal();
Cat.prototype.constructor = Cat;
var cat1 = new Cat;
alert("cat1.species");//动物

任何一个prototype对象都有一个constructor属性,指向它的构造函数.
当执行了Cat.protoType = new Animal 这句代码后,Cat.protoType.constructor指向Animal
而实例car1的cat1.constructor也指向Animal

因此我们必须手动纠正,将Cat.prototype对象的constructor值改为Cat。

为了避免继承链紊乱,下文都遵循这一点,即如果替换了prototype对象,

o.prototype = new plant();
o.prototype.constructor = o;

三、直接继承prototype

function Animal(){}
Animal.prototype.species = "动物";
Cat.prototype = Animal.prototype;
Cat.prototype.constructor = Cat;

虽然这种继承方法相较第二种效率更高更省内存(不用建立Animal实例),但此时,Animal.protoType.constructor也指向了Cat。这种方法其实是错误的。

四、利用空对象作为中介

function F(){
}
F.prototype = Animal.prototype;
Cat.prototype = new F();
Cat.prototype.constructor = Cat;
alert(Animal.prototype.constructor);//Animal

这样F()几乎不占内存,且修改Cat的prototype.constructor不会影响到Animal

将上面的方法封装一下,以便调用

function extend(Child,Parent){
    function F(){}
    F.prototype = Parent.prototype;
    Child.prototype = new F();
    Child.prototypr.constructor = Child;
    Child.uber = Parent.prototype;
}
    

然后就可以调用啦

extend(Cat,Animal);
let cat1 = new Cat("锋锋","黑色");
alert(cat1.species);//动物

五、拷贝继承

function Animal(){
  
}
Animal.prototype.species = "动物";

function extend2(Child,Parent){
  var c = Child.prototype;
  var p = Parent.prototype;
  
  for(var i in p){
    c[i]=p[i];
  }
  c.uber = p;
}

extend2(Cat,Animal);
let cat1 = new Cat("锋锋","黑色");
alert(cat1.species);//动物

*Javascript(三):非构造函数的继承

非构造函数的继承,比如:对象间的继承

var Chinese = {nation:'中国'}
var Doctor = {career:'医生'}

function object(o){
  function F(){}
  F.prototype = o;
  return new F();
}

var Doctor = object(Chinese);
//Doctor.career = '医生';
alert(Doctor.nation);

一、内置类型

NaN表示未定义,或不可表示的值
boolean ->true/ false
函数就是对象

Javascript面试知识点
*Javascript (一):封装
*Javascript(二):构造函数的继承
*Javascript(三):非构造函数的继承
一、内置类型
二、Type of
三、类型转换
3.1 boolean
3.2对象转基本类型
3.3 四则运算符
四、原型
五、instanceof(实例)
六、this
七、闭包
八、深浅拷贝
浅拷贝
深拷贝
九、call、apply、bind使用和区别
十、Map、FlatMap 和 Reduce
Map和FlatMap的区别
十一、async 和 await

二、Type of

typeof 对于基本类型,除了 null 都可以显示正确的类型
对于 null 来说,虽然它是基本类型,但是会显示 object,这是一个存在很久了的 Bug

三、类型转换

3.1 boolean

在条件判断时,除了 undefined, null, false, NaN, '', 0, -0,其他所有值都转为 true,包括所有对象。(不懂)

3.2对象转基本类型

对象在转换基本类型时,首先会调用 valueOf 然后调用 toString。这两个方法可以重写。
(先赋值,后类型转换)

let a = {
  valueOf() {
    return 0;
  },
  toString() {
    return '1';
  },
  [Symbol.toPrimitive]() {
    return 2;
  }
}
1 + a // => 3
'1' + a // => '12'

3.3 四则运算符

当数字和字符串进行运算时:如: '2'和4
加法运算:字符串优先级高。其它运算,数字优先级高。

'2'+4='24'
'2'*4 =8
[1, 2] + [2, 1] // '1,22,1'
'a' + + 'b' // -> "aNaN"

因为: + '1' -> 1

四、原型

构造函数
指向 就是“===”(完全等于)
prototype上的所有属性,子孙都可以共享

function Foo() {}
const f1 = new Foo();
f1.__proto__ === Foo.prototype;  // 实例(子)的__proto__ === 其构造函数(父)的原型(prototype)
Foo.__proto__ === Function.prototype;

Foo.prototype.constructor ===Foo(); 

五、instanceof(实例)

》f1 instanceof Foo

》true

六、this

http://www.ruanyifeng.com/blog/2018/06/javascript-this.html
谁调用this指向谁

七、闭包

【详情见:http://www.ruanyifeng.com/blog/2009/08/learning_javascript_closures.html
一般情况下:

function f1(){
  var n=100;
  function f2(){
    alert(n);  //100
  }
}

一般情况下,内部函数可以读取全局变量,但是外部函数无法读取内部变量。

但是!!!在f1函数return内部f2函数!!就可以在f1外部读取到内部变量了!!

闭包的目的:能够读取其他函数内部变量的函数
在下面代码中,f2函数就是闭包

function f1(){
  let n=100;
  function f2(){
    alert(n);
  }
  return f2(); 
}

let result = f1();
result();  //100

闭包的用途

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

看下面的例子:

function f1(){
  let n=100;
  add=function(){n+=1;}
  
  funciton f2(){
    alert(n);
  }
  retrun f2();
}

let result = f1();
result(); //100
add();
result();//101

result运行了两次。一次值为100,第二次值为101,这证明了函数f1中的局部变量n一直保存在内存中,没有在f1调用后被自动清除。

使用闭包的注意点

  1. 由于闭包会使函数中的变量都被保存在内存中,所以不能滥用闭包。解决方法是,在退出函数之前,将不使用的局部变量全部删除。

  2. 闭包会在父函数外部,改变父函数内部变量。所以一定要小心,不要随便改变父函数内部变量的值。

思考题

例子1:

var name = "The Window";

  var object = {
    name : "My Object",

    getNameFunc : function(){
      return function(){
        return this.name;

      };

    }

  };

  alert(object.getNameFunc()());

例子2:

 var name = "The Window";

  var object = {
    name : "My Object",

    getNameFunc : function(){
      var that = this;
      return function(){
        return that.name;

      };

    }

  };

  alert(object.getNameFunc()());

例子1 输出结果是:The Window
例子2 输出结果是:My Object

this的指向是由它所在函数调用的上下文决定的,而不是由它所在函数定义的上下文决定的。

例子1中alert(object.getNameFunc()());这句调用的时候在window中,所以this指向window

八、深浅拷贝

浅拷贝

var Chinese={nation:'中国'};
var Doctor ={career:'医生'};

function extendcopy(p){
  var c={};
  for( var i in p){
    c[i]=p[i]
  }
  c.uber=p;
  return c;
}

Chinese.birthPlace=['广州','四川','上海'];
var Doctor = extendcopy(Chinese);
Doctor.birthPlace.push('厦门');
alert(Chinese.birthPlace);//广州,四川,上海,厦门

当父对象属性为数组时,子对象改变,会将父对象的属性改变。
extendCopy()只是拷贝基本类型的数据,我们把这种拷贝叫做"浅拷贝"

深拷贝

function deepcopy(p,c){
   var c = c ||{} //如果不传入c,则赋c为{
   for( var i in p){
     if(typeof p[i] === 'object' ){
       c[i] = (p[i].construcor===array)?[]:{};
       deepcopy(p,c);
     }else{
       c[i]=p[i];
     }
   }
   return c;
   }

这样写,父对象就不会受到影响了。

九、call、apply、bind使用和区别

https://juejin.im/post/5a9640335188257a7924d5ef

首先,它们的作用是什么呢?
答案是:改变函数执行时的上下文,再具体一点就是改变函数运行时的this指向。

  let obj = {name: 'tony'};
  
  function Child(name){
    this.name = name;
  }
  
  Child.prototype = {
    constructor: Child,
    showName: function(){
      console.log(this.name);
    }
  }
  var child = new Child('thomas');
  child.showName(); // thomas
  
  //  call,apply,bind使用
  child.showName.call(obj);
  child.showName.apply(obj);
  let bind = child.showName.bind(obj); // 返回一个函数

bind(); // tony

  • call、apply与bind的差别

call和apply改变了函数的this上下文后便执行该函数,而bind则是返回改变了上下文后的一个函数。

  • call、apply的区别

他们俩之间的差别在于参数的区别,call和aplly的第一个参数都是要改变上下文的对象。
除了第一个参数外,call 可以接收一个参数列表,apply 只接受一个参数数组。

 let arr1 = [1, 2, 19, 6];
//例子:求数组中的最值
console.log(Math.max.call(null, 1,2,19,6)); // 19
console.log(Math.max.call(null, arr1)); // NaN
console.log(Math.max.apply(null, arr1)); //  19 直接可以用arr1传递进去

主要应用:
1、将伪数组转化为数组(含有length属性的对象,dom节点, 函数的参数arguments)

case1: dom节点:

<div class="div1">1</div>
<div class="div1">2</div>
<div class="div1">3</div>

let div = document.getElementsByTagName('div');
console.log(div); // HTMLCollection(3) [div.div1, div.div1, div.div1] 里面包含length属性

let arr2 = Array.prototype.slice.call(div);//Array.prototype.slice,浅拷贝
console.log(arr2); // 数组 [div.div1, div.div1, div.div1]


十、Map、FlatMap 和 Reduce

Map

语法:var new_array = arr .map(function callback(currentValue [,index [,array]]){
//返回new_array的元素
} [, thisArg ])

  • 参数:currentValue 必选,当前元素

index 可选,索引
arr 可选,原数组

[1, 2, 3].map((v) => v + 1)
// -> [2, 3, 4] 

FlatMap

语法:var new_array = arr .flatMap(function callback(currentValue [,index [,array]]){
//返回new_array的元素
} [, thisArg ])

Map和FlatMap的区别

区别1:
arr1.map( x => [x * 2] ); 
 // [[2], [4], [6], [8]]

 arr1.flatMap(x => [x * 2] ); 
  // [2, 4, 6, 8]
  
区别2:(flatMap原数组将维)
[1, [2], 3].flatMap((v) => v + 1)

// -> [2, 3, 4]

Reduce
Reduce 作用是数组中的值组合起来,最终得到一个值

function a() {
    console.log(1);
}

function b() {
    console.log(2);
}

[a, b].reduce((a, b) => a(b()))

// -> 2 1

十一、async 和 await

async是异步的意思。
一个函数如果加上 async ,那么该函数就会返回一个 Promise

async function test() {
  return "1";
}
console.log(test()); // -> Promise {<resolved>: "1"}

await只能用在async函数中,就是用于等待异步完成

function sleep() {
  return new Promise(resolve => {
    setTimeout(() => {
      console.log('finish')
      resolve("sleep");
    }, 2000);
  });
}
async function test() {
  let value = await sleep();
  console.log("object");
}
test()

在上面代码中会先打印‘finish’再打印‘object’,因为await会等待sleep()执行完。

async和await相比Promise的优缺点:

优点:处理 then 的调用链,能够更清晰准确的写出代码。

缺点:滥用 await 可能会导致性能问题,因为 await 会阻塞代码,也许之后的异步代码并不依赖于前者,但仍然需要等待前者完成,导致代码失去了并发性。