参考: 前端面试复习之JS复习 juejin.cn/post/684490…
-
基础数据类型和引用数据类型
基础数据类型:String、Number、Boolean、undefined、null
引用数据类型:Object
-
数据类型的存储形式
- 栈内存(stack)和堆内存(heap);
- Stack自动分配内存,Heap动态分配内存;
- 一般在项目中将对象类型置为null,减少内存消耗
- 基础数据类型按值存在stack中,可按值直接访问
- 对象类型在stack中存放引用地址,在heap中存放具体对象
-
this指向
a. 谁调用this,this就指向谁。 b. 通过new 的方式,this永远指向新创建的对象。
function Person(name,age){ this.name = name this.age = age console.log(this) } var xiaohu = new Person('小胡',24)// this = > xiaohub. 箭头函数中没有this,会继承所创建位置的this。
console.log(this) let A = { get: function () { console.log(1, this) let get1 = () => { console.log(2, this) } get1(); } } A.get();//两个console出来的this值相同 -
new过程
a. 创建一个新对象; b. 将新对象的__proto__指向构造函数的原型对象; c. 将构造函数的this指向新对象; d. 将新对象返回给实例。
function A(){}; var b=new A() /** 过程: a. 创建一个新对象:let o=new Object() b. 将新对象的__proto__指向构造函数的原型对象 o.__proto__=A.prototypr c. 将构造函数的this指向新对象;A.call(o) d. 将新对象返回给实例。b=o **/``function newInstance(cons) { let o = new Object(); o.__proto__ = cons.prototype; cons.call(o) return o } function A() { } A.prototype.get = function () { console.log('get') } let b = newInstance(A) b.get() //get -
改变this的指向call、apply、bind
共同点: 第一个参数是this指向的对象,后续为传的参数
不同点:
a. call是单个传参,apply是数组行驶传参,bind都可以
b. call和apply直接执行,bind需要手动调用。
-
new/字面量与Object.create(null)创建对象
new和字面量创建的对象的原型指向Object.prototype,会继承Object的属性和方法, 而Object.create(null)创建的对象,其原型指向null,null作为原型链的顶端,没有也不会继承任何属性和方法
-
作用域链
作用域:规定变量和函数的可使用范围叫做作用域。
a. 每个函数都会有一个作用域,查找变量或函数时,由局部作用域到全局作用域依次查找,这些作用域的集合就叫做作用域链。
b. 当我们调用一个函数时,会创建这个函数的执行环境;执行环境又称为执行上下文,他定义了变量或函数有权访问的其他数据,决定了他们的各自行为。最外围的执行环境称为全局执行环境,在 web 浏览器中,被认为是 window 对象;每个执行环境都有一个变量对象,这个对象我们是无法访问的,变量对象上保存的是当前执行环境中定义的函数和变量。当我们查找一个变量时,会先从当前执行环境的变量对象上查找,找到了则返回这个变量,如果没有找到,会去父级执行环境的变量对象上查找,直到全局执行环境的变量对象,这种形式,会形成作用域链。作用域链本质上是一个指向变量对象的指针列表,它只引用但不实际包含变量对象。
-
闭包
有权访问另一个函数作用域中的变量的函数。
创建闭包的常见方式是在一个函数返回另一个函数。
缺点:
- 内存泄漏。因为匿名函数会在外部被调用,导致外层函数的变量对象一直被引用,一直存放在内存中,无法清除。解决办法是清除匿名函数。
- this指向window。匿名函数的执行环境具有全局性,所以匿名函数中的this是指向全局的,所以需要记录一下外层函数的this值,才能传到匿名函数中。
let A = { get: function () { console.log(1, this) return (function () { console.log(2, this) })() } } A.get();//第2个console打印出window -
原型和原型链
原型:每个JS对象都有__proto__属性,这个属性指向了原型。
原型链:多个对象通过__proto__的方式连接起来。
每个构造函数都有一个原型对象 prototype,原型对象 prototype 都有一个指针 constructor 指向构造函数,每个对象都有一个属性__proto__指向原型对象,让原型对象等于另一个构造函数的实例,那么这个原型对象的属性__proto__就会指向另一个构造函数的原型对象,这样层层递进,就形成了原型链。
-
继承
A. 经典继承(构造函数)
//1、当用调用 call 方法时,this 指向 son 。 //2、此时 Father 构造函数中的 this 指向 son。 //3、也就是说 son 有了 colors 的属性。 //4、每 new 一个 son ,都会产生不同的对象,每个对象的属性都是相互独立的。 function Father(){ this.colors = ["red","blue","green"]; } function Son(){ // this 是通过 new 操作内部的新对象 {} , // 此时 Father 中的 this 就是为 Son 中的新对象{} // 新对象就有了新的属性,并返回得到 new 的新对象实例 // 继承了Father,且向父类型传递参数 Father.call(this); } let s = new Son(); console.log(s.color)基本思想:在子类的构造函数的内部调用父类的构造函数。
优点:
-
保证了原型链中引用类型的独立,不被所有实例共享。
-
子类创建的时候可以向父类传参。
缺点:
-
继承的方法都在构造函数中定义,构造函数不能复用。
-
父类中的方法对子类而言是不可见的,子类所有属性都定义在父类的构造函数中。
B. 组合继承
function Father(name){ this.name = name; this.colors = ["red","blue","green"]; } // 方法定义在原型对象上(共享) Father.prototype.sayName = function(){ alert(this.name); }; function Son(name,age){ // 子类继承父类的属性 Father.call(this,name); //继承实例属性,第一次调用 Father() // 每个实例都有自己的属性 this.age = age; } // 子类和父类共享的方法(实现了父类属性和方法的复用) Son.prototype = new Father(); //继承父类方法,第二次调用 Father() // 子类实例对象共享的方法 Son.prototype.sayAge = function(){ alert(this.age); } var instance1 = new Son("louis",5); instance1.colors.push("black"); console.log(instance1.colors);//"red,blue,green,black" instance1.sayName();//louis instance1.sayAge();//5 var instance1 = new Son("zhai",10); console.log(instance1.colors);//"red,blue,green" instance1.sayName();//zhai instance1.sayAge();//10基本思想:
-
使用原型链实现对原型对象属性和方法的继承
-
借用构造函数来实现对实例属性的继承
优点:
-
在原型兑现上定义的方法实现了函数的复用。
-
每个实例都有属于自己的属性。
缺点:组合继承调用了两次父类的构造函数,造成了不必要的消耗。
C. 原型继承
function object(o){ function F(){} F.prototype = o; // 每次返回的 new 是不同的 return new F(); } var person = { friends : ["Van","Louis","Nick"] }; // 实例 1 var anotherPerson = object(person); anotherPerson.friends.push("Rob"); // 实例 2 var yetAnotherPerson = object(person); yetAnotherPerson.friends.push("Style"); // 都添加至原型对象的属性(所共享) alert(person.friends); // "Van,Louis,Nick,Rob,Style"基本思想:创建临时的构造函数,将传入的对象作为该构造函数的原型对象,然后返回新构造函数的实例。
浅拷贝:object产生的对象是不相同的,但是原型对象都是person对象,所改变存在原型对象的属性被所有生成的实例所共享,不仅Person拥有,而且子类生成的实例也共享。 Object.create():在ECMAScript5中新增了此方法。
参数一:新对象的原型对象
参数二:新对象定义的额外的属性。(可选)
D. 寄生式继承
function createAnother(original){ var clone = object(original); // 通过调用object函数创建一个新对象 clone.sayHi = function(){ // 以某种方式来增强这个对象 alert("hi"); }; return clone; //返回这个对象 }基本思想:不必为了指定子类的原型而调用父类的构造函数。
优点:解决组合继承中两次调用构造函数的开销。
-
-
深拷贝浅拷贝
浅拷贝:拷贝第一层,与原来的拷贝对象还有点关系。
深拷贝:多层全部拷贝下来,与原来的拷贝对象没有关系。
深拷贝和浅拷贝的区别是内存中存储类型不同。基本数据类型的值和大小是固定的,存储在栈中,释放的时候是系统自动释放;引用类型是存储在堆中,大小是动态分配的,不会自动释放。对于一个基本数据类型,例如一个变量a=1;我将a的值赋给变量b,会在栈中生成一个a的副本,对于对象,如果以赋值方法将一个对象付给另一个变量,只是将一个指针付给了这个变量,然后,需要我们对于堆中的对象也进行复制,对于对象中的引用类型没有进行复制的是浅拷贝,对于对象中引用类型也进行复制的是深拷贝。
-
判断数据类型(typeof incetanceof区别)
typeof:判断基本类型,返回undefined,boolean,string,number,返回相应的字符串,function类型返回function,对于null,引用类型等返回object。
incetanceof:判断某个对象是不是另一个对象的实例,原理是根据构造函数来判断。
Object.prototype.toString.call():返回相应的数据类型。
typeof '字符串' //"string" typeof 1 //"number" typeof true //"boolean" typeof undefined //"undefined" typeof null //"object" typeof [1,2,3] //"object" function a(){} typeof a // "function" function B(){} let b=new B() b instanceof B //true Object.prototype.toString.call('zifuchuan') //"[object String]" Object.prototype.toString.call(1) // "[object Number]" Object.prototype.toString.call(true) // "[object Boolean]" Object.prototype.toString.call(undefined) // "[object Undefined]" Object.prototype.toString.call(null) //"[object Null]" Object.prototype.toString.call(b) // "[object Object]" Object.prototype.toString.call(B) // "[object Function]" Object.prototype.toString.call([1,2,3]) // "[object Array]" Object.prototype.toString.call({a:1}) //"[object Object]"