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语句中,都会被自动转为falseundefined不是一个有效的JSON,而null是undefined的类型(typeof)是undefinednull的类型(typeof)是objectJavascript将未赋值的变量默认值设为undefinedJavascript从来不会将变量设为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、和当前遍历数组Arraymap方法,基本用法与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>