JavaScript
什么是闭包
闭包的实质是因为函数嵌套而形成的作用域链
比如说:函数 A 内部有一个函数 B,函数 B 可以访问到函数 A 中的变量,那么函数 B 就是闭包
- 优点:可以避免变量被全局变量污染
- 缺点:函数中的变量都被保存在内存中,内存消耗很大,所以不能滥用闭包
- 解决方法:在退出函数之前,将不使用的局部变量全部删除
for(var i = 0; i < 10; i++) {
setTimeout(function() {
console.log(i);
}, 1000);
}
//输出结果都是10
for(var i = 0; i < 10; i++) {
(function(j) {
setTimeout(function() {
console.log(j);
}, 1000);
})(i);
}
//外部匿名函数立即执行,把 i 作为参数,赋值给 j ,因为是立即执行,所以每次循环输出不同值
原型和原型链
- 每个对象都会在其内部初始化一个属性,就是
prototype
(原型) - 每个对象有一个私有属性,记作
prototype
,属性又有自己的prototype
,形成了原型链
function Foo() {
this.value = 42;
}
Foo.prototype = {
method: function() {}
};
function Bar() {}
// 设置Bar的prototype属性为Foo的实例对象
Bar.prototype = new Foo();
Bar.prototype.foo = 'Hello World';
// 修正Bar.prototype.constructor为Bar本身
Bar.prototype.constructor = Bar;
var test = new Bar() // 创建Bar的一个新实例
// 原型链
test [Bar的实例]
Bar.prototype [Foo的实例]
{ foo: 'Hello World' }
Foo.prototype
{method: ...};
Object.prototype
{toString: ... /* etc. */};
上面的例子中,test
对象从Bar.prototype
和 Foo.prototype
继承下来;因此, 它能访问 Foo
的原型方法 method
。同时,它也能够访问那个定义在原型上的 Foo
实例属性 value
。 需要注意的是 new Bar()
不会创造出一个新的 Foo
实例,而是 重复使用它原型上的那个实例;因此,所有的 Bar
实例都会共享相同的 value
属性。
prototype 是什么
prototype
是函数对象上面预设的对象属性
作用域与命名空间
尽管 JavaScript
支持一对花括号创建的代码段,但是并不支持块级作用域; 而仅仅支持 函数作用域。
function test() { // 一个作用域
for(var i = 0; i < 10; i++) { // 不是一个作用域
// count
}
console.log(i); // 10
}
如果
return
对象的左括号和return
不在一行上就会出错
//下面输出 undefined
function add(a, b) {
return
a + b;
}
console.log(add(1, 2));
隐式的全局变量
- 不使用 var 声明变量将会导致隐式的全局变量产生
// 全局作用域
var foo = 42;
function test() {
// 局部作用域
foo = 21;
}
test();
foo; // 21
在函数
test
内不使用var
关键字声明foo
变量将会覆盖外部的同名变量。 起初这看起来并不是大问题,但是当有成千上万行代码时,不使用var
声明变量将会带来难以跟踪的BUG
局部变量
JavaScript
中局部变量只可能通过两种方式声明,一个是作为函数参数,另一个是通过 var
关键字声明。
// 全局变量
var foo = 1;
var bar = 2;
var i = 2;
function test(i) {
// 函数 test 内的局部作用域
i = 5;
var foo = 3;
bar = 4;
}
test(10);
foo
和i
是函数test
内的局部变量,而对bar
的赋值将会覆盖全局作用域内的同名变量。
var someVar = "Hat";
function myFun() {
var someVar = "Head";
return someVar;
}
函数
myFun
将会返回"Head
",因为局部变量优先级更高
变量声明提升(Hoisting)
bar();
var bar = function() {};
var someValue = 42;
test();
function test(data) {
if (false) {
goo = 1;
} else {
var goo = 2;
}
for(var i = 0; i < 100; i++) {
var e = data[i];
}
}
JavaScript
会提升变量声明。这意味着 var
表达式和 function
声明都将会被提升到当前作用域的顶部。
// var 表达式被移动到这里
var bar, someValue; // 缺省值是 'undefined'
// 函数声明也会提升
function test(data) {
var goo, i, e; // 没有块级作用域,这些变量被移动到函数顶部
if (false) {
goo = 1;
} else {
goo = 2;
}
for(i = 0; i < 100; i++) {
e = data[i];
}
}
bar(); // 出错:TypeError,因为 bar 依然是 'undefined'
someValue = 42; // 赋值语句不会被提升规则(hoisting)影响
bar = function() {};
test();
如果没有提升规则(hoisting)的知识,下面的代码看起来会抛出异常
ReferenceError
:
// 检查 SomeImportantThing 是否已经被初始化
if (!SomeImportantThing) {
var SomeImportantThing = {};
}
实际上,上面的代码正常运行,因为 var
表达式会被提升到全局作用域的顶部。
var SomeImportantThing;
// 其它一些代码,可能会初始化 SomeImportantThing,也可能不会
// 检查是否已经被初始化
if (!SomeImportantThing) {
SomeImportantThing = {};
}
命名空间
只有一个全局作用域导致的常见错误是命名冲突。在 JavaScript
中,这可以通过 匿名包装器 轻松解决。
(function() {
// 函数创建一个命名空间
window.foo = function() {
// 对外公开的函数,创建了闭包
};
})(); // 立即执行此匿名函数
匿名函数被认为是 表达式;因此为了可调用性,它们首先会被执行
( // 小括号内的函数首先被执行
function() {}
) // 并且返回函数对象
() // 调用上面的执行结果,也就是函数对象
有一些其他的调用函数表达式的方法,比如下面的两种方式语法不同,但是效果一模一样。
// 另外两种方式
function(){}();
(function(){}());
继承
既然要实现继承,那么首先我们得有一个父类,代码如下:
// 定义一个动物类
function Animal (name) {
// 属性
this.name = name || 'Animal';
// 实例方法
this.sleep = function(){
console.log(this.name + '正在睡觉!');
}
}
// 原型方法
Animal.prototype.eat = function(food) {
console.log(this.name + '正在吃:' + food);
};
- 原型链继承
核心: 将父类的实例作为子类的原型
function Cat(){
}
Cat.prototype = new Animal();
Cat.prototype.name = 'cat';
// Test Code
var cat = new Cat();
console.log(cat.name); //cat
console.log(cat.eat('fish')); //cat正在吃fish
console.log(cat.sleep()); //cat正在睡觉!
console.log(cat instanceof Animal); //true
console.log(cat instanceof Cat); //true
特点:
- 非常纯粹的继承关系,实例是子类的实例,也是父类的实例
- 父类新增原型方法/原型属性,子类都能访问到
- 简单,易于实现
缺点:
- 要想为子类新增属性和方法,必须要在new Animal()这样的语句之后执行,不能放到构造器中
- 无法实现多继承
- 来自原型对象的所有属性被所有实例共享
- 创建子类实例时,无法向父类构造函数传参
- 构造继承
核心:使用父类的构造函数来增强子类实例,等于是复制父类的实例属性给子类(没用到原型)
function Cat(name){
Animal.call(this);
this.name = name || 'Tom';
}
// Test Code
var cat = new Cat();
console.log(cat.name);
console.log(cat.sleep());
console.log(cat instanceof Animal); // false
console.log(cat instanceof Cat); // true
- 特点:
- 解决了1中,子类实例共享父类引用属性的问题
- 创建子类实例时,可以向父类传递参数
- 可以实现多继承(
call
多个父类对象)
- 缺点:
- 实例并不是父类的实例,只是子类的实例
- 只能继承父类的实例属性和方法,不能继承原型属性/方法
- 无法实现函数复用,每个子类都有父类实例函数的副本,影响性能
- 实例继承
核心:为父类实例添加新特性,作为子类实例返回
function Cat(name){
var instance = new Animal();
instance.name = name || 'Tom';
return instance;
}
// Test Code
var cat = new Cat();
console.log(cat.name);
console.log(cat.sleep());
console.log(cat instanceof Animal); // true
console.log(cat instanceof Cat); // false
- 特点:
- 不限制调用方式,不管是
new 子类()
还是子类(),返回的对象具有相同的效果
- 不限制调用方式,不管是
- 缺点:
- 实例是父类的实例,不是子类的实例
- 不支持多继承
- 拷贝继承
function Cat(name){
var animal = new Animal();
for(var p in animal){
Cat.prototype[p] = animal[p];
}
this.name = name || 'Tom';
}
// Test Code
var cat = new Cat();
console.log(cat.name);
console.log(cat.sleep());
console.log(cat instanceof Animal); // false
console.log(cat instanceof Cat); // true
- 特点
- 支持多继承
- 缺点
- 效率较低,内存占用高(因为要拷贝父类的属性)
- 无法获取父类不可枚举的方法(不可枚举方法,不能使用
for in
访问到)
- 组合继承
核心:通过调用父类构造,继承父类的属性并保留传参的优点,然后通过将父类实例作为子类原型,实现函数复用
function Cat(name){
Animal.call(this);
this.name = name || 'Tom';
}
Cat.prototype = new Animal();
Cat.prototype.constructor = Cat;
// Test Code
var cat = new Cat();
console.log(cat.name);
console.log(cat.sleep());
console.log(cat instanceof Animal); // true
console.log(cat instanceof Cat); // true
- 特点
- 弥补了方式2的缺陷,可以继承实例属性/方法,也可以继承原型属性/方法
- 既是子类的实例,也是父类的实例
- 不存在引用属性共享问题
- 可传参
- 函数可复用
- 缺点
- 调用了两次父类构造函数,生成了两份实例(子类实例将子类原型上的那份屏蔽了)
- 寄生组合继承
核心:通过寄生方式,砍掉父类的实例属性,这样,在调用两次父类的构造的时候,就不会初始化两次实例方法/属性,避免的组合继承的缺点
function Cat(name){
Animal.call(this);
this.name = name || 'Tom';
}
(function(){
// 创建一个没有实例方法的类
var Super = function(){};
Super.prototype = Animal.prototype;
//将实例作为子类的原型
Cat.prototype = new Super();
})();
// Test Code
var cat = new Cat();
console.log(cat.name);
console.log(cat.sleep());
console.log(cat instanceof Animal); // true
console.log(cat instanceof Cat); //true
Cat.prototype.constructor = Cat; // 需要修复下构造函数
ES6中class 的继承
class Person {
//调用类的构造方法
constructor(name, age) {
this.name = name
this.age = age
}
//定义一般的方法
showName() {
console.log("调用父类的方法")
console.log(this.name, this.age);
}
}
let p1 = new Person('kobe', 39)
console.log(p1) //Person {name: "kobe", age: 39}
//定义一个子类
class Student extends Person {
constructor(name, age, salary) {
super(name, age)//通过super调用父类的构造方法
this.salary = salary
}
showName() {//在子类自身定义方法
console.log("调用子类的方法")
console.log(this.name, this.age, this.salary);
}
}
let s1 = new Student('wade', 38, 1000000000)
console.log(s1) //Student {name: "wade", age: 38, salary: 1000000000}
s1.showName()
Class与构造函数的区别
Class
在语法上更贴合面向对象的写法Class
实现继承更加易读、易理解- 本质是语法糖,使用
prototype
原型的继承方式
对象使用和属性
- 访问对象属性:有两种方式来访问对象的属性,点操作符或者中括号操作符
var foo = {name: 'kitten'}
foo.name; // kitten
foo['name']; // kitten
var get = 'name';
foo[get]; // kitten
foo.1234; // SyntaxError
foo['1234']; // works
两种语法是等价的,但是中括号操作符在下面两种情况下依然有效
- 动态设置属性
- 属性名不是一个有效的变量名
删除属性
删除属性的唯一方法是使用delete
操作符;设置属性为 undefined
或者 null
并不能真正的删除属性, 而仅仅是移除了属性和值的关联
var obj = {
bar: 1,
foo: 2,
baz: 3
};
obj.bar = undefined;
obj.foo = null;
delete obj.baz;
for(var i in obj) {
if (obj.hasOwnProperty(i)) {
console.log(i, '' + obj[i]);
}
}
上面的输出结果有
bar undefined
和foo null
- 只有baz
被真正的删除了,所以从输出结果中消失。
函数
- 声明
function foo() {}
上面的方法会在执行前被 解析(hoisted),因此它存在于当前上下文的任意一个地方, 即使在函数定义体的上面被调用也是对的。
foo(); // 正常运行,因为foo在代码运行前已经被创建
function foo() {}
- 函数赋值表达式
var foo = function() {};
这个例子把一个匿名的函数赋值给变量 foo
foo; // 'undefined'
foo(); // 出错:TypeError
var foo = function() {};
由于 var
定义了一个声明语句,对变量 foo
的解析是在代码运行之前,因此 foo
变量在代码运行时已经被定义过了。
但是由于赋值语句只在运行时执行,因此在相应代码执行之前, foo
的值缺省为 undefined
。
- 命名函数的赋值表达式 另外一个特殊的情况是将命名函数赋值给一个变量。
var foo = function bar() {
bar(); // 正常运行
}
bar(); // 出错:ReferenceError
bar
函数声明外是不可见的,这是因为我们已经把函数赋值给了 foo
; 然而在 bar
内部依然可见。这是由于 JavaScript
的 命名处理 所致, 函数名在函数内总是可见的。
arguments 对象
JavaScript
中每个函数内都能访问一个特别变量 arguments
。这个变量维护着所有传递到这个函数中的参数列表。
注意: 由于 arguments
已经被定义为函数内的一个变量。 因此通过 var
关键字定义 arguments
或者将 arguments
声明为一个形式参数, 都将导致原生的 arguments
不会被创建。
arguments
变量不是一个数组(Array
)。 尽管在语法上它有数组相关的属性 length
,但它不从 Array.prototype
继承,实际上它是一个对象(Object
)。
因此,无法对 arguments
变量使用标准的数组方法,比如 push
, pop
或者 slice
。 虽然使用 for
循环遍历也是可以的,但是为了更好的使用数组方法,最好把它转化为一个真正的数组。
- 转化为数组
下面的代码将会创建一个新的数组,包含所有
arguments
对象中的元素
Array.prototype.slice.call(arguments)
- 传递参数
function foo() {
bar.apply(null, arguments);
}
function bar(a, b, c) {
// do something
}
事件绑定的方式
- 嵌入dom
<button onclick="func()">按钮</button>
- 直接绑定
btn.onclick = function(){}
- 事件监听
btn.addEventListener('click',function(){})
事件委托
事件委托利用了事件冒泡,只指定一个事件处理程序,就可以管理某一类型的所有事件。所有用到按钮的事件(多数鼠标事件和键盘事件)都适合采用事件委托技术, 使用事件委托可以节省内存。
<ul>
<li>苹果</li>
<li>香蕉</li>
<li>凤梨</li>
</ul>
// good 减少dom操作
document.querySelector('ul').onclick = (event) => {
let target = event.target
if (target.nodeName === 'LI') {
console.log(target.innerHTML)
}
}
// bad
document.querySelectorAll('li').forEach((e) => {
e.onclick = function() {
console.log(this.innerHTML)
}
})
DOM操作——怎样添加、移除、移动、复制、创建和查找节点
- 创建新节点
createDocumentFragment()
//创建一个DOM片段createElement()
//创建一个具体的元素createTextNode()
//创建一个文本节点
- 添加、移除、替换、插入
appendChild()
removeChild()
replaceChild()
insertBefore()
//在已有的子节点前插入一个新的子节点
- 查找
getElementsByTagName()
//通过标签名称getElementsByName()
//通过元素的Name属性的值(IE容错能力较强,会得到一个数组,其中包括id等于name值的)getElementById()
//通过元素Id,唯一性
// 添加新节点
var p1 = document.createElement('p')
p1.innerHTML = 'this is p1'
div1.appendChild(p1) // 添加新创建的元素
// 移动已有节点。注意,这里是“移动”,并不是拷贝
var p2 = document.getElementById('p2')
div1.appendChild(p2)
// 获取父元素
var div1 = document.getElementById('div1')
var parent = div1.parentElement
// 获取子元素
var div1 = document.getElementById('div1')
var child = div1.childNodes
// 删除节点
var div1 = document.getElementById('div1')
var child = div1.childNodes
div1.removeChild(child[0])
this对象
this
总是指向函数的执行上下文,根据调用方式不同,this
的指向不同- 以匿名函数或直接调用的函数;
this
指向全局上下文(浏览器window NodeJS global
) - 以方法调用则
this
指向调用方法的那个对象 - 以构造函数调用时,
this
指向新创建的对象 - 使用
call
和apply
调用时this为指定的那个对象 - 在事件响应函数中,指向触发这个事件的对象,特殊的是
IE中
的attachEvent
中的this
总是指向全局对象window
- 箭头函数中
this
的指向取决于箭头函数声明的位置,捕获其所在的上下文的this
值,作为自己的this
值
这段代码里的this是什么
1. fn()
this => window
2. obj.fn()
this => obj
3. fn.call(xx)
this => xx
4. fn.apply(xx)
this=> xx
5. fn.bind(xx)
this => xx
6. new Fn()
this =>新的对象
7. fn = ()=> {}
this =>外面的 this
箭头函数带大括号和不带大括号的区别(大括号:函数体)
当不需要函数体,只返回一个值的时候,箭头函数允许你省略
return
关键字和外面的大括号。这样就可以将一个简单的函数简化成一个单行语句
const date = ()=> new Date()
如果箭头函数的代码块部分多于一条语句,就要使用大括号将它们括起来,并且使用
return
语句返回。
var sum = (num1, num2) => { return num1 + num2; }
由于大括号被解释为代码块,所以如果箭头函数直接返回一个对象,必须在对象外面加上括号,否则会报错
// 报错
let getTempItem = id => { id: id, name: "Temp" };
// 不报错
let getTempItem = id => ({ id: id, name: "Temp" });
构造函数
JavaScript
中的构造函数和其它语言中的构造函数是不同的。 通过 new
关键字方式调用的函数都被认为是构造函数。
在构造函数内部 - 也就是被调用的函数内 - this
指向新创建的对象 Object
。 这个新创建的对象的 prototype
被指向到构造函数的 prototype
如果被调用的函数没有显式的 return
表达式,则隐式的会返回 this
对象 - 也就是新创建的对象
function Foo() {
this.bla = 1;
}
Foo.prototype.test = function() {
console.log(this.bla);
};
var test = new Foo();
上面代码把 Foo
作为构造函数调用,并设置新创建对象的 prototype
为 Foo.prototype
。
显式的
return
表达式将会影响返回结果,但仅限于返回的是一个对象
function Bar() {
return 2;
}
new Bar(); // 返回新创建的对象
function Test() {
this.value = 2;
return {
foo: 1
};
}
new Test(); // 返回的对象 {foo: 1}
new Bar()
返回的是新创建的对象,而不是数字的字面值 2。 因此 new Bar().constructor === Bar
,但是如果返回的是数字对象,结果就不同了,如下所示:
function Bar() {
return new Number(2);
}
new Bar().constructor === Number
这里得到的 new Test()
是函数返回的对象,而不是通过new关键字新创建的对象,因此:
(new Test()).value === undefined
(new Test()).foo === 1
如果 new
被遗漏了,则函数不会返回新创建的对象。
function Foo() {
this.bla = 1; // 获取设置全局参数
}
Foo(); // undefined
- 工厂模式
为了不使用
new
关键字,构造函数必须显式的返回一个值:
function Bar() {
var value = 1;
return {
method: function() {
return value;
}
}
}
Bar.prototype = {
foo: function() {}
};
new Bar();
Bar();
上面两种对 Bar
函数的调用返回的值完全相同,一个新创建的拥有 method
属性的对象被返回, 其实这里创建了一个闭包。
还需要注意, new Bar()
并不会改变返回对象的原型(也就是返回对象的原型不会指向 Bar.prototype
)。 因为构造函数的原型会被指向到刚刚创建的新对象,而这里的 Bar
没有把这个新对象返回(而是返回了一个包含 method
属性的自定义对象)。
new操作符
- 创建一个空对象,链接原型,绑定
this
指向 - 属性和方法被加入到 this 引用的对象中,即执行构造函数
- 返回新创建的对象
JS 有哪些数据类型
基本数据类型包括Undefined
、Null
、Boolean
、Number
、String
、Symbol
(ES6新增)六种。 引用数据类型只有Object
一种,主要包括对象、数组和函数。
null和undefined的区别
null
表示一个对象是“没有值”的值,也就是值为“空”undefined
表示一个变量声明了没有初始化(赋值)undefined
和null
在if
语句中,都会被自动转为false
undefined
不是一个有效的JSON
,而null
是undefined
的类型(typeof
)是undefined
null
的类型(typeof
)是object
Javascript
将未赋值的变量默认值设为undefined
Javascript
从来不会将变量设为null
。它是用来让程序员表明某个用var
声明的变量时没有值的null
转为数字类型值为0
,而undefined
转为数字类型为NaN(Not a Number)
同步和异步的区别
同步的概念应该是来自于OS中关于同步的概念:不同进程为协同完成某项工作而在先后次序上调整(通过阻塞,唤醒等方式).同步强调的是顺序性.谁先谁后.异步则不存在这种顺序性
同步:浏览器访问服务器请求,用户看得到页面刷新,重新发请求,等请求完,页面刷新,新内容出现,用户看到新内容,进行下一步操作
异步:浏览器访问服务器请求,用户正常操作,浏览器后端进行请求。等请求完,页面不刷新,新内容也会出现,用户看到新内容
防抖和节流
- 防抖和节流都是防止短时间内高频触发事件的方案。
- 防抖的原理是:如果一定时间内多次执行了某事件,则只执行其中的最后一次。
- 节流的原理是:要执行的事件每隔一段时间会被冷却,无法执行。
- 应用场景有:搜索框实时搜索,滚动改变相关的事件。
防抖
function debounce(delay, cb) {
let timer
return function () {
if (timer) clearTimeout(timer)
timer = setTimeout(function () {
cb()
}, delay)
}
}
节流
function throttle(cb, delay) {
let startTime = Date.now()
return function () {
let currTime = Date.now()
if (currTime - startTime > delay) {
cb()
startTime = currTime
}
}
}
怎么理解Promise对象
promise
是异步编程的一种解决方案,解决了回调地狱的问题
Promise
对象共有三种状态pending
、fulfilled
、rejected
。- Promise的缺点:
Promise
一旦执行便无法被取消- Promise一旦执行便无法被取消;
- 当处于pending状态时,无法得知其具体发展到了哪个阶段
- 常用的方法有:
Promise.prototype.then()
:Promise
实例的状态发生改变时,会调用then
内部的回调函数Promise.prototype.catch()
:.then(null, rejection)
或.then(undefined, rejection)
的别名,用于指定发生错误时的回调函数。
怎么理解宏任务,微任务
- 宏任务有:
script(整体代码)
、setTimeout
、setInterval
、I/O
、页面渲染 - 微任务有:
Promise.then
、Object.observe
、MutationObserver
执行顺序大致如下:
主线程任务——>宏任务——>微任务——>微任务里的宏任务——>.......——>直到任务全部完成
require/import之间的区别
require
是CommonJS
语法,import
是ES6
语法require
只在后端服务器支持,import
在高版本浏览器及Node
中都可以支持require
引入的是原始导出值的复制,import
则是导出值的引用require
是运行时动态加载,import
是静态编译require
调用时默认不是严格模式,import
则默认调用严格模式
call()、apply()、bind()的区别
call()
、apply()
、bind()
是用来改变this的指向的
call()
:Function.call(obj, param1,param2,param3) 一个一个的参数apply()
:Function.apply(obj, [param1,param2,param3]) 参数是数组bind()
:const newFn = Funtion.bind(obj, param1,param2) 返回值是一个function
,调用newFn(param3,param4)
接收到的是param1,param2,param3,param4四个参数
事件模型
W3C
中定义事件的发生经历三个阶段:捕获阶段(capturing
)、目标阶段(targetin
)、冒泡阶段(bubbling
)
- 冒泡型事件:当你使用事件冒泡时,子级元素先触发,父级元素后触发
- 捕获型事件:当你使用事件捕获时,父级元素先触发,子级元素后触发
DOM
事件流:同时支持两种事件模型:捕获型事件和冒泡型事件
- DOM0 直接绑定
<input onclick="sayHi()"/>
btn.onclick = function() {}
btn.onclick = null
DOM2
DOM2
级事件可以冒泡和捕获- 通过
addEventListener
绑定 - 通过
removeEventListener
解绑
// 绑定 btn.addEventListener('click', sayHi) // 解绑 btn.removeEventListener('click', sayHi)
DOM3
DOM3
具有更多事件类型DOM3
级事件在DOM2
级事件的基础上添加了更多的事件类型,全部类型如下:
UI事件,当用户与页面上的元素交互时触发,如:
load
、scroll
焦点事件,当元素获得或失去焦点时触发,如:blur
、focus
鼠标事件,当用户通过鼠标在页面执行操作时触发如:dbclick
、mouseup
滚轮事件,当使用鼠标滚轮或类似设备时触发,如:mousewheel
文本事件,当在文档中输入文本时触发,如:textInput
键盘事件,当用户通过键盘在页面上执行操作时触发,如:keydown
、keypress
合成事件,当为IME
(输入法编辑器)输入字符时触发,如:compositionstart
变动事件,当底层DOM
结构发生变化时触发,如:DOMsubtreeModified
target和currentTarget区别
event.target
返回触发事件的元素event.currentTarget
返回绑定事件的元素
阻止冒泡
W3C
中,使用stopPropagation()
方法IE
下设置cancelBubble = true
阻止事件的默认行为
W3C
中,使用preventDefault()
方法IE
下设置window.event.returnValue = false
javascript有哪些方法定义对象
- 对象字面量:
var obj = {}
; - 构造函数:
var obj = new Object()
; Object.create()
:var obj = Object.create(Object.prototype)
;
map与forEach的区别
forEach
方法,是最基本的方法,就是遍历与循环,默认有3个传参:分别是遍历的数组内容item
、数组索引index
、和当前遍历数组Array
map
方法,基本用法与forEach
一致,但是不同的,它会返回一个新的数组,所以在callback
需要有return
值,如果没有,会返回undefined
for in 和 for of 区别
for...in
循环出的是key
,for...of
循环出的是value
Object.prototype.objCustom = function() {};
Array.prototype.arrCustom = function() {};
let iterable = [3, 5, 7];
iterable.foo = 'hello';
for (let i in iterable) { // 循环的是索引
console.log(i); // 打印 0, 1, 2, "foo", "arrCustom", "objCustom"
}
for (let i in iterable) {
if (iterable.hasOwnProperty(i)) {
console.log(i); // 打印 0, 1, 2, "foo"
}
}
for (let i of iterable) { // 迭代的是值
console.log(i); // 打印 3, 5, 7
}
如何做到修改url参数页面不刷新
HTML5
引入了history.pushState()
和history.replaceState()
方法,它们分别可以添加和修改历史记录条目。
let stateObj = {
foo: "bar",
};
history.pushState(stateObj, "page 2", "bar.html");
动态添加script标签
function loadScript(url, callback){
var s = document.createElement('script');
s.type = 'text/javascript';
if(s.readyState){
s.onreadystatechange = function(){ //兼容IE
if(s.readyState == 'complete' || s.readyState == 'loaded'){
callback();
}
}
}else{
s.onload = function(){ //safari chrome opera firefox
callback();
}
}
s.src = url;
document.head.appendChild(s);
}
//loadScript('https://cdn.bootcdn.net/ajax/libs/jquery/3.5.1/jquery.js',function(){alert(1)})
JSON
- JSON字符串转换为JSON对象
var obj = eval('(' + str + ')');
var obj = str.parseJSON;
var obj = JSON.parse(str);
应该避免使用
eval
,不安全,非常耗性能(2次,一次解析成js语句,一次执行)
- JSON对象转换为JSON字符串
var str = obj.toJSONString();
var str = JSON.stringify(obj);
document load 和 document ready 的区别
页面加载完成有两种事件:
load
是当页面所有资源全部加载完成后(包括DOM
文档树,css
文件,js
文件,图片资源等),执行一个函数
问题:如果图片资源较多,加载时间较长,onload
后等待执行的函数需要等待较长时间,所以一些效果可能受到影响$(document).ready()
是当DOM
文档树加载完成后执行一个函数 (不包含图片,css
等)所以会比load
较快执行
在原生的js
中不包括ready()
这个方法,只有load
方法也就是onload
事件
XML和JSON的区别
- 数据体积:
JSON
相对于XML
来讲,数据的体积小。 - 数据交互:
JSON
与JavaScript
的交互更加方便,更容易解析处理 - 数据描述:
JSON
对数据的描述性比XML
较差 - 传输速度:
JSON
的速度要远远快于XML
WebSocket
由于http
存在一个明显的弊端,即消息只能由客户端推送到服务器端,而服务器端不能主动推送到客户端,导致如果服务器如果有连续的变化,这时只能使用轮询。而轮询效率过低,并不适合,于是 WebSocket
被发明出来,相比与http
具有以下优点:
- 支持双向通信,实时性更强
- 可以发送文本,也可以二进制文件
- 协议标识符是
ws
,加密后是wss
- 较少的控制开销。连接创建后,
ws
客户端、服务端进行数据交换时,协议控制的数据包头部较小 - 支持扩展
- 无跨域问题
- 实现比较简单,服务端库如
socket.io
、ws
,可以很好的帮助入门。客户端也只需要参照api
实现即可
白屏时间
白屏时间是指浏览器从输入网址,到浏览器开始显示内容的时间。
Performance
接口可以获取到当前页面中与性能相关的信息,该类型的对象可以通过调用只读属性Window.performance
来获得
performance.timing.navigationStart
是一个返回代表一个时刻的unsigned long long
型只读属性,为紧接着在相同的浏览环境下卸载前一个文档结束之时的Unix
毫秒时间戳。如果没有上一个文档,则它的值相当于PerformanceTiming.fetchStart
所以将以下脚本放在</head>
前面就能获取白屏时间
<script>
new Date() - performance.timing.navigationStart
</script>