数据类型是基础中的基础,你们每天遇到,咱们此次来讨论深一点,将咱们认为理所固然的事情背后的原理发掘;继承也是前端基础一个大考点,看看继承的原理与使用场景。javascript
本文讨论如下几个点:html
最新的 ECMAScript 标准定义了 7 种数据类型:前端
这个分类咱们应该是至关熟悉了,当时这是按照什么标准分类的。git
事实上上面的分类标准是按照不一样数据在计算机内存中的结构分类的。咱们都知道JavaScript中的变量运行的时候是存在内存中的,若是接触过java的人应该知道,内存中也分为栈内存和堆内存。github
基本类型Undefined、Null、Boolean、Number 和String。这些类型在内存中分别占有固定大小的空间,他们的值保存在栈内存,他们的值保存在栈内存,咱们经过按值来访问的。面试
var a = '1'; var b = '1'; a === b;
上述代码执行时候,能够理解为:编程
JavaScript中的原始值(基本数据类型)均可预知其最大最小内存大小,因此建立的时候直接分配对应的内存空间。segmentfault
复杂的数据类型,如object,array,function等,没法提早预知其要占用多少内存空间,因此这个数据类型被放入了堆内存中,同时在栈内存中保存其堆内存的地址,访问这些变量的时候,在栈内存中获取到其内存地址,而后访问到该对象,这种方式叫按引用访问。api
var a = 'hello world'; var b = 123; var c = null; var d = undefined; var e = {}; var f = function(){console.log(1);}; var g = [1,2,a];
其在内存中的简易模型以下:
上面这个图并非彻底准确的,这里只是简单形容一下不一样数据类型变量的存储关系,偏底层的知识真的须要单独开一篇来说了。
经过上面的图我想应该一目了然了,基本数据类型都是存在栈内存中的,复杂对象则是存在堆内存中,栈内存变量保存的是其内存地址。这也应该想到了咱们常常遇到的问题:对象之间赋值,赋值的是真正的内存地址;对象相互比较===,比较的是内存地址。
JavaScript 引用指向的是值。若是一个值有 10 个引用,这些引用指向的都是同一个值,它们相互之间没有引用 / 指向关系。
JavaScript 对值和引用的赋值 / 传递在语法上没有区别,彻底根据值的类型来决定:
JavaScript内置了一些对象,这些对象能够在全局任意地方调用,而且有各自的属性和方法。MDN上罗列了所有,这里只挑一部分对象说明:
ok经过上面的几个内置对象就会发现一些问题:一些基本数据类型(String,Number,Boolean)有对应的内置对象,可是其余的一些(Null, Undefined)就没有,复杂数据类型则都有,这是为何。
var a = 'hello world'; a[1]; // 'e' a.length; // 11 a.toString(); // hello world a.valueOf(); // hello world a.split(' '); // ['hello', 'world']
有没有想过,变量a命名是个基本类型,不是对象,为何会有这么多属性和方法。由于这些内置的属性和方法都在内置对象String上。
事实上当你调用这些基本数据类型上属性和方法时候,引擎会自动寻找其是否有对应的包装类,有的话生成一个包装类的实例供你使用(使用以后销毁),不然报错。
var a = 'hello world'; a.customAttribute // undefined String.prototype.customAttribute = 'custom'; var b = 'hello world'; b.customAttribute // custom
咱们如今想要访问属性customAttribute
,这个属性没有在内置对象上,因此获取到的值是undefined
;咱们向内置对象的原型链上添加该属性,以后全部的string
上均可以获取到该值。
JavaScript中的类型转换也是个大坑,很多面试都会问到。JavaScript 是一种动态类型语言,变量没有类型限制,能够随时赋予任意值。
显示转换
直接调用对应的包装类进行转换。具体可分红三种状况:
// 数值:转换后仍是原来的值 Number(324) // 324 // 字符串:若是能够被解析为数值,则转换为相应的数值 Number('324') // 324 // 字符串:若是不能够被解析为数值,返回 NaN Number('324abc') // NaN // 空字符串转为0 Number('') // 0 // 布尔值:true 转成 1,false 转成 0 Number(true) // 1 Number(false) // 0 // undefined:转成 NaN Number(undefined) // NaN // null:转成0 Number(null) // 0
使用Number包装类来进行类型转换,隐藏的逻辑:
valueOf
方法。若是返回原始类型的值,则直接对该值使用Number
函数,再也不进行后续步骤。valueOf
方法返回的仍是对象,则改成调用对象自身的toString
方法。若是toString
方法返回原始类型的值,则对该值使用Number
函数,再也不进行后续步骤。toString
方法返回的是对象,就报错。var obj = {x: 1}; Number(obj) // NaN // 等同于 if (typeof obj.valueOf() === 'object') { Number(obj.toString()); } else { Number(obj.valueOf()); } var obj1 = { valueOf: function () { return {}; }, toString: function () { return {}; } }; Number(obj1) // TypeError: Cannot convert object to primitive value Number({ valueOf: function () { return 2; } }) // 2 Number({ toString: function () { return 3; } }) // 3 Number({ valueOf: function () { return 2; }, toString: function () { return 3; } }) // 2
若是使用String则规则相对简单:
值为基本数据类型
true
转为字符串"true"
,false
转为字符串"false"
。"undefined"
。"null"
。值为对象
toString
方法。若是返回原始类型的值,则对该值使用String
函数,再也不进行如下步骤。toString
方法返回的是对象,再调用原对象的valueOf
方法。若是valueOf
方法返回原始类型的值,则对该值使用String
函数,再也不进行如下步骤。valueOf
方法返回的是对象,就报错。Boolean规则更简单:除了五个值(undefined,null,(+/-)0,NaN,‘’)的转换结果为false
,其余的值所有为true
。
隐式转换
隐式转换也分三种状况:
转布尔值
JavaScript 遇到预期为布尔值的地方(好比if
语句的条件部分),就会将非布尔值的参数自动转换为布值。系统内部会自动调用Boolean
函数。
因此跟上面同样,所以除了五个值(undefined,null,(+/-)0,NaN,‘’),其余都是自动转为true
。
转字符串
JavaScript 遇到预期为字符串的地方,就会将非字符串的值自动转为字符串。具体规则是,先将复合类型的值转为原始类型的值,再将原始类型的值转为字符串。
字符串的自动转换,主要发生在字符串的加法运算时。当一个值为字符串,另外一个值为非字符串,则后者转为字符串。
'5' + 1 // '51' '5' + true // "5true" '5' + false // "5false" '5' + {} // "5[object Object]" '5' + [] // "5" '5' + function (){} // "5function (){}" '5' + undefined // "5undefined" '5' + null // "5null"
转数值
JavaScript 遇到预期为数值的地方,就会将参数值自动转换为数值。系统内部会自动调用
Number
函数。
除了加法运算符(+
)有可能把运算子转为字符串,其余运算符都会把运算子自动转成数值。
'5' - '2' // 3 '5' * '2' // 10 true - 1 // 0 false - 1 // -1 '1' - 1 // 0 '5' * [] // 0 false / '5' // 0 'abc' - 1 // NaN null + 1 // 1 undefined + 1 // NaN
具体参考阮一峰老师:JavaScript类型转换
这三个复杂对象咱们太熟悉不过了,天天都在打交道。可是实际上咱们也并非彻底掌握。
数组方法不少,咱们能够分类来整理记忆。
有哪些方法返回的是新数组
遍历数组方法有几种,区别在于什么
常见的有:
filter方法传入函数的参数有几个,都是什么含义
不仅是filter方法,相似这种第一个参数为callback的方法如:some,every,forEach,map,find,findIndex的方法callback参数都同样:currentValue,Index,array。
github上参照了MDN整理了一份完整的文档,用于本身的查缺补漏。
建立对象的方法有几种
字面量方式:
var person={ name:"SF", age:25 say:function(){ alert(this.name+"今年"+this.age); } }; person.say();
利用Object对象建立实例
var my = new Object(); my.name = "SF"; //JavaScript的发明者 my.age = 25; my.say = function() { alert("我是"+this.name+"今年"+my.age); } my.say(); var obj = Object.create(null); obj.name = 'SF';
构造函数
function Person(name,age) { this.name = name; this.age = age; this.say = function() { alert("我叫" + this.name + ",今年" + this.age + "岁); } } var my = new Person("SF",25); //实例化、建立对象 my.say(); //调用say()方法
原型模式
function Person() { } Person.prototype.name = 'aus'; Person.prototype.job = 'fe' Person.prototype.sayName = function() { console.log(this.name) } var person1 = new Person();
组合构造函数和原型
function Person( name, age, job ) { this.name = name; this.age = age; this.job = job; this.friends = ["Shelby","Court"]; } Person.prototype = { constructor: Person, sayName: function(){ alert(this.name); } } var person1 = new Person("Nicholas", 29, "software Engineer"); var person2 = new Person("Greg", 27, "Doctor"); person1.friends.push("Van"); alert(person1.friends); //"Shelby,Court,Van" alert(person2.friends); //"Shelby,Court" alert(person1.friends === person2.friends); //false alert(person1.sayName === person2.sayName); //true
对象的扩展密封和冻结有什么区别
扩展特性
Object.isExtensible
方法Object.preventExtensions
方法密封特性
Object.isSealed
方法Object.seal
方法冻结特性
Object.isFrozen
方法Object.freeze
方法
浅冻结
与 深冻结
简单说就是对象有可扩展性(能够随意添加属性),限制对象的可扩展性(Object.preventExtensions)以后,对象不可添加新属性(可是现有属性能够修改和删除)。
密封对象(seal)指的是对象的属性不可增长或者删除,而且属性配置不可修改(属性值可修改)。
冻结对象(freeze)则更加严格,不可增长或者删除属性,而且属性彻底不可修改。
这里不作过多介绍,详细能够看这里。
怎样快速实现浅拷贝以及深拷贝
Object.assign是常见的浅拷贝方法,怎样本身实现。
// 利用原生api function shallowClone(obj) { return Object.create( Object.getPrototypeOf(obj), Object.getOwnPropertyDescriptors(obj) ); } // 属性浅拷贝 function shallowCopy(copyObj) { var obj = {}; for ( var i in copyObj) { obj[i] = copyObj[i]; } return obj; }
深拷贝以前整理过:github。
对象的方法参照MDN整理了一份,github。
这节算是给继承铺垫基础知识了,js里最出名的原型和原型链,面试必考,平常开发也特别常见。
prototype中文译为'原型',大部分Object和Function都有prototype。我的以为原型是一个特殊的普通对象,对象里面的属性和方法都用于指定的用途:共享。咱们能够按照本身的意愿去修改原型,而且从新被共享。
当建立函数的时候,每个函数都会自动有一个prototype属性,这个属性的值是空对象(空对象不是空)。
一旦你把这个函数当成构造函数调用(经过new调用)JS会建立构造函数的实例,实例是不具备原型的。
function A (){}; A.prototype // {} var a = new A(); a.prototype // undefined
中文翻译过来叫'原型属性',这是一个隐式属性,不可被枚举,可是他的用途相当重要。每一个对象建立的时候,都会有一个隐式的属性__proto__,该属性的值是其对应的原型(其实就是说明 该对象的来源)。
function A (){}; A.__proto__ === Function.prototype; // true var b = {}; b.__proto__ === Object.prototype; // true var c = []; c.__proto__ === Array.prototype; // true
能够肯定的是,__proto__指向的是其构造函数的原型。
构造函数实例都拥有指向其构造函数的constructor属性。constructor属性的值是一个函数对象 为了将实例的构造器的原型对象暴露出来。
function A(){}; A.constructor === Function // true var a = new A(); a.construtor === A // true var obj = {}; obj.constructor === Object // true
能够肯定的是,constructor属性指向其构造函数。
上面三者的关系能够用下图表示:
这里就不得不提一句:使用new关键字实例化对象,内在过程到底发生了什么。
咱们能够理解为将new关键字实例化对象拆成两步:
function A(){}; function create (base) { var obj = {}; obj.__proto__ = base.prototype; base.call(obj); return obj; } var a = create(A); a instanceof A // true
上面三个角色到期了以后,就到了另外一个重点:原型链。
var a = Object.create(null); a.a = 1; var b = Object.create(a); b.b = 2; var c = Object.create(b); c.c = 3; c.a // 1 c.b // 2 c.c // 3 a.d = 4; c.d; c.a = 0; c.a; // 0
上面这个例子用到了Object.create函数建立了一个原型为空的对象a。能够看到c并无a,b属性,可是却能够读出该值来,这就是原型链。
当访问一个对象的属性(方法)的时候,若是对象自身没有该属性(方法),就会去该对象的__proto__上寻找,若是__proto__上也没有,就去__proto__.__proto__上寻找,以此类推,直到找到一个值返回;若没有则返回undefined。这种按照对象原型属性寻找造成一个相似链状的结构,叫作原型链。
画个图表示:
上图中的__proto__
红线能够理解为原型链。
这里要注意的是,对象的原型属性,保存的是对象的内存地址引用,须要读取原型属性的时候会找到该对象当时的状态,因此更改原型链上原型属性对象,会对该条原型链上的其余对象形成影响。
ok通过这么多铺垫终于来到了继承,继承是面向对象里面最重要的概念之一。咱们先来把相关概念介绍,再来看动手实现。
无论是实例,混入或者继承,他们的诞生都是为了解决同一个问题:代码复用。只不过实现方式不一样。
这个是咱们平常开发中最经常使用的一种。
var date = new Date(); var instanceLightBox = new LightBox();
实例化一个对象能够理解为调用类的构造函数,返回一个拥有类全部属性和方法的对象。
这样说可能也不许确,咱们以var a = new A();
为例,实例化一个对象有几个特色:
function A () { this.a = 1; }; A.prototype.getA = function(){ return this.a; } var a = new A(); a.a; // 1 a.getA(); // 1
事实上咱们在上面已经讲解了调用new关键字发生了什么,这里原理很少讲。为何要用实例化类:咱们能够吧构造函数当作一个工厂,工厂产出了定制化模板(构造函数)和标准模板(构造函数的原型)的产品;咱们能够经过屡次实例化一个类,产出多个同样的产品,从而实现了代码复用。
混入更像是一个加工厂,对已有的对象进行添加新属性的操做。
function A (){ this.a = 1; }; // 一个很是简单的mixin例子 function mixin(sourceObj, targetObj){ for (var key in sourceObj) { // 只会在不存在的状况下复制 if (!(key in targetObj)) { targetObj[key] = sourceObj[key]; } } } var a = new A(); var b = {b:2}; mixin(b, a); a.b; // 2
这个例子能够看到,targetObj混入了sourceObj的特有属性,若是属性是方法或者对象的话,targetObj保存的知识对象的引用,而不是本身独有的属性,这样sourceObject更改targetObj也会跟着更改。
继承里面有两个角色,父类和子类。继承理解为获得父类全部的属性,而且能够重写这些属性。一样是得到一个function所有的属性和方法,我认为实例和继承的最大区别在于实例是构造函数实例对象,继承是类继承类,数据类型有明显区别。
咱们先来看看ES6中的继承:
class Parent { constructor (props) { const {name, phone} = props; this.name = name; this.phone = phone; } getInfo(){ return this.name + ':' + this.phone; } } class Child extends Parent { constructor(props){ super(props); const {gender} = props; this.gender = gender; } getNewInfo(){ return this.name + ':' + this.gender + ':' + this.phone; } } var childIns = new Child({ name: 'aus', gender: 'male', phone: '1888888888' });
先不讨论继承是如何实现的,先来看看继承的结果。ES6中的继承,Child类拿到了Parent类的构造器里的非属性和原型上的全部属性,而且能够扩展本身的私有属性和原型属性。可是父类和子类仍然公用父类的原型。
继承有三个特色:
这里多态不详细介绍,咱们来了解概念与实例。
多态:同一操做做用于不一样的对象,能够有不一样的解释,产生不一样的执行结果。
举个例子,父类原型上有个方法a,子类原型上有个同名方法a,这样在子类实例上调用a方法必然是子类定义的a,可是我若是想用父类上的a怎么办。
class Parent { constructor (props) { const {name, phone} = props; this.name = name; this.phone = phone; } getInfo(){ return this.name + ':' + this.phone; } } class Child extends Parent { constructor(props){ super(props); const {gender} = props; this.gender = gender; } getInfo(from){ // 全完自定义 if('child' === from){ return this.getNewInfo(); } else { return super.getInfo(); } } getNewInfo(){ return this.name + ':' + this.gender + ':' + this.phone; } } var childIns = new Child({ name: 'aus', gender: 'male', phone: '1888888888' });
多态是一个很是普遍的话题,咱们如今所说的“相对”只是多态的一个方面:任何方法均可以引用继承层次中高层的方法(不管高层的方法名和当前方法名是否相同)。之因此说“相对”是由于咱们并不会定义想要访问的绝对继承层次(或者说类),而是使用相对引用“查找上一层”。
一道很是常见的面试题,有多种方法,分红两个思路,篇幅有限,不过多介绍,详细的文档在github上,或者自行google。