基本数据类型html
Stringgit
Number程序员
Booleangithub
null面试
特殊: typeof null === 'Object' //true
segmentfault
Undefinedapi
Symbol 符号(ES6新增)数组
引用数据类型浏览器
基本数据类型和引用数据类型的区别(存储位置不一样)bash
stack
)中的简单数据段,占据空间小、大小固定,属于被频繁使用数据,因此放入栈中存储;heap
)中的对象,占据空间大、大小不固定,若是存储在栈中,将会影响程序运行的性能;引用数据类型在栈中存储了指针,该指针指向堆中该实体的起始地址。当解释器寻找引用值时,会首先检索其在栈中的地址,取得地址后从堆中得到实体数据封装类对象:Object
、Array
、Boolean
、Number
和 String
其余对象:Function
、Arguments
、Math
、Date
、RegExp
、Error
typeof null === 'Object' //true
null
是惟一一个用typeof
检测会返回object
的基本类型值(注意‘基本’两字)
缘由:不一样的对象在底层都表示为二进制 在JavaScript中二进制前三位为0的话都会被判断为object类型 null的二进制表示全是0,天然前三位也是0 因此 typeof null === “object”
参考连接:深刻理解JS的类型、值、类型转换
判断对象用 instanceof,其内部机制是经过原型链来判断的
instanceof原理:判断实例对象的__proto__
属性,和构造函数的prototype
属性,是否为同一个引用(是否指向同一个地址)
注意1:虽说,实例是由构造函数 new 出来的,可是实例的__proto__
属性引用的是构造函数的prototype
。也就是说,实例的__proto__
属性与构造函数自己无关。
注意2:在原型链上,原型的上面可能还会有原型,以此类推往上走,继续找__proto__
属性。这条链上若是能找到, instanceof 的返回结果也是 true。
咱们也能够试着实现一下 instanceof
function instanceof(left, right) {
// 得到类型的原型
let prototype = right.prototype
// 得到对象的原型
left = left.__proto__
// 判断对象的类型是否等于类型的原型
while (true) {
if (left === null)
return false
if (prototype === left)
return true
left = left.__proto__
}
}
复制代码
问题:已知A继承了B,B继承了C。怎么判断 a 是由A直接生成的实例,仍是B直接生成的实例呢?仍是C直接生成的实例呢?
分析:这就要用到原型的constructor
属性了。
foo.__proto__.constructor === Foo
的结果为true,可是 foo.__proto__.constructor === Object
的结果为false。因此,用 consturctor判断就比用 instanceof判断,更为严谨。
Object.prototype.toString.call(xx)
转换成字符串 String()
toString() 能够被显式调用,或者在须要字符串化时自动调用
null 转换为 "null",undefined 转换为 "undefined",true 转换为 "true"。 数字的字符串化则遵循通用规则 极小和极大的 数字使用指数形式:
// 1.07 连续乘以七个 1000
var a = 1.07 * 1000 * 1000 * 1000 * 1000 * 1000 * 1000 * 1000;
// 七个1000一共21位数字
a.toString(); // "1.07e21"
复制代码
数组的默认 toString() 方法通过了从新定义,将全部单元字符串化之后再用 "," 链接起来
var a = [1,2,3];
a.toString(); // "1,2,3"
复制代码
转换成数字 Number()
其中 true 转换为 1,false 转换为 0。undefined 转换为 NaN,null 转换为 0。 处理失败 时返回 NaN(处理数字常量失败时会产生语法错误)
转换成布尔值 Boolean()
• undefined
• null
• false
• +0、-0 和 NaN
• ""
复制代码
除了上面之外的,都为true
字符串和数字之间的隐式转换
一个坑
[] + {}; // "[object Object]" {} + []; // 0
console.log([] + {}); //[object Object]
console.log({} + []); //[object Object]
第一行代码中,{} 出如今 + 运算符表达式中,所以它被看成一个值(空对象)来处理。 第二行代码中,{} 被看成一个独立的空代码块(不执行任何操做)。代码块结尾不须要分号,因此这里不存在语法上的问题。最后 + [] 将 [] 显式强制类型转换(参见第 4 章) 为 0。
第四行代码中,{} 其实应该当成一个代码块,而不是一个 Object,当你在console.log使用的时候,{} 被当成了一个 Object
隐式强制类型转换为布尔值
下面的状况会发生 布尔值隐式强制类型转换。
|| 与 &&
== 与 ===
== 容许在相等比较中进行强制类型转换,而 === 不容许
[] == ![] //true
复制代码
参考连接:为何[] ==![]
4种方式,每种方式的不一样在于this的初始化
通常而言,在Javascript中,this指向函数执行时的当前对象。
function myFunction(a, b) {
return a * b;
}
myFunction(10, 2); // myFunction(10, 2) 返回 20
window.myFunction(10, 2); //myFunction() 和 window.myFunction() 是同样的
复制代码
函数做为全局对象调用,会使 this 的值成为全局对象。 使用 window 对象做为一个变量容易形成程序崩溃。
var myObject = {
firstName:"John",
lastName: "Doe",
fullName: function () {
return this.firstName + " " + this.lastName;
}
}
myObject.fullName(); // 返回 "John Doe"
复制代码
fullName 方法是一个函数。函数属于对象。 myObject 是函数的全部者。this对象,拥有 JavaScript 代码。实例中 this 的值为 myObject 对象。
函数做为对象方法调用,会使得 this 的值成为对象自己。
// 构造函数:
function myFunction(arg1, arg2) {
this.firstName = arg1;
this.lastName = arg2;
}
// This creates a new object
var x = new myFunction("John","Doe");
x.firstName; // 返回 "John"
复制代码
构造函数中 this 关键字没有任何的值。 this 的值在函数调用实例化对象(new object)时建立。
call() 和 apply() 是预约义的函数方法。 两个方法可用于调用函数,两个方法的第一个参数必须是对象自己。
function myFunction(a, b) {
return a * b;
}
myObject = myFunction.call(myObject, 10, 2); // 返回 20
复制代码
function myFunction(a, b) {
return a * b;
}
myArray = [10, 2];
myObject = myFunction.apply(myObject, myArray); // 返回 20
复制代码
经过 call() 或 apply() 方法你能够设置 this 的值, 且做为已存在对象的新方法调用。
在 JavaScript 严格模式(strict mode)下, 在调用函数时第一个参数会成为 this 的值, 即便该参数不是一个对象。
在 JavaScript 非严格模式(non-strict mode)下, 若是第一个参数的值是 null 或 undefined, 它将使用全局对象替代。
参考连接:详解call、apply、bind
做用域就是变量与函数的可访问范围,即做用域控制着变量与函数的可见性和生命周期
内层函数可访问外层函数局部变量
外层函数不能访问内层函数局部变量
通俗地讲,当声明一个函数时,局部做用域一级一级向上包起来,就是做用域链。
1.当执行函数时,老是先从函数内部找寻局部变量
2.若是内部找不到(函数的局部做用域没有),则会向建立函数的做用域(声明函数的做用域)寻找,依次向上
闭包就是可以读取其余函数内部变量的函数。
函数 A
内部有一个函数 B
,函数 B
能够访问到函数 A
中的变量,那么函数 B
就是闭包。
经典面试题,循环中使用闭包解决
var
定义函数的问题
for (var i = 1; i <= 5; i++) {
setTimeout(function timer() {
console.log(i)
}, i * 1000)
}
//咱们但愿输出的是1,2,3,4,5,但由于 setTimeout 是个异步函数,因此会先把循环所有执行完毕,这时候 i就是 6 了,因此会输出五个6
复制代码
解决方法:
闭包
for (var i = 1; i <= 5; i++) {
(function(j) {
setTimeout(function timer() {
console.log(j)
}, j * 1000)
})(i)
}
复制代码
使用 setTimeout
的第三个参数,这个参数会被当成 timer
函数的参数传入
for (var i = 1; i <= 5; i++) {
setTimeout(
function timer(j) {
console.log(j)
},
i * 1000,
i
)
}
复制代码
使用let
for (let i = 1; i <= 5; i++) {
setTimeout(function timer() {
console.log(i)
}, i * 1000)
}
复制代码
对象字面量
person={firstname:"Mark",lastname:"Yun",age:25,eyecolor:"black"};
复制代码
工厂方式(内置对象)
var wcDog =new Object();
wcDog.name="旺财";
wcDog.age=3;
wcDog.work=function(){
alert("我是"+wcDog.name+",汪汪汪......");
}
wcDog.work();
复制代码
方式1与方式2效果同样,第一种写法,person
会指向Object
经过构造函数
// 无参
function Person(){}
var person=new Person();//定义一个function,若是使用new"实例化",该function能够看做是一个Class
person.name="Mark";
person.age="25";
person.work=function(){
alert(person.name+" hello...");
}
person.work();
复制代码
// 带参(用this关键字定义构造的上下文属性)
function Pet(name,age,hobby){
this.name=name;//this做用域:当前对象
this.age=age;
this.hobby=hobby;
this.eat=function(){
alert("我叫"+this.name+",我喜欢"+this.hobby+",是个程序员");
}
}
var maidou =new Pet("麦兜",25,"coding");//实例化、建立对象
maidou.eat();//调用eat方法
复制代码
Object.create
var p = {name:'smyhvae'};
var obj3 = Object.create(p); //此方法建立的对象,是用原型链链接的,obj3是实例,p是obj3的原型(name是p原型里的属性),构造函数是Objecet
复制代码
function create(Con, ...args) {
let obj = {}
Object.setPrototypeOf(obj, Con.prototype)
let result = Con.apply(obj, args)
return result instanceof Object ? result : obj
}
复制代码
首先函数接受不定量的参数,第一个参数为构造函数,接下来的参数被构造函数使用
而后内部建立一个空对象 obj
由于 obj
对象须要访问到构造函数原型链上的属性,因此咱们经过 setPrototypeOf
将二者联系起来。这段代码等同于 obj.__proto__ = Con.prototype
将 obj
绑定到构造函数上,而且传入剩余的参数
判断构造函数返回值是否为对象,若是为对象就使用构造函数返回的值,不然使用 obj
,这样就实现了忽略构造函数返回的原始值
参考连接:new操做符
function Foo()
仍是let a = { b : 1 }
,其实都是经过new产生的new Object()
的方式建立对象须要经过做用域链一层层找到 Object
,可是你使用字面量的方式就没这个问题function Foo() {}
// function 就是个语法糖
// 内部等同于 new Function()
let a = { b: 1 }
// 这个字面量内部也是使用了 new Object()
复制代码
prototype
(原型)prototype
里找这个属性,这个prototype
又会有本身的prototype
,因而就这样一直找下去,也就是咱们平时所说的原型链的概念person1.constructor === Person
Person.prototype.constructor === Person
复制代码
__proto__
指向其构造函数的原型person1.__proto__ === Person.prototype
复制代码
function Parent1() {
this.name = 'parent1 的属性';
}
function Child1() {
Parent1.call(this); //【重要】此处用 call 或 apply 都行:改变 this 的指向,parent的实例 --> 改成指向child的实例
this.type = 'child1 的属性';
}
console.log(new Child1);
复制代码
这种方式,虽然改变了 this 的指向,可是,Child1 没法继承 Parent1 的原型。也就是说,若是我给 Parent1 的原型增长一个方法,这个方法是没法被 Child1 继承的。
/* 经过原型链实现继承 */
function Parent() {
this.name = 'Parent 的属性';
}
function Child() {
this.type = 'Child 的属性';
}
Child.prototype = new Parent(); //【重要】
console.log(new Child());
复制代码
咱们把Parent
的实例赋值给了Child
的prototye
,从而实现继承。此时,new Child.__proto__ === new Parent()
的结果为true
这种继承方式,Child 能够继承 Parent 的原型,但有个缺点:
若是修改 child1实例的name属性,child2实例中的name属性也会跟着改变。形成这种缺点的缘由是:child1和child2共用原型。即:chi1d1.__proto__ === child2__proto__
是严格相同。
用原型链实现对原型属性和方法的继承,用借用构造函数技术来实现实例属性的继承。
/* 组合方式实现继承:构造函数、原型链 */
function Parent3() {
this.name = 'Parent 的属性';
this.arr = [1, 2, 3];
}
function Child3() {
Parent3.call(this); //【重要1】执行 parent方法
this.type = 'Child 的属性';
}
Child3.prototype = new Parent3(); //【重要2】第二次执行parent方法
var child = new Child3();
复制代码
这种方式,能解决以前两种方式的问题:既能够继承父类原型的内容,也不会形成原型里属性的修改。
这种方式的缺点是:让父亲Parent的构造方法执行了两次。
extends
关键字主要用于类声明或者类表达式中,以建立一个类,该类是另外一个类的子类。其中constructor
表示构造函数,一个类中只能有一个构造函数,有多个会报出SyntaxError
错误,若是没有显式指定构造方法,则会添加默认的 constructor
方法。
DOM0
element.onclick = function () {
}
复制代码
一是在标签内写onclick事件 二是在JS写onclick=function(){}函数
DOM2
//高版本浏览器
element.addEventListener('click', function () {
}, false);
//IE8及如下版本
element.attachEvent('onclick', function () {
});
//兼容写法
/* * 参数: * element 要绑定事件的对象 * eventStr 事件的字符串(不要on) * callback 回调函数 */
function myBind(element , eventStr , callback){
if(element.addEventListener){
//大部分浏览器兼容的方式
element.addEventListener(eventStr , callback , false);
}else{
//IE8及如下
element.attachEvent("on"+eventStr , function(){
//在匿名函数 function 中调用回调函数callback
callback.call(element);
});
}
复制代码
上面的第三参数中,true表示事件在捕获阶段触发,false表示事件在冒泡阶段触发(默认)。若是不写,则默认为false。
DOM3
element.addEventListener('keyup', function () {
}, false);
复制代码
DOM3中,增长了不少事件类型,好比鼠标事件、键盘事件等。
为什么事件没有DOM1的写法呢?由于,DOM1标准制定的时候,没有涉及与事件相关的内容。
事件传播的三个阶段是:事件捕获、事件目标、事件冒泡
事件从祖先元素往子元素查找(DOM树结构),直到捕获到事件目标 target。
addEventListener能够捕获事件
element.addEventListener('click', function () {
}, true);
复制代码
参数为true,表明事件在捕获阶段执行。
捕获阶段,事件依次传递的顺序是:window --> document --> html--> body --> 父元素、子元素、目标元素。
window.addEventListener("click", function () {
alert("捕获 window");
}, true);
document.addEventListener("click", function () {
alert("捕获 document");
}, true);
document.documentElement.addEventListener("click", function () {
alert("捕获 html");
}, true); //获取html节点
document.body.addEventListener("click", function () {
alert("捕获 body");
}, true); //获取body节点
fatherBox.addEventListener("click", function () {
alert("捕获 father");
}, true);
childBox.addEventListener("click", function () {
alert("捕获 child");
}, true);
复制代码
当一个元素上的事件被触发的时候(好比说鼠标点击了一个按钮),一样的事件将会在那个元素的全部祖先元素中被触发。这一过程被称为事件冒泡;这个事件从原始元素开始一直冒泡到DOM树的最上层。
通俗来说,冒泡指的是:子元素的事件被触发时,父元素的一样的事件也会被触发。取消冒泡就是取消这种机制。
冒泡顺序:
通常的浏览器: (除IE6.0以外的浏览器)
IE6.0:
不能冒泡的事件:
blur、focus、load、unload、onmouseenter、onmouseleave
检查一个元素是否会冒泡:event.bubbles
阻止冒泡:
w3c的方法:(火狐、谷歌、IE11)
event.stopPropagation();
复制代码
IE10如下:
event.cancelBubble = true
复制代码
兼容代码:
childBox.onclick = function(event){
event = event || window.event;
if(event && event.stopPropagation){
event.stopPropagation();
}else{
event.cancelBubble = true;
}
}
复制代码
上方代码中,咱们对childBox进行了阻止冒泡,产生的效果是:事件不会继续传递到 father、grandfather、body了
阻止默认事件
event.preventDefault();
复制代码
function stopDefault( event ) {
//阻止默认浏览器动做(W3C)
if ( event && event.preventDefault )
event.preventDefault();
//IE中阻止函数器默认动做的方式
else
window.event.returnValue = false;
}
复制代码
好比,已知<a>
标签绑定了click事件,此时,若是给<a>
设置了这个方法,就阻止了连接的默认跳转
阻止冒泡
代码见上
设置事件优先级
event.stopImmediatePropagation();
复制代码
好比说,我用addEventListener给某按钮同时注册了事件A、事件B。此时,若是我单击按钮,就会依次执行事件A和事件B。如今要求:单击按钮时,只执行事件A,不执行事件B。该怎么作呢?这是时候,就能够用到stopImmediatePropagation
方法了。作法是:在事件A的响应函数中加入这句话。
event.currentTarget //当前所绑定的事件对象。在事件委托中,指的是【父元素】。
event.target //当前被点击的元素。在事件委托中,指的是【子元素】。
复制代码
var myEvent = new Event('clickTest');
element.addEventListener('clickTest', function () {
console.log('smyhvae');
});
//元素注册事件
element.dispatchEvent(myEvent); //注意,参数是写事件对象 myEvent,不是写 事件名 clickTest
复制代码
事件代理/事件委托是利用事件冒泡的特性,将本应该绑定在多个元素上的事件绑定在他们的祖先元素上,尤为在动态添加子元素的时候,能够很是方便的提升程序性能,减少内存空间。