JavaScript基础解析

JS封神篇

一:说说你对JavaScript 的最直接的了解

这个问题怎么说呢,
		官方的讲,
		通过这几年对js接触和使用,
		我感觉它时而强大,时而笨拙,有时候无所不能,有事时候
		也让人抓耳挠腮,尤其近年来node的火热,js的地位可谓更
		不可取代,作为 前端开发灵魂吧,js的重要性就
		不在强调了,总的概括 就是掌握js的精髓我感觉答题划分几个
		部分,首先 它的语法结构我们要深入了解,熟练用js语言玩耍
		我们的业务逻辑,其次就是对js高级部分 例如 面向对象开发,
		原型,闭包,各种回调等等 有深入的理解,其次就是当你掌握
		js 到一定程度时 自己能够有封装类似jquery的功能函数。反正
		还是那句话吧, js乃前端的灵魂和核心 至少目前为止是这样,
		我认为  呵呵呵!
	
	面试官:“小伙子,精辟!!!”

二:js 的数据类型有哪些,你认为目前这些数据类型完善吗?
1.js的数据类型 包括两大类:
分别是 基本数据类型,和引用数据类型
基本:string number boolean null undefined
引用:Object Array Function

    2.一般我用什么 关键字 来判别数据类型
       那肯定是 typeof操作符啦

    3.你认为js的数据类型很严谨吗?
       哦,这个...
       这么说吧,js本身就是一门 松散型的 脚本语言,它
       的数据类型的定义 肯定也是不够严谨的,向我们平时
       使用的一些数据类型 即使定义错了,也不报错,不过
       这种状况也在 慢慢改善,例如 ES6 里面就新增了,let
       ,const 关键字 相对来更接近一些 严格的后台语言那样
	   
    4.你认为目前的数据类型足够完善吗?
       完善的话,我想肯定是否定的,因为随着技术和业务现实
       的不断结合,当下我们认识的 肯定日后有满足不了业务需要
       的情况,就比如说 现在有一些火热 视屏 图像 音频 用户行为
       等等的资源,都可能是促进 我们数据类型扩展的一些要求,反正
       个人认为,没有足够完善 是能越来越改善 	

三:变量声明后 它在内存结构中是如何体现的?
概述:
说到这个问题 首先提几个概念,什么是数据,什么是变量, 什么是内存
1. 我认为数据简单地说,就是在编程语言中它是存储在内存或硬盘中代表特定信息,其最大的特点就是 可存储,可修改。我们常说一切皆对象,有它的道理但细究的话也不全面,对象实际上是数据的一种容器,或者说是一系列有关系的数据的集合。我觉的更准确的说法是一切皆数据。

       2. 变量 简单解释就是 可变化的量, 由变量名和变量值组成,在js中变量名是

字符串类型的每个变量都对应的一块小内存, 变量名用来查找对应的内存,
变量值就是内存中保存的数据。变量的值不会是一个对象,引用类型变量保存的是对象数据的在堆内存中的地址

       3. 内存 就是我们计算机的储存数据的空间,当然准确点讲,是通电情况下的

储存空间,临时的。它分为 堆和栈 结构

    总结:当我们声明一个 基本数据类型的变量 它直接就会 存到栈空间,用的时候

根据变量名获取变量值。 当变量是引用类型时 它会将对象的变量名存到栈
空间,对象值保存到堆空间,我们访问的时候 会先到栈空间找到对应的指
针也就是变量名,然后再去获取相对应的值。简单地说就是 基本数据类型
直接保存它的值,而引用数据类型 存的是指向堆内存的地址。

    经典的实验:分别声明一个基本数据类型的变量和一个引用数据类型的变量,当给基

本数据类型的变量重新赋值后 它之前赋给第二个变量的是还是存在的。
反之,引用数据类型由于是保存的是值得指针也就是地址,当你改变之
前的值,第二个被赋值的变量也会改变。
/基本数据类型/
var num1 = 5;
var num2 = num1;
num1 = 6;
alert(num2);//5
/引用数据类型/
var obj1=new Object();
var obj2=obj1;
obj1.name=“Jim”;
alert(obj2.name);//“Jim”

四:什么是变量提升,或者预解析 以及我们需要注意的问题?

  概述:所谓变量提升 就是只要我们进行变量或者函数声明,当js加载的时候,js引

擎就会将变量声明提升到它所在作用域的最开始的部分。
注意的的地方:
–当变量名和函数名同名的时候,以函数名优先

五 你能区别一下 typeof 和 instanceof

扫描二维码关注公众号,回复: 6179103 查看本文章
1.先来说说typeof吧。首先需要注意的是,typeof方法返回一个字符串,来表示数据的类型。		  
	  大体对照情况就是这样:
		Undefined                             “undefined”
		Null	                                   “object”
		布尔值	                              “boolean”
		数值	                              “number”
		字符串	                              “string”
		Symbol (ECMAScript 6 新增)	             “symbol”
		宿主对象(JS环境提供的,比如浏览器)	  Implementation-dependent
		函数对象	                          “function”                
		任何其他对象	                      “object”	
	  存在的问题:
		我们会发现一个问题,就是typeof来判断数据类型其实并不准确。比如数组、

正则、日期、对象的typeof返回值都是object,这就会造成一些误差。
解决:
所以在typeof判断类型的基础上,我们还需要利用Object.prototype.toString方
法来进一步判断数据类型。

2.接下来该说说instanceof方法了。instanceof运算符可以用来判断某个构造函数
  的prototype属性是否存在于另外一个要检测对象的原型链上。它的返回值是Boolean

六:js中this指向 简单的说说??
概述:
这个问题简单的可总结为三点
1. 全局作用域或者普通函数中this指向全局对象window。
2. 方法调用中谁调用this指向谁
3. 在构造函数或者构造函数原型对象中this指向构造函数的实例
当然,比较特殊的一个地方也要提一嘴,那就是ES6中 的箭头函数
this 指向需要记住以下两点:
1.箭头函数的this绑定看的是this所在的函数定义在哪个对象下,
绑定到哪个对象则this就指向哪个对象
2.如果有对象嵌套的情况,则this绑定到最近的一层对象上

七:你是如何 理解js中的面向对象说法呢?
概述:说道面向对象,我认为 js中的面向对象可谓是 大道至简,回归原始,
其他语言中说道面向对象,必然会有类的概念,还有明确的继承等等。
而js中是没有的,有的只是最经典的 object,从object出发,他的原型
继承以及类式继承 也体现了继承的思想,同时js中的模块化思想也照应了
其他面向对象中的“类”,以至于我们js也是 另一种风格的面向对象语言,
做到了 高内聚,低耦合的表现。对于代码的扩展和未来的优化也是逐步升级。
当然,ES6 中出现了 “class 类”的概念 以及 extends 这也标志js逐步也在往
纯后端语言靠拢。这就是我大概的理解。

八:js中集中创建对象的方式你有什么见解??
概述:
分细了说,创建对象的方式五花八门,有很多,什么字面量,工厂,构造函数,原
型等等。简单的划分大体我们最常用的 我感觉就有三种:
1. 直接创建 也就是字面量形式
将成员信息写到{}中,并赋值给一个变量,此时这个变量就是一个对象

		2. 构造函数创建方式
		   var obj = new 函数名();
		   
		3. 通过object方式创建。
		   先通过object构造器new一个对象,再往里丰富成员信息。
		   var obj = new Object(); 
	总结:这是最常用的,如果要说工厂模式也算一种,我感觉有点牵强,工厂模式只

不过是相当于一个 生产池 将创建对象的事情放到内部函数中 最后将其返
回,它内部的结构也是构造函数 我大体是这么理解的。

九:new一个对象背后做了些什么?
概述:
1.创建一个空对象
2.给对象设置__proto, 指向(等价于)构造函数对象的prototype属性值
3.执行构造函数体(给对象添加属性/方法)

十:你对 js中的 函数有什么理解和看法?
概述:通过我的经验和理解,咱们js中的函数 总的来讲分为js内置函数和我们的自
定义函数,内置函数不多介绍了,自定义函数里大体分为几种类型
1. 字面量形式的函数
function 函数名(形参){
函数体
}

	   2.命名函数
		 var 函数名 = function(形参){
		     函数体
		 }
	   
	   3.构造函数
	     var 函数名 = new Function(形参,函数体);
         注意:形参 和 函数体 必须用引号括起来
		 
	   4.回调函数
	     注意:回调函数本质也是自定义的普通函数,当它作为参数出现的时候
		       就是我们所谓的回调函数了
			   
	   5.自调函数
	     (function(){
		   函数体;
		 })();
	     第一个小括号 - 定义函数
         第二个小括号 - 调用函数
         作用 - 节省全局域命名空间
		 
	总结看法:js中的函数可以称之为 多样 灵活。但是其实,函数的本质就是对象。

确切一点来说,其实是第一类对象(first-class object)。关于第一类对象,解释如下:
第一类对象又称第一类公民,在编程语言中指的是一个具有以下特性的实体:
1.能够作为参数被传递
2.能够从一个函数结果中返回
3.能够被修改和赋值给变量
虽然看起来高大上,但是我们只要先记住,在js里函数也是对象,可以拥
有自己的属性和方法,而它和一般js对象的区别是:可以被调用,也就是
可执行。当然,函数还有一个明显的特点就是,提供作用域:在函数作用
域内的变量都是局部变量,对外部不可见。由于js中其他代码块,比如
for和while循环等并不提供作用域,所以有很多地方会利用函数来控制
作用域。

十一:趁热打铁 那你能说一下对 构造函数 的认识吗?

  1.概述:先解释一下什么是构造函数
          简单地说构造函数就是你用new关键字创建对象时调用的函数。
		作用:
		     创建多个共享特定属性和行为的对象,主要是用于生成对象的饼干模具
        缺点:
		     当实例化多个对象时,会重复的创建对象,造成内存空间的浪费,
		     增大CPU的开销,并没有消除代码的冗余,(如后面代码所示,原型正好解

决了此类问题)
存在问题:
同一个构造函数创建出来不同的实例化对象,公用的方法不等同,也就是
说,当你new一个构造器对象,上面的构造函数就执行一遍,每次都会新建一个function,会新开辟一个内存空间,每次都是指向一个新的堆的对象 ,这样占用内存消耗非常的大,怎么解决这个问题
如何解决:
将构造函数里面自定义的方法拿出来,独立放在构造函数外

  2.普通函数和构造函数有什么区别:
        有new与无new的差别
        写法上,构造函数首字母大写(目的只是用于区分普通函数与构造函数,
		提醒你在创建实例化对象前加new操作符)
        当函数没有被new调用时,构造函数中的this就能与全局this对象(即window)

3.如果想普通函数也具有构造函数的功能,怎么做?
function Animal(name,age){
// 加一this条件判断,用instanceof来检查自己是否被new调用
if(this instanceof Animal){
this.name = name;
this.age = age;
this.fun = function(){
return this.name+" "+this.age+“岁了”; }
}else{
// 以new递归调用自己来为对象创建正确的实例,这样做的目的是在
不同的情况下表现出一致的行为,常常是为了保护那些忘记了使用
new的情况
return new Animal(name,age);
}
}
// 无new的情况
var animal1 = new Animal(“cat”,2);
var animal2 = Animal(“dog”,3);

  4. 为何内置构造函数无new也能工作??
      例如:
        var arr = Array; // 当没有参数时,构造函数后面的圆括号可以省略
		var obj = Object({
			name:"随笔川迹",
			sex:"boy",
			fun:function(){
			   return this.name+" "+this.sex+" "+Object.prototype.toString.call(this);
		}});
		console.log(obj.fun());
      原因:
	     因为那些内置系统构造函数(Array,Object,RegExp,Date,Error,String等)
		 都被设计为作用域安全的构造函数,也就是说在整个全局范围内都是可见的,
		 一个作用域安全的构造函数无new也可以工作,并返回同样类型的对象

十二:小伙子你太棒啦!以上问题理解的这么透彻 我决定给你涨5000工资
如果你再能很好的解释一下 回调函数 的深意?

  概述:何为回调函数,官方解释:当程序跑起来时,一般情况下,应用程序会时常通

过API调用库里所预先备好的函数。但是有些库函数却要求应用先传给它一个
函数,好在合适的时候调用,以完成目标任务。这个被传入的、后又被调用的
函数就称为回调函数(callback function)。
也可以这样说:
通常将一个函数B传入另一个函数A,并且在 需要的时候再调用函数A。
最简单地说:
说白了,回调就是回溯,先定义好将要使用的函数体,然后再
调用这个函数,我们通常把callback作为一个参数传入先定义的那个函数。
总结:
我认为回调函数就是 js 中无法替代的一种灵魂,包括我所了解的nodeJS 里
面大量的回调函数应用数不尽数,总归一句话,js是单线程 事件驱动的,造就了他无阻塞的特点,那么回调就是完成无阻塞调用的最强助手。

十三: OK,你已经超神了,我们目前有个 开发组长的位置,不知你愿不愿意,如果可以的话请跟我再解释一下我们js中 原型的 理解?

   概述:1.原型说起来比较抽象,我先举个简单的小例子:
		“原型”这个东西,就是万恶的产品经理经常搞的那个嘛~假设产品经理是用

的Sketch(一款制作原型图的软件)做的产品原型,那么UI设计会从产品
经理那儿拷贝一份产品经理做好的原型,然后在上面修改(当然不一定这么
干,不过可以这么干就是了),最后的苦逼的小前端就会基于设计给出的设
计稿生成页面。我们也可以这么说,设计做的事情是把原型改成设计稿, 而
小前端做的则是把设计稿作为参考生成html/css/js。而JavaScript中的原型,
也差不是这个理儿,也就是用来复制一个副本 & 在副本的基础上修改。

		 2. 技术人员的眼中的原型:
		      原型,顾名思义,本身就拥有一定的功能,只需要改改就能用。
			  JavaScript中每个可构造的函数(非箭头函数)都有个prototype字段,
			  也就是所谓的原型。按照基于原型的OO语言的惯例,新建对象相当于

对原有对象的扩展或者复制。对应到JS,就是prototype,而JavaScript有
一个new关键字,语义是将构造函数F的prototype设定为新对象的
__proto, 并执行F。我们可以换一种等价的说法,new操作符创建了一
个新对象,并将F.prototype以某种形式复制(实际上是给了个引用,但是可以理解为写时复制),而F本身则是对新对象的一些修饰。这样的,
原型就真的成为了"原型"。既然new是对原型的拷贝,那么很自然的就
有:obj instanceof F的判别方式是obj是否具有F.prototype的所有属性;
Function.prototype 是function(){}。这样是很符合直觉的。

		 3. 顺带也说一下原型链: 
		      我们都知道每个对象都有一个prototype属性,指向它的原型对象.这个

原型对象里面同时还有自己的原型,原型一环扣一环,直到某个对象的原
型为null,这一级一级的链结构就是原型链。

			  其实原型和原型链不用那么复杂的去理解,很简单!!!
			       a = new A();
				   a的原型(模板)就是A这个构造函数,a是通过A刻出来的,a

的爸(或者妈)就是A,A的爸(或者妈)又是谁呢?A是一个构
造函数,那么函数是一个对象,是一个具体的对象,那它的爸爸
妈妈就是一个抽象的对象 也就是ObjectObject的爸爸妈妈是谁
呢,没了,他自己已经到头了,所以它的原型就是null。

			   再举个例子:
			       var A = function(){};
                   var a = new A();
			分析:
				   a._proto_ 就是 实例化之前的A ,模板就是function,也就是

A.prototype,A.prototype就是指的是function这个对象,而
constructor就是指向自己本身,那么 function的模板呢,当然就是
Object啦,所以A.prototype._proto_就等价于 function.proto
具体的function的模板就是Object而最高的对象的模板呢,没了,
就是null,我想你们应该都明白了吧。看到好多人用图,其实让大
家思考的时候大家就按自己的想法走了

			 4. 原型链继承也简单说一下:
					 js属性查找:由于js原型链的存在,当查找一个对象属性时候,不

只是在对象上查找,还会沿着该js对象的原型链往上查找,直到找到一
个匹配的属性或者查找到原型链末尾。当然如果js对象上与其原型
的对象上都有同名的属性,我们遵循该属性的作用域就近原则 (术语
叫做"属性遮蔽").而这种对象能共享原型中的属性和方法的关系就叫
做继承。当然ES6 中引出了 伟大的class 我们以后也可以清晰明了
的利用class 和关键字extends来建立继承关系,更好的实现面向对
象的多态的思想。

			 5. 在实际开发当中 我们 什么场景下用到了原型的相关知识
			    在实际开发中,会怎么实现面向对象的原型继承呢。正常在我们拿到

需求的时候,如果需求逻辑复杂,且在多个页面中有相似逻辑的时候,
我们就会想到使用面向对象了,因为面向对象解决的就是逻辑的封装
和复用。假设页面A,页面B,页面C中存在相同逻辑,那么我们可
以封装父构造函数对象,首先将页面A,页面B,页面C中相同逻辑
抽离,相同逻辑可以是同一个ajax请求返回数据,或者是数据格式化
等等的相同操作。将这些方法在Parent.prototype中定义,至于A,
B,C页面自己特有的方法,则在如A.prototype中定义。这样很好地
了解决了我们的问题,逻辑清晰,代码复用性强。如果在Parent方法
参数中加入了回调callback,并且在callback中想调用子函数方法或者
属性

十四: 你的回答很完美,其实有个秘密想告诉你,我就是咱们公司
的产品经理, 我们产品是很可爱的,不要有偏见。放下偏
见 你能再回答一下闭包的相关知识吗?
此时心里有一万头“草泥马”奔过… 这题一定要好好答
概述:谈闭包首先 铺垫一下 我们js中的作用域和执行上下文的内容
1. 理解作用域:
在javascript中,作用域是执行代码的上下文(方法调用中this所代表
的值),作用域有三种类型:
a. 全局作用域(Global scope)
b. 局部作用域(Local/Function scope,函数作用域)
c. eval作用域
在函数内部使用var定义的代码,其作用域都是局部的,且只对该函数
的其他表达式是可见的,包括嵌套子函数中的代码,局部变量只能在它
被调用的作用域范围内进行读和写的操作,在全局作用域内定义的变
量从任何地方都是可以访问的,因为它是作用域链中的最高层中的最
后一个,在整个范围内都是可见的

             注意:在Es6之前是没有块级作用域的,而Es6后是有的,也就是说

if,while,switch, for语句是有了块级作用域的,可以使用let关键字
声明变量,修正了var关键字的缺点,注意let使用规则
2. 理解js执行环境:
所谓执行坏境,它定义了变量或函数有访问的其他数据的能力,它决定了
各自的行为,它的侧重点在于函数的作用域,而并不是所要纠结的上下文,
一旦函数一声明定义,就会自动的分配产生了作用域,有着自己的执行坏
境,执行坏境可以分为创建与执行
两个阶段:
a.第一阶段阶段,js解析器首先会创建一个变量对象(活动对象),它由
定义在执行坏境中的变量,函数声明和参数组成,在这个阶段,系统
会自动的产生一个this对象,
作用域链会被初始化,随之,this的值也会被确定
b.第二阶段,也就是代码执行,代码会被解释执行,你会发现,每个执行
坏境都有一个与之关联的变量对象,执行坏境中所有定义的变量和
函数都保存在这个对象中,
注意:
我们是无法手动的访问这个对象的,只有js解析器才能够访问它,
其实也就是this,尽管很抽象,但是理解它还是蛮重要的

		  3. 分析作用域链:
		        当javascript查找与变量相关联的值时,会遵循一定的规则,也就是沿

着作用域链从当前函数作用域内逐级的向上查找,直到顶层全局作用域结束,若找到则返回该值,若无则返回undefined,这个链条是基于作用域的层次结构的,一旦当代码在坏境中执行时,会自动的创建一个变量对象的作用域链,其作用域链的用途也就是保证对执行坏境的全局变量和具有访问权限函数内的局部变量定制特殊的规则,由内到外有序的对变量或者函数进行访问。这条抽象的链式结构我们就称之为作用域链

		  4.闭包的形成(其原因就是作用域链造成的)
		        如果理解了上面的内容,那闭包也就好理解了,
				闭包在js高程中的解释是:有权访问另一个函数作用域中的变量的

函数。
简答的说就是 假设函数a是定义在函数b中的函数,那么函数a就是一个闭
包。正常情况下,在函数的外部访问不到函数内部的变量,但有了闭包就可以
间接的实现访问内部变量的需要。也就是说,闭包是连接函数内部和外部的桥
梁。这就是闭包的
第一个作用:访问函数内部的变量。
还有另外一个作用就是:让被引用的变量值始终保持在内存中。

		 5.闭包使用时需要注意的问题:
		        1、由于闭包会使得函数中被引用的变量一直保存在内存中,消耗内

存,所以谨慎使用闭包,否则会造成网页的性能问题。
2、闭包会改变父函数内部变量的值。如果父函数再次被执行的,
而在外部已经执行过闭包修改变量的值,那么,这次执行的结
果就会和上次的不一样。

	面试官:“这小子,难不住他,再问问...”
	       6. 哦哦 很不错,你回答的很到位,那你能详细解释一下你刚才提到的eval作用域吗?
		      我:“卧槽!死磕到底,放马过来吧!!!”
              回答:首先这是一个js原生的内置方法
      eval函数是强大的数码转换引擎,字符串经eval转换后得到一个javascript对象一个对象经过eval转换后数据类型不确定,在相加过程中自动与其他数据类型一致这里为什么会提到 eval作用域 呢?是因为当我们使用eval时,他可以被赋值给变量,例如   var evalg = eval;  evalg("alert(1)"); eval被赋值时,也会把当前eval所处的变量作用域也赋值过去;这样就造成了 一个 作用域的混乱,所以这个我们平时也不用 太多坑...
					
	面试官:“OK啦!!,面试了三个月,终于找到一个合适的,恭喜您,正式成为我们公司产品部门的新成员!!”
	我:“我靠,玩勺子把去吧,哥去下家面试了!!!”			 

十五:你能解释一下js中的继承实现的原理和思路吗?
概述:
说道继承,js长得继承可谓是五花八门,不止一种,这是因为 JavaScript(ES6之
前)中的继承机制并不是明确规定的,而是通过模仿实现的。这意味着所有的继承细节并非完全由解释程序处理。可以根据需求决定适合的继承方式。

	  1.对象冒充方式(类式继承)
			  function ClassA(name) {
				this.name = name;
				this.sayName = function () {
					console.log(this.name);
				};
			}

			  function ClassB(name,age) {
				this.classA = ClassA;
				this.classA(name);
				delete this.classA;
				this.age = age;
				this.sayAge = function(){
					console.log(this.age);
				}
			}
构造函数使用this关键字给所有属性和方法赋值(即采用类声明的构造函数方式)。因为构造函数只是一个函数,所以可使ClassA构造函数成为ClassB的方法,然后调用它。ClassB就会收到ClassA的构造函数中定义的属性和方法。
	注意:所有新属性和新方法都必须在删除了新方法的代码行后定义,因为可能会覆

盖父类的相关属性和方法

	  2.对象冒充也可以实现多重继承
	     如果存在ClassA和ClassB,这时ClassC想继承这两个类,如下:
		    function ClassA(name){
				this.name = name;
				this.sayName = function (){
					console.log(this.name);
				}
			}

			function ClassB(age){
				this.age = age;
				this.sayAge = function(){
					console.log(this.age);
				}
			}

			function ClassC(name,age){
				this.method = ClassA;
				this.method(name);

				this.method = ClassB;
				this.method(age);
				delete this.method;
			}
        注意:这种实现方式的缺陷是:如果两个类ClassA和ClassB具有同名的属性或方法, ClassB具有高优先级,因为它从后面的类继承。由于这种继承方法的流行, ECMAScript(ES3)的第三版为Function对象加入了两个方法,即call()和apply()。
	    
	  3. 原型链继承就很好理解了,例如:
	        function ClassA() {}
				ClassA.prototype.name = 'Tom';
				ClassA.prototype.sayName = function () {
					console.log(this.name);
				};

				function ClassB() {}
				ClassB.prototype = new ClassA();
				ClassB.prototype.age = 25;
				ClassB.prototype.sayAge = function () {
					console.log(this.age);
				};

				var tom = new ClassA();
				var jerry = new ClassB();
				tom.sayName();                         //'Tom'
				jerry.sayName();                       //'Tom'
				jerry.name = 'Jerry';
				tom.sayName();                         //'Tom'
				jerry.sayName();                       //'Jerry'
				jerry.sayAge();                        //25
				console.log(tom instanceof ClassA);    //true
				console.log(jerry instanceof ClassA);  //true
				console.log(jerry instanceof ClassB);  //true  
				
说道原型本质就是 复制粘贴,在这里:
这里把ClassB的prototype属性设置称ClassA的实例,避免逐个赋值prototpye属性。在调用ClassA时没有设置参数,因为在原型链中要确保构造函数是无参的。在原型链中,instanceof的结果也有了变化,对于ClassA和ClassB都返回了true。因为prototype属性的重指定,子类中的新属性都必须出现在prototype被赋值后。
  1. 组合式继承:由于原型链继承没办法传参,而冒充对象方式可以传参,二者结
    合就是组合继承。

  2. 深入谈谈 call()和 apply() 呗!
    call概述:
    call方法是与经典的对象冒充方法最相似的方法。它的第一个参数用
    作this的对象,其他参数都直接传递给函数自身
    a.经典的用法:
    function sayName(a) {
    console.log(a + this.name);
    };

     				var tom = {};
     				tom.name = "Tom";
    
     				sayName.call(tom, 'This is ');  //'This is Tom'	
    
     			分析:函数sayName在对象外定义,但也可以引用this。
     			
     	      b. call() 方法改写对象冒充
     		     function ClassA(name){
     				this.name = name;
     				this.sayName = function(){
     					console.log(this.name);
     				}
     			}
    
     			function ClassB(name,age){
     				//this.method = ClassA;
     				//this.method(name);
     				//delete this.method;
     				ClassA.call(this,name);
     				this.age = age;
     				this.sayAge = function (){
     					console.log(this.age);
     				}
     			}
    
     			var tom = new ClassB('Tom',25);
     			tom.sayName();                       //'Tom'
     			tom.sayAge();                        //25
     			console.log(tom instanceof ClassA);  //false
     			console.log(tom instanceof ClassB);  //true
                 分析:这是一个典型的 call 取代 对象冒充的方式
     			
             
         	apply概述:
                  apply方法有两个参数,用作this的对象和要传递给函数的参数数组
                  a. 例如:
     					 function sayName(prefex,mark) {
     							console.log(prefex+ this.name+ mark);
     						};
    
     						var tom = {};
     						tom.name = 'Tom';
    
     						sayName.apply(tom, ['This is ','!']);  //'This is Tom!'	
    
                  							
    
                   b. 同样可以使用apply改写对象冒充
     					 function ClassA(name){
     						this.name = name;
     						this.sayName = function(){
     							console.log(this.name);
     						}
     					}
    
     					function ClassB(name,age){
     						ClassA.apply(this,arguments);
     						this.age = age;
     						this.sayAge = function (){
     							console.log(this.age);
     						}
     					}
    
     					var tom = new ClassB('Tom',25);
     					tom.sayName();                       //'Tom'
     					tom.sayAge();                        //25  
     					console.log(tom instanceof ClassA);  //false
     					console.log(tom instanceof ClassB);  //true
     注意:只有父类中参数顺序和子类中的参数完全一致时才可以传递参数数组
    

十六:JavaScript 中 this 是如何工作的 ?
辅助用例:
var x = 0;
var foo = {
x:1,
bar:{
x:2,
baz: function () {
console.log(this.x)
}
}
}

			var a = foo.bar.baz
			foo.bar.baz() // 2
			a() //0
   总结概述:
		1.this 永远指向函数运行时所在的对象,而不是函数创建时所在的对象
        2.匿名函数和不处于任何对象中的函数,This指向window
        3.call, apply指的This是谁就是谁。
        4.普通函数调用,函数被谁调用,This就指向谁
		5.当然箭头函数得先看他外层有没有函数 有就是当前this 没有就是window

十七:如何检测对象类型?或者怎么检测一个数据是数组类型?
检测一个对象的类型,强烈推荐使用 Object.prototype.toString
方法; 因为这是唯一一个可依赖的方式。
我们使用Object.prototype.toString
方法:
Object.prototype.toString.call([]) // “[object Array]”
Object.prototype.toString.call({}) // “[object Object]”
Object.prototype.toString.call(2) // “[object Number]”
注意:
为什么不能用typeOf ?
typeof只有在基本类型的检测上面才好使,在引用类型(Function除外)
里面他返回的都是object,另 typeof null === “object”

十八:你了解 事件流? 事件捕获?事件冒泡?

  事件流:从页面中接收事件的顺序。也就是说当一个事件产生时,
          这个事件的传播过程,就是事件流。

  IE中的事件流叫事件冒泡;
                事件冒泡:事件开始时由最具体的元素接收,然后逐级向上
				传播到较为不具体的节点(文档)。对于html来说,
				就是当一个元素产生了一个事件,它会把这个事件传递给它的父元素,
				父元素接收到了之后,还要继续传递给它的上一级元素,
				就这样一直传播到document对象
				(亲测现在的浏览器到window对象,只有IE8及下不这样

  事件捕获是不太具体的元素应该更早接受到事件,而最具体的节点应该最后接收到事件。他们的用意是在事件到达目标之前就捕获它;也就是跟冒泡的过程正好相反,以html的click事件为例,document对象(DOM级规范要求从document开始传播,但是现在的浏览器是从window对象开始的)最先接收到click事件的然后事件沿着DOM树依次向下传播,一直传播到事件的实际目标;

十九:如何清除一个定时器?

	window.clearInterval();
	window.clearTimeout();

二十:如何添加一个dom对象到body中?innerHTML和innerText区别?
body.appendChild(dom元素);
innerHTML:从对象的起始位置到终止位置的全部内容,包括Html标签。
innerText:从起始位置到终止位置的内容, 但它去除Html标签
分别简述五个window对象、属性

	成员对象 
	window.event  window.document  window.history 
	window.screen window.navigator window.external
	Window对象的属性如下:
	window //窗户自身
	window.self //引用本窗户window=window.self 
	window.name //为窗户命名 
	window.defaultStatus //设定窗户状态栏信息 
	window.location //URL地址,配备布置这个属性可以打开新的页面

二十一:你了解js的垃圾回收机制吗?闭包会不会导致内存泄漏?
概述:
JavaScript 具有自动垃圾收集机制,就是找出那些不再继续使用的变量,
然后释放其占用的内存。为此,垃圾收集器会按照固定的时间间隔
(或代码执行中预定的收集时间)。常用的的方法有两种,即标记清楚和引用数。

	   1.标记清除
	       JavaScript 中最常用的垃圾收集方式是标记清除(mark-and-sweep)。
		   垃圾收集器在运行的时候会给存储在内存中的所有变量都加上标记
		   (可以使用任何标记方式)。然后,它会去掉环境中的变量以及被环
		   境中的变量引用的变量的标记。而在此之后再被加上标记的变量将被
		   视为准备删除的变量,原因是环境中的变量已经无法访问到这些变量了。
		   最后,垃圾收集器完成内存清除工作,销毁那些带标记的值并回收它们
		   所占用的内存空间。
		   
	  

 2.引用计数

引用计数(reference counting)的含义是跟踪记录每个值被引用的次数。
当声明了一个变量并将一个引用类型值赋给该变量时,则这个值的引用次数就是1。如果同一个值又被赋给另一个变量,则该值的引用次数加1。相反,如果包含对这个值引用的变量又取得了另外一个值,则这个值的引用次数减1。当这个值的引用次数变成0 时,则说明没有办法再访问这个值了,因而就可以将其占用的内存空间回收回来。这样,当垃圾收集器下次再运行时,它就会释放那些引用次数为零的值所占用的内存。

  3. 闭包到底会不会 造成 内存泄漏呢?
	      由于IE9 之前的版本对JScript 对象和COM 对象使用不同的垃圾收集。
		  因此闭包在IE 的这些版本中会导致一些特殊的问题。具体来说,
		  如果闭包的作用域链中保存着一个HTML 元素,那么就意味着该元素将无

法被销毁
请看例子:
function assignHandler(){
var element = document.getElementById(“someElement”);
element.onclick = function(){
alert(element.id);
};
}
以上代码创建了一个作为element 元素事件处理程序的闭包,而这个闭包则又创建了一个循环引用。由于匿名函数保存了一个对assignHandler()的活动对象的引用,因此就会导致无法减少element 的引用数。只要匿名函数存在, element 的引用数至少也是1,因此它所占用的内存就永远不会被回收
解决办法:
已经提到过,把element.id 的一个副本保存在一个变量中,从而消除闭
包中该变量的循环引用同时将element变量设为null。
function assignHandler(){
var element = document.getElementById(“someElement”);
var id = element.id;
element.onclick = function(){
alert(id);
};
element = null;
}
总结:闭包并不会引起内存泄漏,只是由于IE9之前的版本对JScript对象
和COM对象使用不同的垃圾收集,从而导致内存无法进行回收,
这是IE的问题,所以闭包和内存泄漏没半毛钱关系。

二十二:你了解浏览器的线程机制吗?
这个很难很难… but 我一说就不难了
1. 理解线程和进程
线程和进程区分不清,是很多新手都会犯的错误,没有关系。这很正常。
先看看下面这个形象的比喻:
- 进程是一个工厂,工厂有它的独立资源

           - 工厂之间相互独立

           - 线程是工厂中的工人,多个工人协作完成任务

           - 工厂内有一个或多个工人

           - 工人之间共享空间
	    
	    再完善完善概念:
		   - 工厂的资源 -> 系统分配的内存(独立的一块内存)

		   - 工厂之间的相互独立 -> 进程之间相互独立

		   - 多个工人协作完成任务 -> 多个线程在进程中协作完成任务

		   - 工厂内有一个或多个工人 -> 一个进程由一个或多个线程组成

	       - 工人之间共享空间 -> 同一进程下的各个线程之间共享程序的内
		     存空间(包括代码段、数据集、堆等)
			 
	    然后再巩固下:
		    如果是windows电脑中,可以打开任务管理器,可以看到有一个后台进程列表。对,那里就是查看进程的地方,而且可以看到每个进程的内存资源信息以及cpu占有率。所以,应该更容易理解了:进程是cpu资源分配的最小单位(系统会给它分配内存)

最后,再用较为官方的术语描述一遍:
进程是cpu资源分配的最小单位(是能拥有资源和独立运行的最小单位)
线程是cpu调度的最小单位(线程是建立在进程的基础上的一次程序运行单位,
一个进程中可以有多个线程)

	2. 掌握浏览器是多进程的
       
	   理解了进程与线程了区别后,接下来对浏览器进行一定程度上的认识:(先看下

简化理解)浏览器是多进程的浏览器之所以能够运行,是因为系统给它的进程分配了资源(cpu、内存)
简单点理解:每打开一个Tab页,就相当于创建了一个独立的浏览器进程
注意:打开了Chrome浏览器的多个标签页,然后可以在Chrome的任务管理器中看到有多个进程(分别是每一个Tab页面有一个独立的进程,以及一个主进程)。
感兴趣的可以自行尝试下,如果再多打开一个Tab页,进程正常会+1以上在这里浏览器应该也有自己的优化机制,有时候打开多个tab页后,可以在Chrome任务管理器中看到,有些进程被合并了(所以每一个Tab标签对应一个进程并不一定是绝对的)

3.浏览器都包含哪些进程?
	    知道了浏览器是多进程后,再来看看它到底包含哪些进程:(为了简化理解,仅列举主要进程)
		a.Browser进程:浏览器的主进程(负责协调、主控),只有一个。作用有
          负责浏览器界面显示,与用户交互。如前进,后退等
          将Renderer进程得到的内存中的Bitmap,绘制到用户界面上
          网络资源的管理,下载等

        b.第三方插件进程:每种类型的插件对应一个进程,仅当使用该插件时才创建

        c.GPU进程:最多一个,用于3D绘制等

        d.浏览器渲染进程(浏览器内核)(Renderer进程,内部是多线程的):默认每

个Tab页面一个进程,互不影响。主要作用为页面渲染,脚
本执行,事件处理等

	4.浏览器多进程的优势
	  a. 避免单个page crash影响整个浏览器

      b. 避免第三方插件crash影响整个浏览器

      c. 多进程充分利用多核优势

      d. 方便使用沙盒模型隔离插件等进程,提高浏览器稳定性
	  
	  简单一点说:
	      如果浏览器是单进程,那么某个Tab页崩溃了,就影响了整个浏览器,
		  体验有多差;同理如果是单进程,插件崩溃了也会影响整个浏览器;
		  而且多进程还有其它的诸多优势。。。
		  
		  当然,内存等资源消耗也会更大,有利就有弊。
		  
    5.那么重点中的重点来了:浏览器内核(渲染进程)
	      上面扯了那么多,其实就是为了引出这个主题,对于普通的前端操作来说,
		  最终要的是什么呢?答案是渲染进程
		  
		可以这样理解,页面的渲染,JS的执行,事件的循环,都在这个进程内进行。
		接下来重点分析这个进程:
		     请牢记,浏览器的渲染进程是多线程的
		   a.GUI渲染线程
			  负责渲染浏览器界面,解析HTML,CSS,构建DOM树和RenderObject

树,布局和绘制等。当界面需要重绘(Repaint)或由于某种操作引发回
流(reflow)时,该线程就会执行
注意:GUI渲染线程与JS引擎线程是互斥的,当JS引擎执行时GUI线程
会被挂起(相当于被冻结了),GUI更新会被保存在一个队列中等
到JS引擎空闲时立即被执行。

           b.JS引擎线程
             也称为JS内核,负责处理Javascript脚本程序。(例如V8引擎)
             JS引擎线程负责解析Javascript脚本,运行代码。
             JS引擎一直等待着任务队列中任务的到来,然后加以处理,
			 浏览器无论什么时候都只有一个JS线程在运行JS程序
             注意: GUI渲染线程与JS引擎线程是互斥的,所以如果JS执行的时间过

长,这样就会造成页面的渲染不连贯,导致页面渲染加载阻塞。

           c.事件触发线程
              归属于浏览器而不是JS引擎,用来控制事件循环(可以理解,
			  JS引擎自己都忙不过来,需要浏览器另开线程协助)
              当JS引擎执行代码块如setTimeOut时(也可来自浏览器内核的其他线

程,如鼠标点击、AJAX异步请求等),会将对应任务添加到事件线程中当
对应的事件符合触发条件被触发时,该线程会把事件添加到待处理队列
的队尾,等待JS引擎的处理
注意: 由于JS的单线程关系,所以这些待处理队列中的事件都得排队
等待JS引擎处理(当JS引擎空闲时才会去执行)

           d.定时触发器线程
              传说中的setInterval与setTimeout所在线程
              浏览器定时计数器并不是由JavaScript引擎计数的,(因为JavaScript引擎是单线程的,如果处于阻塞线程状态就会影响记计时的准确)

因此通过单独线程来计时并触发定时(计时完毕后,添加到事件队列中,
等待JS引擎空闲后执行)
注意:W3C在HTML标准中规定,规定要求setTimeout中低于4ms的时
间间隔算为4ms。

           e.异步http请求线程
              在XMLHttpRequest在连接后是通过浏览器新开一个线程请求
              将检测到状态变更时,如果设置有回调函数,异步线程就产生状态变更

事件,将这个回调再放入事件队列中。再由JavaScript引擎执行。看到
这里,如果觉得累了,可以先休息下,这些概念需要被消化理解,后面
还有什么
特别难,那就只掌握这些就可以啦 以内后面的我也不会…

猜你喜欢

转载自blog.csdn.net/qq_42363090/article/details/83311277