1.JS中有哪些数据类型
大方面是两种:基本数据类型(栈存储)、引用数据类型(堆存储)
基本数据类型:number,string,boolean,null,undefind,symbol,bigint
引用数据类型:object,Function
其中object又可以分为:普通对象{},数组对象,正则对象,Math数学函数对象,Date日期对象等。
2.null和undefined区别
undefined是声明了一个变量但没有给这个变量赋值;
null是声明了一个变量同时也给这个变量赋了值,只不过这个值暂时是个空对象指针,不占用任何内存,后面可以随时替换掉;
这里会引申出来一个问题就是定义变量时用0赋值和用null赋值有什么区别?
0赋值会占用内存,而null赋值不会占用内存,所以一般定义变量会先复制null。它还有一个作用就是null把指针指向了一个空指针,以此来实现对一个内存的释放。
3.JS中数据类型检测的几种方式
1.typeof:用来检测数据类型的运算符
注意:taypeof返回的结果首先是字符串'',其次才是对应的数据类型。检测基本数据类型时,返回的是'对应的数据类型',检测函数对象时,返回的是'function',检测其他对象时,返回的是'object'。有一个特殊情况,检测null时,也会返回'object',但null并不是对象。
2.instanceof:用来检测当前实例是否属于某个类
<pre class="hljs language-awk" style="box-sizing: border-box; font-family: var(--bs-font-monospace); font-size: 0.875em; direction: ltr; unicode-bidi: bidi-override; display: block; margin-top: 0px !important; margin-bottom: 1.25rem; overflow: auto; color: rgb(36, 41, 46); background: rgb(233, 236, 239); padding: 1rem; max-height: 35rem; line-height: 1.5; position: relative;">//假如zhangsan是Student这个类构建出来的一个实例
//同时Student也是People的子类,
zhangsan instanceof Student //true
zhangsan instanceof People //true
//这里也可以认为Object也是Array的父类
[] instanceof Array //true
[] instanceof Object //true
{} instanceof Object //true</pre>
3.constructor: 基于构造函数检测数据类型(也是基于类的方式)
4.Object.prototype.toString.call():检测数据类型最好的办法
4.基本数据类型转换的几种方式
1.parseInt():处理的值是字符串,从字符串的左侧开始查找有效数字(遇到非有效数字字符则停止查找),如果处理的值不是字符串,需要先转换为字符串再进行查找。如下:
<pre class="hljs language-stylus" style="box-sizing: border-box; font-family: var(--bs-font-monospace); font-size: 0.875em; direction: ltr; unicode-bidi: bidi-override; display: block; margin-top: 0px !important; margin-bottom: 1.25rem; overflow: auto; color: rgb(36, 41, 46); background: rgb(233, 236, 239); padding: 1rem; max-height: 35rem; line-height: 1.5; position: relative;">parseInt('') //NaN
parseInt(null) //parseInt('null')->NaN
parseInt('12px') //12</pre>
parseFloat()如上
2.Number:它是按照浏览器最底层机制,把其它数据类型转换为数字
字符串:看是否包含非有效数字字符,如果包含,则结果就是NaN;如果是' '空字符串,则结果就是' '->0
布尔:true->1,false->0
null:->0
undefined:->NaN
-
引用类型值都要先转换为字符串再转换为数字
{}/函数/正则等 ->NaN
->' '->0
['12'] ->'12' ->12
-
[12,23] ->'12,23' ->NaN
基本数据类型判断如下:<pre class="hljs language-stylus" style="box-sizing: border-box; font-family: var(--bs-font-monospace); font-size: 0.875em; direction: ltr; unicode-bidi: bidi-override; display: block; margin-top: 0px !important; margin-bottom: 1.25rem; overflow: auto; color: rgb(36, 41, 46); background: rgb(233, 236, 239); padding: 1rem; max-height: 35rem; line-height: 1.5; position: relative;">Number(' ') //0
Number(null) //0
Number('12px') //NaN
</pre>
3.isNaN( ):除了数字类型和空字符串是false之外,其余一般都为true,如下:
<pre class="hljs language-livescript" style="box-sizing: border-box; font-family: var(--bs-font-monospace); font-size: 0.875em; direction: ltr; unicode-bidi: bidi-override; display: block; margin-top: 0px !important; margin-bottom: 1.25rem; overflow: auto; color: rgb(36, 41, 46); background: rgb(233, 236, 239); padding: 1rem; max-height: 35rem; line-height: 1.5; position: relative;">isNaN(' ') //先把' '隐式转换为数字0,isNaN(0)->false
isNaN(12) //false
isNaN('12px') //isNaN(NaN) ->true </pre>
其它值转换为数字的几种方法:
number,parseInt,parseFloat,==比较,数学运算
其它值转换为字符串的几种方法:
+字符串拼接,tostring()
其它值转换为布尔的几种方法:
Boolean,!/!!,条件判断
5.什么时候用==,什么时候用===
==:如果两边的类型不相同,会先转换为相同的类型,然后再进行比较
===:如果类型不相同,肯定不相等,不会默认转化数据类型
个人认为:除了判断==null时,其余时候都用===
==比较时的规则:
1.对象==字符串:对象转换为字符串去比较
2.null==undefined:但是和其它值都不相等
3.剩下的两边不同时:都转换为数字去比较
4.NaN和谁都不相等,包括它自己
6.var,let,const区别
1.作用域:ES5中的var只有全局作用域和函数作用域,而ES6中let,const,for(),try…catch..有块级作用域
2.变量提升:var存在变量提升,而let,const声明的变量不会变量提升,所以用它俩定义的变量一定要声明后再使用,否则会报错
3.重复声明变量:var允许,let,const不允许
4.全局变量和全局属性:var和function声明的全局变量可以作为全局对象的属性;而let,const声明的全局变量不可以作为全局对象的属性
5.const声明的变量必须经过初始化,如果值为简单数据类型,则不可以再做更改;如果值为复合数据类型,则可以修改里面的值,因为此时变量指向的是一个内存地址,const只是保证这个地址是固定的,至于里面的数据就不能控制了
7.如何获取安全的 undefined 值?
因为undefined是一个标识符,所以可以被当作变量来使用和赋值,但是这样会影响undefined的正常判断。
表达式void _ 没有返回值,因此返回结果是 undefined。void 并不改变表达式的结果,只是让表达式不返回值。
按惯例我们用void 0来获得 undefined。
8.什么是闭包,为什么要用它?
闭包是指有权访问另一个函数作用域中变量的函数,创建闭包的最常见的方式就是在一个函数内创建另一个函数,创建的函数可以访问到当前函数的局部变量。
闭包有两个常用的用途。
闭包的第一个用途是使我们在函数外部能够访问到函数内部的变量。通过使用闭包,我们可以通过在外部调用闭包函数,从而在外部访问到函数内部的变量,可以使用这种方法来创建私有变量。
函数的另一个用途是使已经运行结束的函数上下文中的变量对象继续留在内存中,因为闭包函数保留了这个变量对象的引用,所以这个变量对象不会被回收。
9.什么是原型和原型链
在 js 中我们是使用构造函数来新建一个对象的,每一个构造函数的内部都有一个 prototype 属性值,这个属性值是一个对象,这个对象包含了可以由该构造函数的所有实例共享的属性和方法。当我们使用构造函数新建一个对象后,在这个对象的内部将包含一个指针,这个指针指向构造函数的 prototype 属性对应的值,在 ES5 中这个指针被称为对象的原型。一般来说我们是不应该能够获取到这个值的,但是现在浏览器中都实现了proto 属性来让我们访问这个属性,但是我们最好不要使用这个属性,因为它不是规范中规定的。ES5中新增了一个Object.getPrototypeOf()方法,我们可以通过这个方法来获取对象的原型。
当我们访问一个对象的属性时,如果这个对象内部不存在这个属性,那么它就会去它的原型对象里找这个属性,这个原型对象又会有自己的原型,于是就这样一直找下去,也就是原型链的概念。原型链的尽头一般来说都是 Object.prototype 所以这就是我们新建的对象为什么能够使用 toString() 等方法的原因。
所有原型链的终点都是Object函数的prototype属性。Objec.prototype指向的原型对象同样拥有原型,不过它的原型是null,而null则没有原型。
10.项目当中哪里用到了原型
比如想求一个数组的平均值,但数组里是没有.average()方法,所以就需要自己加:
<pre class="hljs language-javascript" style="box-sizing: border-box; font-family: var(--bs-font-monospace); font-size: 0.875em; direction: ltr; unicode-bidi: bidi-override; display: block; margin-top: 0px !important; margin-bottom: 1.25rem; overflow: auto; color: rgb(36, 41, 46); background: rgb(233, 236, 239); padding: 1rem; max-height: 35rem; line-height: 1.5; position: relative;"> Array.prototype.average=funciton(){
let sum=0;
for(let i=0;i<this.length;i++){
sum+=this[ i ];
}
return sum/this.length
}</pre>
还有很多,比如:Vue.prototype.$axios=axios
11.什么是内存泄漏,哪些操作会导致内存泄漏
对于持续运行的服务进程,必须及时释放不再用到的内存,而这个不再用到的内存,没有及时释放,就叫做内存泄漏。否则,内存占用越来越高,轻则影响系统性能,重则导致进程崩溃。
导致内存泄漏的操作:
1.我们由于使用未声明的变量,而意外的创建了一个全局变量,而使这个变量一直留在内存中无法被回收。
2.我们设置了setInterval 定时器,而忘记取消它,如果循环函数有对外部变量的引用的话,那么这个变量会被一直留在内存中,而无法被回收。
3.我们获取一个DOM元素的引用,而后面这个元素被删除,由于我们一直保留了对这个元素的引用,所以它也无法被回收。
4.不合理的使用闭包,从而导致某些变量一直被留在内存当中。
12.如何实现深浅拷贝
见我的笔记中有一篇总结
13.JS中创建对象的几种方式
我们一般使用字面量的形式直接创建对象
<pre class="hljs language-abnf" style="box-sizing: border-box; font-family: var(--bs-font-monospace); font-size: 0.875em; direction: ltr; unicode-bidi: bidi-override; display: block; margin-top: 0px !important; margin-bottom: 1.25rem; overflow: auto; color: rgb(36, 41, 46); background: rgb(233, 236, 239); padding: 1rem; max-height: 35rem; line-height: 1.5; position: relative;">var obj = {}</pre>
但是这种创建方式对于创建大量相似对象的时候,会产生大量的重复代码。但js和一般的面向对象的语言不同,在ES6之前它没有类的概念。但是我们可以使用函数来进行模拟,从而产生出可复用的对象创建方式,我了解到的方式有这么几种:
1.工厂模式:工厂模式的主要工作原理是用函数来封装创建对象的细节,从而通过调用函数来达到复用的目的。但是它有一个很大的问题就是创建出来的对象无法和某个类型联系起来,它只是简单的封装了复用代码,而没有建立起对象和类型间的关系。
<pre class="hljs language-pgsql" style="box-sizing: border-box; font-family: var(--bs-font-monospace); font-size: 0.875em; direction: ltr; unicode-bidi: bidi-override; display: block; margin-top: 0px !important; margin-bottom: 1.25rem; overflow: auto; color: rgb(36, 41, 46); background: rgb(233, 236, 239); padding: 1rem; max-height: 35rem; line-height: 1.5; position: relative;">function createPerson(name,age,family) {
var o = new Object();
o.name = name;
o.age = age;
o.family = family;
o.say = function(){
alert(this.name);
}
return o;
}</pre>
2.构造函数模式:js 中每一个函数都可以作为构造函数,只要一个函数是通过new来调用的,那么我们就可以把它称为构造函数。执行构造函数首先会创建一个对象,然后将对象的原型指向构造函数的prototype属性,然后将执行上下文中的this指向这个对象,最后再执行整个函数,如果返回值不是对象,则返回新建的对象。因为this的值指向了新建的对象,因此我们可以使用this给对象赋值。构造函数模式相对于工厂模式的优点是,所创建的对象和构造函数建立起了联系,因此我们可以通过原型来识别对象的类型。但是构造函数存在一个缺点就是,造成了不必要的函数对象的创建,因为在 js 中函数也是一个对象,因此如果对象属性中如果包含函数的话,那么每次我们都会新建一个函数对象,浪费了不必要的内存空间,因为函数是所有的实例都可以通用的。
<pre class="hljs language-javascript" style="box-sizing: border-box; font-family: var(--bs-font-monospace); font-size: 0.875em; direction: ltr; unicode-bidi: bidi-override; display: block; margin-top: 0px !important; margin-bottom: 1.25rem; overflow: auto; color: rgb(36, 41, 46); background: rgb(233, 236, 239); padding: 1rem; max-height: 35rem; line-height: 1.5; position: relative;">function Person(name, age) {
this.name = name
this.age = age
this.say = function() {
console.log(name)
}
}
var person = new Person('zhangsan', 18)</pre>
3.原型模式:因为每一个函数都有一个prototype属性,这个属性是一个对象,它包含了通过构造函数创建的所有实例都能共享的属性和方法。因此我们可以使用原型对象来添加公用属性和方法,从而实现代码的复用。这种方式相对于构造函数模式来说,解决了函数对象的复用问题。但是这种模式也存在一些问题,一个是没有办法通过传入参数来初始化值,另一个是如果存在一个引用类型如Array这样的值,那么所有的实例将共享一个对象,一个实例对引用类型值的改变会影响所有的实例。
<pre class="hljs language-javascript" style="box-sizing: border-box; font-family: var(--bs-font-monospace); font-size: 0.875em; direction: ltr; unicode-bidi: bidi-override; display: block; margin-top: 0px !important; margin-bottom: 1.25rem; overflow: auto; color: rgb(36, 41, 46); background: rgb(233, 236, 239); padding: 1rem; max-height: 35rem; line-height: 1.5; position: relative;">function Person() {}
Person.prototype.name = "lisi";
Person.prototype.age = 21;
Person.prototype.say = function(){
console.log(this.name);
};
var person1 = new Person();</pre>
4.组合使用构造函数模式和原型模式:这是创建自定义类型的最常见方式。因为构造函数模式和原型模式分开使用都存在一些问题,因此我们可以组合使用这两种模式,通过构造函数来初始化对象的属性,通过原型对象来实现函数方法的复用。这种方法很好的解决了两种模式单独使用时的缺点,但是有一点不足的就是,因为使用了两种不同的模式,所以对于代码的封装性不够好。
<pre class="hljs language-javascript" style="box-sizing: border-box; font-family: var(--bs-font-monospace); font-size: 0.875em; direction: ltr; unicode-bidi: bidi-override; display: block; margin-top: 0px !important; margin-bottom: 1.25rem; overflow: auto; color: rgb(36, 41, 46); background: rgb(233, 236, 239); padding: 1rem; max-height: 35rem; line-height: 1.5; position: relative;">function Person(name, age) {
this.name = name
this.age = age
}
Person.prototype.say = function() {
console.log(this.name)
}
var person1 = new Person('zhangsan')</pre>
5.动态原型模式:这一种模式将原型方法赋值的创建过程移动到了构造函数的内部,通过对属性是否存在的判断,可以实现仅在第一次调用函数时对原型对象赋值一次的效果。这一种方式很好地对上面的混合模式进行了封装。
<pre class="hljs language-javascript" style="box-sizing: border-box; font-family: var(--bs-font-monospace); font-size: 0.875em; direction: ltr; unicode-bidi: bidi-override; display: block; margin-top: 0px !important; margin-bottom: 1.25rem; overflow: auto; color: rgb(36, 41, 46); background: rgb(233, 236, 239); padding: 1rem; max-height: 35rem; line-height: 1.5; position: relative;">function Person(name, age, job) {
this.name = name;
this.age = age;
// 方法
if (typeof this.sayName != "function") {
Person.prototype.sayName = function() {
alert(this.name);
}
}
}
var person1 = new Person("ZhangSan", 18);
person1.sayName();</pre>
6.寄生构造函数模式:这一种模式和工厂模式的实现基本相同,我对这个模式的理解是,它主要是基于一个已有的类型,在实例化时对实例化的对象进行扩展。这样既不用修改原来的构造函数,也达到了扩展对象的目的。它的一个缺点和工厂模式一样,无法实现对象的识别。
<pre class="hljs language-arcade" style="box-sizing: border-box; font-family: var(--bs-font-monospace); font-size: 0.875em; direction: ltr; unicode-bidi: bidi-override; display: block; margin-top: 0px !important; margin-bottom: 1.25rem; overflow: auto; color: rgb(36, 41, 46); background: rgb(233, 236, 239); padding: 1rem; max-height: 35rem; line-height: 1.5; position: relative;">function SpecialArray(){
var array = new Array();
array.push.apply(array,arguments);
array.toPipedString = function(){
return this.join("|");
};
return array;
}
var colors = new SpecialArray("red","green","pink");
alert(colors.toPipedString());// red|green|pink
alert(colors instanceof SpecialArray); // false</pre>
还有稳妥构造函数模式,想要了解的小伙伴可以去参考‘红宝书’
14.JS中继承的几种方式
1.以原型链的方式来实现继承:这种实现方式存在的缺点是,在包含有引用类型的数据时,会被所有的实例对象所共享,容易造成修改的混乱。还有就是在创建子类型的时候不能向父类型传递参数。要想为子类新增属性和方法,必须要在new语句之后执行,不能放到构造器中。
<pre class="hljs language-stylus" style="box-sizing: border-box; font-family: var(--bs-font-monospace); font-size: 0.875em; direction: ltr; unicode-bidi: bidi-override; display: block; margin-top: 0px !important; margin-bottom: 1.25rem; overflow: auto; color: rgb(36, 41, 46); background: rgb(233, 236, 239); padding: 1rem; max-height: 35rem; line-height: 1.5; position: relative;">function Animal() {}
Animal.prototype.name = 'cat'
Animal.prototype.age = 1
Animal.prototype.say = function() {console.log('hello')}
var cat = new Animal()
cat.name // cat
cat.age // 1
cat.say() // hello</pre>
2.借用构造函数的方式:这种方式是通过在子类型的函数中调用父类型的构造函数来实现的,这种方法解决了不能向父类型传递参数的缺点,但是它存在的一个问题就是父类型原型定义的方法和属性子类型也没有办法访问到,而是通过call方式的实现的继承,无法实现函数方法的复用,占用堆内存。
<pre class="hljs language-stylus" style="box-sizing: border-box; font-family: var(--bs-font-monospace); font-size: 0.875em; direction: ltr; unicode-bidi: bidi-override; display: block; margin-top: 0px !important; margin-bottom: 1.25rem; overflow: auto; color: rgb(36, 41, 46); background: rgb(233, 236, 239); padding: 1rem; max-height: 35rem; line-height: 1.5; position: relative;">function Animal() {
this.species = "动物"
}
function Cat(name, age) {
Animal.call(this)
this.name = name
this.age = age
}
var cat = new Cat('豆豆', 2)
cat.name // 豆豆
cat.age // 2
cat.species // 动物</pre>
3.组合继承,组合继承是将原型链和借用构造函数组合起来使用的一种方式。通过借用构造函数的方式来实现类型的属性的继承,通过将子类型的原型设置为超类型的实例来实现方法的继承。这种方式解决了上面的两种模式单独使用时的问题,但是由于我们是以父类型的实例来作为子类型的原型,所以调用了两次父类的构造函数,造成了子类型的原型中多了很多不必要的属性,同样会占用内存。
<pre class="hljs language-javascript" style="box-sizing: border-box; font-family: var(--bs-font-monospace); font-size: 0.875em; direction: ltr; unicode-bidi: bidi-override; display: block; margin-top: 0px !important; margin-bottom: 1.25rem; overflow: auto; color: rgb(36, 41, 46); background: rgb(233, 236, 239); padding: 1rem; max-height: 35rem; line-height: 1.5; position: relative;">function Animal() {
this.species = "动物"
}
function Cat(name){
Animal.call(this)
this.name = name
}
Cat.prototype = new Animal() // 重写原型
Cat.prototype.constructor = Cat</pre>
4.原型式继承,原型式继承的主要思路就是基于已有的对象来创建新的对象,实现的原理是,向函数中传入一个对象,然后返回一个以这个对象为原型的对象。这种继承的思路主要不是为了实现创造一种新的类型,只是对某个对象实现一种简单继承,ES5中定义的Object.create()方法就是原型式继承的实现。缺点与原型链方式相同。
<pre class="hljs language-reasonml" style="box-sizing: border-box; font-family: var(--bs-font-monospace); font-size: 0.875em; direction: ltr; unicode-bidi: bidi-override; display: block; margin-top: 0px !important; margin-bottom: 1.25rem; overflow: auto; color: rgb(36, 41, 46); background: rgb(233, 236, 239); padding: 1rem; max-height: 35rem; line-height: 1.5; position: relative;">function Animal() {
this.species = "动物"
}
Animal.prototype.age = 2
function Dog(object) {
function Fun(){}
Fun.prototype = obj
return new Fun()
}
let animal = new Animal()
let dog = Dog(animal)
console.log(dog.age) //2</pre>
5.寄生式继承,寄生式继承的思路是创建一个用于封装继承过程的函数,通过传入一个对象,然后复制一个对象的副本,然后对象进行扩展,最后返回这个对象。这个扩展的过程就可以理解是一种继承。这种继承的优点就是对一个简单对象实现继承,如果这个对象不是我们的自定义类型时。缺点是没有办法实现函数的复用。
6.(常用)寄生式组合继承,组合继承的缺点就是使用父类型的实例做为子类型的原型,导致添加了不必要的原型属性。寄生式组合继承的方式是使用父类型的原型的副本来作为子类型的原型,这样就避免了创建不必要的属性。
<pre class="hljs language-javascript" style="box-sizing: border-box; font-family: var(--bs-font-monospace); font-size: 0.875em; direction: ltr; unicode-bidi: bidi-override; display: block; margin-top: 0px !important; margin-bottom: 1.25rem; overflow: auto; color: rgb(36, 41, 46); background: rgb(233, 236, 239); padding: 1rem; max-height: 35rem; line-height: 1.5; position: relative;">function Father() {
this.name = "父亲";
this.run = function () {
console.log(this.name + "开始跑步")
}
}
Father.prototype.eat = function() {
console.log(this.name + "开始吃饭")
};
function Son(age) {
Father.call(this);
this.age = age;
this.sleep = function () {
console.log(this.name + "开始睡觉")
}
}
Son.prototype = Object.create(Father.prototype);
Son.prototype.constructor = Son;
Son.prototype.playGame = function () {
console.log(this.name + "开始玩游戏")
};
const son = new Son(10);
console.log(son.name); //父亲
console.log(son.age); //10
son.run(); //父亲开始跑步
son.eat(); //父亲开始吃饭
son.sleep(); //父亲开始睡觉
son.playGame(); //父亲开始玩游戏</pre>
7.使用ES6中的class实现类继承
见我的笔记中有一篇 ‘使用class实现继承’
15.new操作符具体做了什么?如何重写new函数?
帮我们创建一个空对象(实例)
设置原型,让这个新对象的proto指向函数的.prototype
让函数中的this指向这个新对象,执行构造函数的代码,为这个新对象 添加私有属性。
-
判断函数的返回值类型,如果函数没有写return或者return的是基本类型值,就把创建的实例对象返回,如果return的是引用类型,就直接返回这个引用类型的对象,例如:
<pre class="hljs language-vim" style="box-sizing: border-box; font-family: var(--bs-font-monospace); font-size: 0.875em; direction: ltr; unicode-bidi: bidi-override; display: block; margin-top: 0px !important; margin-bottom: 1.25rem; overflow: auto; color: rgb(36, 41, 46); background: rgb(233, 236, 239); padding: 1rem; max-height: 35rem; line-height: 1.5; position: relative;"> function func(x){
let y = 200
this.x=x
return 10
}
let res = new func(100)
console.log(res) //func{x:100},因为return的是一个基本类型
console.log(res.x) //100
console.log(res.y) //undefined,因为y不是res的私有属性
console.log(x) //报错x is not defined,因为此时的x是res的私有属性,this不再指向了window</pre>上面这4步操作都是V8引擎底层去帮我们实现的。
重写具体实现:<pre class="hljs language-javascript" style="box-sizing: border-box; font-family: var(--bs-font-monospace); font-size: 0.875em; direction: ltr; unicode-bidi: bidi-override; display: block; margin-top: 0px !important; margin-bottom: 1.25rem; overflow: auto; color: rgb(36, 41, 46); background: rgb(233, 236, 239); padding: 1rem; max-height: 35rem; line-height: 1.5; position: relative;">//构造函数
function Dog(name){
this.name = name
}
Dog.prototype.bark = function(){
console.log('wangwang')
}
Dog.prototype.sayName = function(){
console.log('my name is' + this.name)
}
//重写new函数
function _new(Fn){
//获取参数集合,借用数组原型上的slice方法实现把类数组转换为数组
let args = Array.prototype.slice.call(arguments,1)
//创建一个空对象
let obj = {}
//让新建对象的原型指向构造函数的原型对象
obj.proto = Fn.prototype
//把函数执行,把函数中的this指向obj,把参数传给obj
let res = Fn.apply(obj,args)
if(res !==null && (typeof res === 'object' || typeof res === 'function')){
return res
}
return obj
}
//用ES6语法进行优化
function _new(Fn, ...args){
// 新建一个空对象,对象的原型为构造函数的 prototype 对象
let obj = Object.create(Fn.prototype)
//把函数执行,把函数中的this指向obj,把参数传给obj
let res = Fn.call(obj, ...args)
if(res !==null && (typeof res === 'object' || typeof res === 'function')){
return res
}
return obj
}
//进行使用
let samo = _new(Dog, '萨摩')
samo.bark() //'wangwang'
samo.sayName() //'my name is萨摩'
console.log(samo.name) //'萨摩'</pre>16.谈谈对this的理解
this 是执行上下文中的一个属性,它指向最后一次调用这个方法的对象。在实际开发中,this的指向可以通过四种调用模式来判断。
(1)函数调用模式,当一个函数不是一个对象的属性时,直接作为函数来调用时,this指向全局对象。<pre class="hljs language-kotlin" style="box-sizing: border-box; font-family: var(--bs-font-monospace); font-size: 0.875em; direction: ltr; unicode-bidi: bidi-override; display: block; margin-top: 0px !important; margin-bottom: 1.25rem; overflow: auto; color: rgb(36, 41, 46); background: rgb(233, 236, 239); padding: 1rem; max-height: 35rem; line-height: 1.5; position: relative;">function fun() {
console.log(this)
}
fun() //window</pre>(2)方法调用模式,如果一个函数作为一个对象的方法来调用时,this指向这个对象。
<pre class="hljs language-javascript" style="box-sizing: border-box; font-family: var(--bs-font-monospace); font-size: 0.875em; direction: ltr; unicode-bidi: bidi-override; display: block; margin-top: 0px !important; margin-bottom: 1.25rem; overflow: auto; color: rgb(36, 41, 46); background: rgb(233, 236, 239); padding: 1rem; max-height: 35rem; line-height: 1.5; position: relative;">let obj = {
eat: '牛排',
run: function () {
console.log(this.eat)
}
}
obj.run() //'牛排' this=>obj</pre>(3)构造器调用模式,如果一个函数用new调用时,函数执行前会新创建一个对象,this指向这个新创建的对象。
<pre class="hljs language-pgsql" style="box-sizing: border-box; font-family: var(--bs-font-monospace); font-size: 0.875em; direction: ltr; unicode-bidi: bidi-override; display: block; margin-top: 0px !important; margin-bottom: 1.25rem; overflow: auto; color: rgb(36, 41, 46); background: rgb(233, 236, 239); padding: 1rem; max-height: 35rem; line-height: 1.5; position: relative;">function Dog(name) {
this.name = name
}
let samo = new Dog('萨摩')
console.log(samo.name) //'萨摩' this=>samo</pre>(4)通过apply 、 call 和 bind 的调用
这四种方式,使用构造器调用模式的优先级最高,然后是 apply 、 call 和 bind 调用模式,然后是方法调用模式,然后是函数调用模式。
17.apply()、call()、bind()的区别
这三个方法都可以指定调用函数的this指向。其中apply方法接收两个参数:一个是this绑定的对象,一个是参数数组。call方法接收的参数:第一个是this绑定的对象,后面的其余参数是传入函数执行的参数。也就是说,在使用call()方法时,传递给函数的参数必须逐个列举出来。bind方法通过传入一个对象,返回一个 this 绑定了传入对象的新函数。这个函数的this指向除了使用new时会被改变,其他情况下都不会改变。
<pre class="hljs language-javascript" style="box-sizing: border-box; font-family: var(--bs-font-monospace); font-size: 0.875em; direction: ltr; unicode-bidi: bidi-override; display: block; margin-top: 0px !important; margin-bottom: 1.25rem; overflow: auto; color: rgb(36, 41, 46); background: rgb(233, 236, 239); padding: 1rem; max-height: 35rem; line-height: 1.5; position: relative;">var name = "全局";
var age = 20;
var p1 = {
name: "张三",
age: 12,
func: function (from) {
console.log(姓名:${this.name},年龄:${this.age},来自:${from}
)
}
}
var p2 = {
name: "李四",
age: 32,
}
p1.func() //姓名:张三,年龄:12, 因为直接通过p1调用func方法时,this指向的是当前p1这个对象
p1.func.call(null,'北京') //姓名:全局,年龄:20, 因为当使用call方法,传入的第一个参数为空或者null时,this指向window
p1.func.apply(p2,['北京']) //姓名:李四,年龄:15,来自:北京, 因为第一个参数传入p2时,就把函数的this指向了p2对象,所以会得到p2的属性值
p1.func.bind(p2,['北京'])() //姓名:李四,年龄:15,来自:北京,因为bind会返回一个this绑定了传入参数的新对象,所以要用()去调用</pre>
18.手写 call、apply 及 bind 函数
<pre class="hljs language-javascript" style="box-sizing: border-box; font-family: var(--bs-font-monospace); font-size: 0.875em; direction: ltr; unicode-bidi: bidi-override; display: block; margin-top: 0px !important; margin-bottom: 1.25rem; overflow: auto; color: rgb(36, 41, 46); background: rgb(233, 236, 239); padding: 1rem; max-height: 35rem; line-height: 1.5; position: relative;"> call函数实现
Function.prototype.myCall = function (context) {
// 判断调用对象
if (typeof this !== "function") {
console.error("type error");
}
// 获取参数
let args = [...arguments].slice(1),
let result = null;
// 判断 context 是否传入,如果未传入则设置为 window
context = context || window;
// 将调用函数设为对象的方法
context.fn = this;
// 调用函数
result = context.fn(...args);
// 将属性删除
delete context.fn;
return result;
}
apply 函数实现
Function.prototype.myApply = function (context) {
// 判断调用对象是否为函数
if (typeof this !== "function") {
throw new TypeError("Error");
}
let result = null;
// 判断 context 是否存在,如果未传入则为 window
context = context || window;
// 将函数设为对象的方法
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("Error");
}
var fn = this;
// 获取参数
var args = [...arguments].slice(1),
return function Fn() {
// 根据调用方式,传入不同绑定值
return fn.apply(this instanceof Fn ? this : context, args.concat(...arguments));
}
}</pre>
call 函数的实现步骤:
(1)判断调用对象是否为函数,即使我们是定义在函数的原型上的,但是可能出现使用 call 等方式调用的情况。
(2)判断传入上下文对象是否存在,如果不存在,则设置为 window 。
(3)处理传入的参数,截取第一个参数后的所有参数。
(4)将函数作为上下文对象的一个属性。
(5)使用上下文对象来调用这个方法,并保存返回结果。
(6)删除刚才新增的属性。
(7)返回结果。
apply 函数的实现步骤:
(1)判断调用对象是否为函数,即使我们是定义在函数的原型上的,但是可能出现使用 call 等方式调用的情况。
(2)判断传入上下文对象是否存在,如果不存在,则设置为 window 。
(3)将函数作为上下文对象的一个属性。
(4)判断参数值是否传入
(5)使用上下文对象来调用这个方法,并保存返回结果。
(6)删除刚才新增的属性
(7)返回结果
bind 函数的实现步骤:
(1)判断调用对象是否为函数,即使我们是定义在函数的原型上的,但是可能出现使用 call 等方式调用的情况。
(2)保存当前函数的引用,获取其余传入参数值。
(3)创建一个函数返回
(4)函数内部使用 apply 来绑定函数调用,需要判断函数作为构造函数的情况,这个时候需要传入当前函数的 this 给 apply 调用,其余情况都传入指定的上下文对象。
19.箭头函数和普通函数的区别
1.箭头函数用箭头定义,普通函数用function
2.箭头函数全都是匿名函数,普通函数可以是匿名也可以是具名函数
3.箭头函数不能用于构造函数,普通函数可以
4.箭头函数不具有arguments对象,可以用rest参数替代,不具有prototype对象,不具有super,不具有new.target
5.普通函数的this指向调用函数的对象,可以用call,apply改变this指向
6.箭头函数本身没有this,它的this是在它声明时捕获它所处作用域中的this:
-
如果箭头函数声明在全局作用域下,那么它的this就是全局作用域的this,即window
<pre class="hljs language-arcade" style="box-sizing: border-box; font-family: var(--bs-font-monospace); font-size: 0.875em; direction: ltr; unicode-bidi: bidi-override; display: block; margin-top: 0px !important; margin-bottom: 1.25rem; overflow: auto; color: rgb(36, 41, 46); background: rgb(233, 236, 239); padding: 1rem; max-height: 35rem; line-height: 1.5; position: relative;">var num = 10
var func = () => {
console.log(this.num);
}
func() //10, 此时的this指向全局window</pre> -
如果箭头函数声明在一个函数体当中,那么它的this就是这个函数作用域中的this,谁调用这个函数它就指向谁,如下:
<pre class="hljs language-javascript" style="box-sizing: border-box; font-family: var(--bs-font-monospace); font-size: 0.875em; direction: ltr; unicode-bidi: bidi-override; display: block; margin-top: 0px !important; margin-bottom: 1.25rem; overflow: auto; color: rgb(36, 41, 46); background: rgb(233, 236, 239); padding: 1rem; max-height: 35rem; line-height: 1.5; position: relative;">var father = {
age: 40,
outerFunc() {
var innerFunc = () => {
console.log(2 * this.age);
};
innerFunc();
}
};
father.outerFunc(); //80 </pre>对于内层函数innerFunc,它本身并没有this值,其使用的this来自作用域链,来自更高层函数的作用域。innerFunc的外层函数outerFunc是普通函数,它是有this值的,它的this值就是调用它的father对象
20.同步和异步的区别,异步加载的方式有哪些?
同步:指的是当一个进程在执行某个请求的时候,如果这个请求需要等待一段时间才能返回,那么这个进程会一直等待下去,直到消息返回为止再继续向下执行。
异步:指的是当一个进程在执行某个请求的时候,如果这个请求需要等待一段时间才能返回,这个时候进程会继续往下执行,不会阻塞等待消息的返回,当消息返回时系统再通知进程进行处理。
异步加载方式有以下几种(我了解的):
1.<script>
标签的async="async"属性,async的定义和用法(是HTML5的属性);async
属性规定一旦脚本可用,则会异步执行:
(1)<script type="text/javascript" src="xxxxxxx.js" async="async"></script>
(2)HTML5中新增的属性,Chrome、FF、IE9&IE9+均支持(IE6~8不支 持)。此外,这种方法不能保证脚本按顺序执行。
(3)async 属性仅适用于外部脚本(只有在使用 src 属性时)。
2.<script>
标签的defer="defer"属性,这个属性会让脚本的加载与文档的解析同步解析,然后在文档解析完成后再执行这个脚本文件,这样的话就能使页面的渲染不被阻塞。此方法可以确保所有设置了defer
属性的脚本按顺序执行。
3.$(document).ready(),此方法需要引入jQuery,兼容所有浏览器
<pre class="hljs language-xml" style="box-sizing: border-box; font-family: var(--bs-font-monospace); font-size: 0.875em; direction: ltr; unicode-bidi: bidi-override; display: block; margin-top: 0px !important; margin-bottom: 1.25rem; overflow: auto; color: rgb(36, 41, 46); background: rgb(233, 236, 239); padding: 1rem; max-height: 35rem; line-height: 1.5; position: relative;"><head>
<script src="http://common.cnblogs.com/script/jquery.js" type="text/javascript"></script>
<script type="text/javascript"> $(document).ready(function() {
alert("加载完成!");
}); </script>
</head></pre>
4.动态创建<script>
标签
<pre class="hljs language-scheme" style="box-sizing: border-box; font-family: var(--bs-font-monospace); font-size: 0.875em; direction: ltr; unicode-bidi: bidi-override; display: block; margin-top: 0px !important; margin-bottom: 1.25rem; overflow: auto; color: rgb(36, 41, 46); background: rgb(233, 236, 239); padding: 1rem; max-height: 35rem; line-height: 1.5; position: relative;">(function(){
var script = document.createElement('script');
script.type = 'text/javascript';
script.src = "http://code.jquery.com/jquery-1.7.2.min.js";
var tmp = document.getElementsByTagName('script')[0];
tmp.parentNode.insertBefore(script, tmp);
})();</pre>
5.创建并插入iframe,让它异步执行js
21.延迟加载JS的方式
1.定时器方式:
<pre class="hljs language-xml" style="box-sizing: border-box; font-family: var(--bs-font-monospace); font-size: 0.875em; direction: ltr; unicode-bidi: bidi-override; display: block; margin-top: 0px !important; margin-bottom: 1.25rem; overflow: auto; color: rgb(36, 41, 46); background: rgb(233, 236, 239); padding: 1rem; max-height: 35rem; line-height: 1.5; position: relative;"><script id=”my”></script>
<script> setTimeout(document.getElementById('my').src='include/index.js',3000);//延时3秒 </script> </pre>
2.是我们一般采用的是将 js 脚本放在文档的底部,来使 js 脚本尽可能的在最后来加载执行。
22.
持续更新
</article>
[图片上传失败...(image-ab4bbf-1659442034417)]
阅读 970<time datetime="2020-05-22T02:06:40.000Z" itemprop="datePublished" style="box-sizing: border-box;">更新于 2021-11-23</time>
赞1 收藏1
本作品系原创,采用《署名-非商业性使用-禁止演绎 4.0 国际》许可协议
0 条评论
[图片上传失败...(image-ce8bdb-1659442034418)]
<textarea rows="1" placeholder="撰写评论 …" aria-label="评论" class="comment-text form-control" style="box-sizing: border-box; margin: 0px; font-family: inherit; font-size: 1rem; line-height: 1.5; resize: none; display: block; width: 704px; padding: 0.375rem 0.75rem; font-weight: 400; color: rgb(33, 37, 41); background-color: rgb(255, 255, 255); background-clip: padding-box; border: 1px solid rgb(206, 212, 218); appearance: none; border-radius: 0.25rem; transition: border-color 0.15s ease-in-out 0s, box-shadow 0.15s ease-in-out 0s; min-height: calc(1.5em + 0.75rem + 2px); overflow: hidden; overflow-wrap: break-word; height: 38px;"></textarea>
评论支持部分 Markdown 语法:**粗体** _斜体_ [链接](http://example.com)
代码- 列表 > 引用
。你还可以使用 @
来通知其他用户。
继续阅读
[##### 前端面试之CSS
盒模型:(1)有两种盒子模型:IE盒模型(border-box)、W3C标准盒模型(content-box)(2)盒模型:分为内容(content)、填充(padding)、边界(margin)、边框(border)四个部分
薛定谔吥养猫阅读 864](https://segmentfault.com/a/1190000022742249?utm_source=sf-similar-article) [##### 前端面试之webpack篇
还是以前一样,有些概念面试可能会考,我都用*标记了出来,两句话就总结清楚其余的地方如果你想了解webpack,就仔细看看,虽然本教程不能让你webpack玩的很6,但是懂操作流程够了。面试你一般问你webpack的原理,...
云中歌赞 81阅读 45.5k评论 6](https://segmentfault.com/a/1190000011383224?utm_source=sf-similar-article) [##### 前端面试之JavaScript(总结)
1. JS基本的数据类型和引用类型 基本数据类型:number、string、null、undefined、boolean、symbol -- 栈 引用数据类型:object、array、function -- 堆 两种数据类型存储位置不同 原始数据类型是直接存储在栈(st...
小小蚊子赞 51阅读 4.5k评论 2](https://segmentfault.com/a/1190000015294769?utm_source=sf-similar-article) [##### 前端面试之websocket篇