闭包就是能够读取其他函数内部变量的函数
闭包是指有权访问另⼀个函数作用域中变量的函数
,创建闭包的最常⻅的方式就是在⼀个函数内创建另⼀个函数, 通过另⼀个函数访问这个函数的局部变量,利用闭包可以突破作用链域
闭包的特性:
函数内再嵌套函数
内部函数可以引用外层的参数和变量
参数和变量不会被垃圾回收机制回收
说说你对闭包的理解
使用闭包主要是为了设计私有的方法和变量。闭包的优点是可以避免全局变量的污染
, 缺点是闭包会常驻内存,会增大内存使用量,使用不当很容易造成内存泄露
。在js中, 函数即闭包, 只有函数才会产生作用域的概念
闭包的最大用处有几个,
⼀个是可以读取函数内部的变量
,
另⼀个就是让这些变量始终保 持在内存中
闭包的另⼀个用处, 是封装对象的私有属性和私有方法
好处:能够实现封装和缓存等;
坏处:就是消耗内存 、不正当使用会造成内存溢出的问题
使用闭包的注意点
由于闭包会使得函数中的变量都被保存在内存中, 内存消耗很大,所以不能滥用闭包, 否则会造成网页的性能问题,在IE中可能导致内存泄露
解决方法是,在退出函数之前,将不使用的局部变量全部删除
使用闭包的示例
当内部函数在外部函数返回后仍然可以访问外部函数的变量时,就形成了闭包。
下面是一个经典的 JavaScript 闭包示例:
function outerFunction() {
var x = 10;
function innerFunction() {
console.log(x); // 内部函数访问外部函数的变量 x
}
return innerFunction; // 返回内部函数的引用
}
var closure = outerFunction(); // 调用外部函数并将返回的内部函数保存在 closure 变量中
closure(); // 输出 10,内部函数仍然可以访问外部函数的变量 x
在这个示例中,outerFunction()
是外部函数,它有一个变量 x
和一个内部函数 innerFunction()
。内部函数可以访问外部函数的变量 x
,即使外部函数已经执行完毕。outerFunction()
返回了内部函数的引用,并将其保存在 closure
变量中。然后,我们通过调用 closure()
来执行内部函数,输出变量 x
的值,即 10。
闭包是 JavaScript 中强大且常用的概念。通过使用闭包,我们可以实现许多有趣和有用的功能,比如创建私有变量、封装特定逻辑等。
作用域链的作用是保证执⾏环境里有权访问的变量和函数是有序的
,作用域链的变量只能向上访问
, 变量访问到 window 对象即被终止
,作用域链向下访问变量是不被允许的
简单的说,作用域就是变量与函数的可访问范围, 即作用域控制着变量与函数的可⻅性和生命周期
作用域链是 JavaScript 中用于查找变量和函数的机制
。它是由多个执行环境(也称为作用域)形成的嵌套结构,每个作用域都有一个关联的变量对象或词法环境。
当在代码中引用一个变量时,JavaScript 引擎会按照作用域链从内到外依次查找该变量。作用域链的构建方式是根据变量的声明位置确定的。
在函数中,每个函数都有自己的作用域,在函数创建时会创建一个特殊的内部对象,我们称之为词法环境。词法环境包含了函数的所有局部变量,函数的参数以及对外层作用域的引用。当函数执行时,它会在自己的词法环境中查找变量,并在需要时逐级向外层作用域查找,直到找到变量或者达到全局作用域。
如果变量在当前作用域中找不到,JavaScript 引擎会继续沿着作用域链向外层作用域查找,直到达到全局作用域。全局作用域是所有作用域的最外层作用域,它包含了所有全局变量和函数,以及一些内置的 JavaScript 对象和函数。
作用域链的构建过程发生在函数定义时。当一个函数被定义时,它的作用域链就被确定了,它会包含函数自身的词法环境以及外层作用域的引用。
总之,作用域链是 JavaScript 中用于查找变量和函数的一种机制,它由多个嵌套的作用域构成,根据变量的声明位置进行查找。通过作用域链,我们可以访问到不同作用域中的变量,这样可以确保变量的可见性和封装性。
• 原型: 每个对象都会在其内部初始化⼀个属性,就是 prototype (原型)
, 当我们访问⼀个对象的属性时
• 原型链 : 如果这个对象内部不存在这个属性,那么他就会去 prototype 里找这个属性, 这个 prototype ⼜会有自⼰的 prototype , 于是就这样⼀直找下去,也就是我们平时所说的 原型链的概念
关系: instance.constructor.prototype = instance.__proto__
特点
JavaScript 对象是通过引用来传递的, 我们创建的每个新对象实体中并没有⼀份属于自⼰的原型副本 。当我们修改原型时,与之相关的对象也会继承这⼀改变
当我们需要⼀个属性的时,Javascript 引擎会先看当前对象中是否有这个属性, 如果没有,就会查找他的 Prototype对象是否有这个属性, 如此递推下去,⼀ 直检索到 Object 内建对象.
**事件代理 ( Event Delegation )**, ⼜称之为 事件委托 。是 JavaScript 中常用绑定事 件的常用技巧 。顾名思义,“事件代理”即是把原本需要绑定的事件委托给父元素,让父元素担当事件监听的职务 。事件代理的原理是DOM元素的事件冒泡 。使用事件代理的好处是 可以提高性能
优点:
可以大量节省内存占用,减少事件注册, 比如在 table 上代理所有 td 的 click 事件就 非常棒
可以实现当新增子对象时无需再次对其绑定
事件代理示例
事件代理(Event delegation)是一种常用的 JavaScript 模式,它允许我们使用单个事件处理程序来处理多个子元素的事件。
这种模式非常适合在动态添加或移除子元素的情况下,提供更高效的事件处理方式。
下面是一个事件代理的示例:
- Item 1
- Item 2
- Item 3
- Item 4
在这个示例中,我们有一个 ul
元素,并在其中包含了多个 li
子元素。我们希望在点击任何一个 li
元素时,输出被点击的文本内容。
首先,通过 document.getElementById()
方法获取到了 ul
元素的引用。然后,定义了一个名为 handleClick
的事件处理函数。在这个函数中,我们使用了事件对象 event
的 target
属性来获取触发事件的元素。
通过检查 event.target.tagName
属性,我们可以确定被点击的元素是否是 li
元素。如果是,就可以获取其文本内容,并进行相应的处理(在这里,我们简单地将文本内容输出到控制台)。
最后,通过 addEventListener
方法将 click
事件绑定到 ul
元素,并指定 handleClick
函数作为事件处理函数。这样就实现了事件代理,在点击 li
元素时触发统一的事件处理逻辑。
通过事件代理,我们可以避免给每个子元素都添加事件监听器,而是将事件处理放在父元素上进行。这样可以节省内存和提高性能,尤其在大规模或动态变化的元素列表中非常有用。
构造继承
原型继承
实例继承
拷贝继承
原型 prototype 机制或 apply 和 call 方法去实现较简单, 建议使用构造函数与原型混合方式
Javascript如何实现继承示例
在 JavaScript 中,可以使用以下几种方式来实现继承:
原型链继承: 原型链继承是 JavaScript 中最基本的继承方式,通过将子类的原型对象指向父类的实例来实现继承。这样,子类就可以访问到父类原型上的属性和方法。
function Parent() {
this.name = 'Parent';
}
Parent.prototype.sayHello = function() {
console.log('Hello, I am ' + this.name);
};
function Child() {
this.name = 'Child';
}
Child.prototype = new Parent();
var child = new Child();
child.sayHello(); // 输出:Hello, I am Child
构造函数继承: 构造函数继承通过在子类的构造函数中调用父类的构造函数来实现继承。这样,子类就可以继承父类的属性。
function Parent() {
this.name = 'Parent';
}
function Child() {
Parent.call(this);
this.name = 'Child';
}
var child = new Child();
console.log(child.name); // 输出:Child
组合继承: 组合继承结合了原型链继承和构造函数继承的优点,既可以继承父类的属性,又可以继承父类原型上的方法。
function Parent() {
this.name = 'Parent';
}
Parent.prototype.sayHello = function() {
console.log('Hello, I am ' + this.name);
};
function Child() {
Parent.call(this);
this.name = 'Child';
}
Child.prototype = new Parent();
Child.prototype.constructor = Child;
var child = new Child();
child.sayHello(); // 输出:Hello, I am Child
型式继承: 原型式继承通过创建一个临时的构造函数,将父类的实例作为新创建对象的原型,从而实现继承。
function createObject(parent) {
function F() {}
F.prototype = parent;
return new F();
}
var parent = {
name: 'Parent',
sayHello: function() {
console.log('Hello, I am ' + this.name);
}
};
var child = createObject(parent);
child.name = 'Child';
child.sayHello(); // 输出:Hello, I am Child
ES6 中的 class 和 extends: ES6 引入了 class
关键字和 extends
关键字,提供了更简洁的语法来定义类和实现继承。
class Parent {
constructor() {
this.name = 'Parent';
}
sayHello() {
console.log('Hello, I am ' + this.name);
}
}
class Child extends Parent {
constructor() {
super();
this.name = 'Child';
}
}
var child = new Child();
child.sayHello(); // 输出:Hello, I am Child
以上是几种常见的继承方式,每种方式都有各自的优缺点。
在选择实现继承方式时,可以根据具体需求和场景来选择适合的方式。
this 总是指向函数的直接调用者 ( 而非间接调用者)
如果有 new 关键字, this 指向 new 出来的那个对象
在事件中, this 指向触发这个事件的对象, 特殊的是,IE 中的 attachEvent 中的this 总是指向全局对象 Window
在 JavaScript 中,this
是一个特殊的对象,它在函数被调用时自动创建,并且指向调用该函数的上下文对象。
this
的值取决于函数是如何被调用的,它可以有多种不同的值。
具体来说,以下是 this
的一些常见用法和理解:
在全局作用域中,当直接在全局作用域中使用 this
,它指向全局对象(在浏览器环境中通常是 window
对象)。
在函数中,this
取决于函数的调用方式。如果函数是作为对象的方法被调用(例如 obj.method()
),则 this
指向调用该方法的对象(obj
)。如果函数是以函数形式被调用(例如 func()
),则 this
取决于函数的调用上下文,通常是全局对象(浏览器环境下是 window
)。
使用构造函数创建对象时,this
指向新创建的实例对象。
使用 call()
或 apply()
方法调用函数时,可以显式地指定 this
的值。
使用箭头函数时,this
与所在的词法作用域保持一致,而不是动态绑定。
总之,this
提供了一种在函数内部引用当前执行上下文的机制,它的值在函数被调用时动态确定。要理解 this
的具体值,需要考虑函数的调用方式和上下文环境。
This对象示例
当谈到 this
对象的示例时,以下是一些常见的用法和场景:
全局作用域中的 this
对象示例:
console.log(this); // 在浏览器环境中通常指向 window 对象
方法调用中的 this
对象示例:
const obj = {
name: 'Alice',
sayHello: function() {
console.log('Hello, ' + this.name);
}
};
obj.sayHello(); // 输出:Hello, Alice
构造函数中的 this
对象示例:
function Person(name) {
this.name = name;
}
const person = new Person('Bob');
console.log(person.name); // 输出:Bob
使用 call()
或 apply()
改变 this
对象示例:
const obj1 = { name: 'Alice' };
const obj2 = { name: 'Bob' };
function greet() {
console.log('Hello, ' + this.name);
}
greet.call(obj1); // 输出:Hello, Alice
greet.call(obj2); // 输出:Hello, Bob
箭头函数中的 this
对象示例:
const obj = {
name: 'Alice',
sayHello: function() {
setTimeout(() => {
console.log('Hello, ' + this.name);
}, 1000);
}
};
obj.sayHello(); // 输出:Hello, Alice(箭头函数保持词法作用域的 this 值)
这些示例展示了 this
对象在不同情况下的使用方式和取值。注意,在实际编程中,this
的行为可能会更加复杂,因此在使用时需要谨慎理解和处理。
W3C 中定义事件的发生经历三个阶段:捕获阶段 ( capturing ) 、 目标阶段 ( targetin ) 、冒泡阶段 ( bubbling )
冒泡型事件: 当你使用事件冒泡时, 子级元素先触发, 父级元素后触发
捕获型事件: 当你使用事件捕获时, 父级元素先触发, 子级元素后触发
DOM 事件流: 同时支持两种事件模型:捕获型事件和冒泡型事件
阻止冒泡:在 W3c 中,使用 stopPropagation() 方法;在IE下设置 cancelBubble = true
阻止捕获:阻止事件的默认行为,例如 click - 后的跳转 。在 W3c 中,使用 preventDefault() 方法,在 IE 下设置 window.event.returnValue = false
创建⼀个空对象, 并且 this 变量引用该对象, 同时还继承了该函数的原型
属性和方法被加⼊到 this 引用的对象中
新创建的对象由this 所引用, 并且最后隐式的返回 this
在JavaScript中,new
操作符用于创建一个对象实例。使用new
操作符时,它会执行以下步骤:
创建一个空的普通JavaScript对象。
将这个新对象的原型(__proto__
)设置为构造函数的原型(即构造函数的 prototype
属性)。
执行构造函数,并将构造函数内部的this
指向这个新对象。
如果构造函数没有显式返回一个对象,则返回这个新对象。
下面是一个示例来说明new
操作符的作用:
// 定义一个构造函数
function Person(name, age) {
this.name = name;
this.age = age;
this.sayHello = function() {
console.log("Hello, my name is " + this.name);
};
}
// 使用new操作符创建一个Person对象实例
var person1 = new Person("Alice", 25);
console.log(person1.name); // 输出: "Alice"
console.log(person1.age); // 输出: 25
person1.sayHello(); // 输出: "Hello, my name is Alice"
在上面的示例中,我们定义了一个构造函数Person
,它有两个参数name
和age
,以及一个方法sayHello
。当我们使用new
操作符创建一个对象实例person1
时,new
操作符会执行以下操作:
创建一个空的普通JavaScript对象,即person1
。
将person1
对象的原型(__proto__
)设置为Person.prototype
。
在Person
的执行上下文中,将构造函数内部的this
指向person1
对象。
执行构造函数,将传入的参数赋值给person1
的属性。在这个例子中,person1.name
被设置为"Alice"
,person1.age
被设置为25
。
Person
构造函数中定义的sayHello
方法也被添加到了person1
对象上。
new
操作符返回这个新创建的对象实例person1
。
通过使用new
操作符,我们可以创建一个带有特定属性和方法的对象实例,并且该对象实例可以通过构造函数中的this
关键字进行访问和操作。
Ajax 的原理简单来说是 **在用户和服务器之间加了—个中间层( AJAX 引擎)**, 通过 XmlHttpRequest 对象来向服务器发异步请求,从服务器获得数据,然后用 javascript 来操作 DOM 而更新页面 。使用户操作与服务器响应异步化 。这其中最关键的⼀步就是从服务器获得请求数据
Ajax 的过程只涉及 JavaScript 、 XMLHttpRequest 和 DOM 。
XMLHttpRequest 是 aja x的核⼼机制
ajax 有那些优缺点?
优点:
通过异步模式,提升了用户体验.
优化了浏览器和服务器之间的传输,减少不必要的数据往返,减少了带宽占用.
Ajax 在客户端运行,承担了⼀部分本来由服务器承担的⼯作,减少了大用户量下的服 务器负载。
Ajax 可以实现动态不刷新 ( 局部刷新)
缺点:
安全问题 AJAX 暴露了与服务器交互的细节。
对搜索引擎的支持比较弱。
不容易调试。
首先了解下浏览器的 同源策略
同源策略 /SOP ( Same origin policy) 是⼀ 种约定, 由Netscape公司1995年引⼊浏览器, 它是浏览器最核⼼也最基本的 安全功能, 如果缺少了同源策略, 浏览器很容易受到 XSS 、 CSFR 等攻击 。 所谓同源是指"协议+域名+端口"三者相同, 即便两个不同的域名指向同⼀个ip 地址,也非同源
那么怎样解决跨域问题的呢? 在 JavaScript 中,解决跨域问题的常见方法包括以下几种:
CORS(跨域资源共享):CORS 是一种通过浏览器和服务器进行交互的机制,允许服务器告知浏览器是否允许进行跨域访问。服务器需要在响应头中设置相应的 CORS 头信息来授权特定的源(域、协议和端口)访问资源。例如:
// 客户端代码
fetch('http://api.example.com/data', {
method: 'GET',
headers: {
'Origin': 'http://yourdomain.com'
}
})
.then(response => response.json())
.then(data => {
// 处理返回的数据
})
.catch(error => {
// 处理错误
});
在服务器端的响应头中设置相关的 CORS 头信息:
Access-Control-Allow-Origin: http://yourdomain.com
Access-Control-Allow-Methods: GET, POST, PUT, DELETE
Access-Control-Allow-Headers: Content-Type
JSONP(JSON with Padding):JSONP 是一种利用 标签可以跨域加载资源的技术。通过动态创建一个
标签,并将跨域请求封装为一个回调函数,服务器返回的数据会作为参数传递给回调函数。例如:
function handleResponse(data) {
// 处理返回的数据
}
var script = document.createElement('script');
script.src = 'http://api.example.com/data?callback=handleResponse';
document.body.appendChild(script);
代理服务器:可以设置一个与目标服务器同源的代理服务器,客户端与代理服务器进行通信,由代理服务器去请求需要跨域访问的资源,并将结果返回给客户端。例如,使用 Node.js 和 Express 框架搭建一个简单的代理服务器:
const express = require('express');
const fetch = require('node-fetch');
const app = express();
app.get('/api/data', (req, res) => {
const url = 'http://api.example.com/data';
fetch(url)
.then(response => response.json())
.then(data => res.json(data))
.catch(error => res.status(500).json({ error: 'Something went wrong' }));
});
app.listen(3000, () => {
console.log('代理服务器已启动,监听端口 3000');
});
通过访问代理服务器的 /api/data
路径来获取跨域资源。
这些方法可以用来解决跨域问题,具体选择哪种方法取决于你的需求和实际情况。