JavaScript篇(持续更新)
1、原型是什么?原型链是什么?
原型是一个prototype对象,用于表示类型之间的关系;
原型链指的是在JavaScript中对象之间的继承是通过prototype对象指向父类对象,直到指向Object对象为止,这样就形成了一个原型指向的链条,专业术语称之为原型链。
举例:
Student——>Person——>Object:学生继承人这个类,人这个类继承对象类;
<span style="font-size:14px;">
var Person=function(){
this.age="匿名"
}; var Student=function(){}; //创建继承关系,prototype执行Person的一个实例对象 Student.prototype=new Person();
</span>
五条原型规则:
1、所有的引用类型(除null外)都可以自由扩展属性;
2、所有的引用类型都有一个_proto_属性,叫隐式原型,属性值是一个普通的对象;
3、所有的函数,都有一个prototype属性,叫显式原型,属性值是一个普通的对象;
4、所有的引用类型的_proto_属性,指向它的构造函数的prototype属性值;
5、当试图得到一个对象的某个属性时,如果没有,会向它的_proto_中寻找,即去它的构造函数的prototype中寻找。
Dog类继承了Animal类,随即拥有的Animal的eat方法
<script type="text/javascript"> function Animal() { this.eat = function () { console.log("animal eat"); } } function Dog() { this.bark = function () { console.log("dog bark") } } Dog.prototype = new Animal(); var hashiqi = new Dog(); hashiqi.eat(); //animal eat hashiqi.bark(); //dog bark </script>
2、关于this的种种
隐式绑定:关于this,一般来说,谁调用了方法,该方法的this就指向谁,如:
function foo(){ console.log(this.a) } var a = 3; var obj = { a: 2, foo: foo }; obj.foo(); // 输出2,因为是obj调用的foo,所以foo的this指向了obj,而obj.a = 2
如果存在多次调用,对象属性引用链只有上一层或者说最后一层在调用位置上起作用,如:
function foo() { console.log( this.a ) } var obj2 = { a: 42, foo: foo } var obj1 = { a: 2, obj2: obj2 } obj1.obj2.foo(); // 42
隐式丢失:一个最常见的this绑定问题就是被隐式绑定的函数会丢失绑定对象,也就是说他应用默认绑定,从而把this绑定到全局对象或者undefined上,取决于是否是严格模式,如:
function foo() { console.log( this.a ) } var obj1 = { a: 2, foo: foo } var bar = obj1.foo; // 函数别名! var a = "oops, global"; // a是全局对象的属性 bar(); // "oops, global"
虽然bar是obj1.foo的一个引用,但是实际上,它引用的只是foo函数本身,而不是方法本身,因此此时的bar()其实是一个不带任何修饰的函数调用,因此应用了默认绑定。稍作修改,结果就会变,如:
function foo() { console.log( this.a ) } var obj1 = { a: 2, foo: foo }
var a = "oops, global"; // a是全局对象的属性
var bar = obj1.foo(); //2
一个更微妙、更常见并且更出乎意料的情况发生在传入回调函数时:
function foo() { console.log( this.a ) } function doFoo( fn ){ // fn 其实引用的是 foo fn(); // <-- 调用位置! } var obj = { a: 2, foo: foo } var a = "oops, global"; // a是全局对象的属性 doFoo( obj.foo ); // "oops, global"
参数传递其实就是一种隐式赋值,因此我们传入函数时也会被隐式赋值,所以结果和上一个例子一样,obj调用了函数本身,而不是方法,因此this.a指向了全局对象的属性a。如果把函数传入语言内置的函数而不是传入自己声明的函数(如setTimeout等),结果也是一样的。
显示绑定:简单的说,就是指定this,如call()、apply()、bind()方法绑定等。
硬绑定:因为普通的显示绑定无法解决隐式丢失的问题,因此出现了硬绑定。硬绑定是显式绑定的一个变种,使this不能再被修改,如:
function foo( something ) { console.log( this.a, something) return this.a + something } var obj = { a: 2 } var bar = function() { return foo.apply( obj, arguments) } var b = bar(3); // 2 3 console.log(b); // 5
在bar函数中,foo使用了apply函数绑定了obj,也就是说foo中的this将指向obj,与此同时,使用arguments(不限制传入参数的数量)作为参数传入foo函数中。所以在运行bar(3)的时候,首先输出obj.a也就是2和传入的3,然后foo返回了两者的相加值,所以b的值为5。
同样,本例也可以使用bind:
function foo( something ) { console.log( this.a, something) return this.a + something } var obj = { a: 2 } var bar = foo.bind(obj) var b = bar(3); // 2 3 console.log(b); // 5
new绑定:在传统的面向类的语言中,使用new初始化类的时候会调用类中的构造函数。但是JavaScript中new的机制实际上和面向类编程语言的机制完全不同。使用new来调用函数,或者说发生构造函数调用时,会自动执行下面的操作:
1、创建或者说构造一个全新的对象;
2、这个新对象会被执行prototype连接;
3、这个新对象会绑定到函数调用的this;
4、如果函数没有返回其他对象,那么new表达式中的函数会自动返回这个新对象,如:
function foo(a){ this.a = a } var bar = new foo(2); console.log(bar.a); // 2
使用new来调用foo()时,我们会构造一个新对象并把它绑定到foo()调用中的this上。new是最后一种可以影响函数调用时this绑定行为的方法,我们称之为new绑定。
this的优先级:毫无疑问,默认绑定(全局环境中,this默认绑定到window)的优先级是四条规则中最低的,所以我们可以先不考虑它。首先测试下隐式绑定和显示绑定哪个优先级更高:
function foo(a){ console.log(this.a) } var obj1 = { a: 2, foo: foo } var obj2 = { a: 3, foo: foo } obj1.foo(); // 2 obj2.foo(); // 3 obj1.foo.call(obj2); // 3 obj2.foo.call(obj1); // 2
可以看到,显式绑定优先级更高,也就是说在判断时应该先考虑是否可以存在显式绑定。
现在要搞清楚new绑定和隐式绑定的优先级谁高谁低:
function foo(a){ this.a = something } var obj1 = { foo: foo } var obj2 = {} obj1.foo(2); console.log(obj1.a); // 2 obj1.foo.call(obj2,3); console.log(obj2.a); // 3 var bar = new obj1.foo(4) console.log(obj1.a); // 2 console.log(bar.a); // 4
可以看到new绑定比隐式绑定优先级更高。
最后来看看new绑定和显式绑定谁的优先级更高:
function foo(something){ this.a = something } var obj1 = {} var bar = foo.bind(obj1); bar(2); console.log(obj1.a); // 2 var baz = new bar(3); console.log(obj1.a); // 2 console.log(baz.a); // 3
可以看到,new绑定修改了硬绑定中的this,所以new绑定的优先级比显式绑定更高。
之所以要在new中使用硬绑定函数,主要目的是预先设置函数的一些参数,这样在使用new进行初始化时就可以只传入其余的参数。bind()的功能之一就是可以把除了第一个参数(第一个参数用于绑定this)之外的其他参数都不传给下层的函数(这种技术称为“部分应用”,是“柯里化”的一种)。举例来说:
3、JavaScript的typeof返回哪些数据类型?
Object number function boolean underfind、String;
4、数组方法pop()、push()、unshift()、shift()的概念
push()尾部添加,pop()尾部删除;
unshift()头部添加,shift()头部删除;
5、JavaScript中的事件委托
事件委托就是利用事件冒泡,只指定一个事件处理程序,就可以管理某一类型的所有事件。
6、为什么要使用JavaScript的事件委托?
为了减少代码的DOM操作,提高程序性能。
更多:http://www.cnblogs.com/liugang-vip/p/5616484.html
7、JavaScript中的闭包
从技术上来讲,在JS中,每个function都是闭包,因为它总是能访问在它外部定义的数据。
8、何时使用==何时使用===?
===:是严格相等,仅考虑相同类型的值是否相等,如:console.log(1===true) //false;
== :非严格相等,在比较之前,会尝试为不同类型的值进行转换,然后类似严格相等,如:console.log(1==true) //true。
9、window.onload和DOMContentLoaded的区别是?
window.addEventListener('load',function(){ //页面的全部资源加载完才会执行,包括图片、视频等 }) document.addEventListener('DOMContentLoaded',function(){ //DOM渲染完即可执行,此时图片、视频还可能没有加载完 })
10、用JavaScript创建10个<a>标签,点击的时候弹出来对应的序号
//(function(){})()是自执行函数,就是不用调用,只要定义完成,立即执行的函数。
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title></title> </head> <body> </body> <script> //涉及到了作用域 var i; for(i=0;i<10;i++){ (function(i){ var a = document.createElement('a'); a.innerHTML = i+'<br>'; a.addEventListener('click',function(e){ e.preventDefault(); alert(i); }); document.body.appendChild(a); })(i) } </script> </html>
11、简述如何实现一个模块加载器,实现类似require.js的基本功
参考:http://www.cnblogs.com/yexiaochai/p/3961291.html
12、实现数组的随机排序
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title></title> </head> <body> </body> <script> Array.prototype.shuffle = function() { //shuffle 函数挂载在 Array 对象的原型之下,便于数组直接调用该函数。 //在 shuffle 函数内部,this 引用的就是调用该 shuffle 的数组 var input = this; for (var i = input.length-1; i >=0; i--) { //使用了两行代码在指定范围内挑选一个随机元素: var randomIndex = Math.floor(Math.random()*(i+1)); //在这三行代码中,第一行使用新的变量保存了随机元素的值; //第二行将选中元素 input[i] 的值赋给随机元素 input[randomIndex]; //第三行就随机元素的值 itemAtIndex 赋给选中元素 input[i]。 //本质上是一个互换两个元素的值的过程,并不难理解。 var itemAtIndex = input[randomIndex]; input[randomIndex] = input[i]; input[i] = itemAtIndex; } return input; } var tempArray = [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ] tempArray.shuffle(); console.log(tempArray); //循环用于遍历所有数组内的所有元素,并进行随机交换。 //注意,遍历顺序是从后往前进行的,也就是说从 input.length-1 位置的元素开始, //知道遍历到数组中的第一个元素。遍历过程中的位置由变量 i 指定。 //变量 randomIndex 存储了一个随机数,该随机数可以用作数组的索引, //进而提取一个随机元素。注意,该随机数的最大值并不是数组的长度,而是变量 i 的值。 //确定了随机元素的索引之后,用新的变量保存该元素的值,然后交换选中元素和随机元素的值 </script> </html>
13、JavaScript中有哪些内置函数,与内置对象的区别是什么?
内置函数:是浏览器内核自带的,不用任何函数库引入就可以直接使用的函数,如常规函数(alert等)、数组函数(reverse等)、日期函数(getDate等)、数学函数(floor等)、字符串函数(length等);
内置对象:是浏览器本身自带的,内置对象中往往包含了内置函数。内置对象有Object、Array、Boolean、Number、String、Function、Date、RegExp等。
14、JavaScript变量按照存储方式区分为哪些类型,并描述其特点
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title></title> </head> <body> </body> <script> //值类型 //变量的交换,按值访问,操作的是他们实际保存的值。 //等于在一个新的地方按照新的标准开了一个空间(栈内存), //这样a的值对b的值没有任何影响 var a = 100; var b = a; b = 200; console.log("a:"+a+",b:"+b); //引用类型 //变量的交换,当查询时,我们需要先从栈中读取内存地址, //然后再顺藤摸瓜地找到保存在堆内存中的值; //发现当复制的是对象,那么obj1和obj2两个对象被串联起来了, //obj1变量里的属性被改变时候,obj2的属性也被修改。 var obj1 = {x:100}; var obj2 = obj1; obj2.x = 200; console.log("obj1:"+obj1.x+",obj2:"+obj2.x); </script> </html>
15、如何理解JSON
stringify()是把对象变成字符串;parse()是把字符串变成对象
16、强制类型转换(字符窜拼接、==运算符、if语句、逻辑运算符)
17、JavaScript中&&和||
只要“||”前面为false,不管“||”后面是true还是false,都返回“||”后面的值;
只要“||”前面为true,不管“||”后面是true还是false,都返回“||”前面的值。
且在js逻辑运算中,0、”“、null、false、undefined、NaN都会判为false,其他都为true。
只要“&&”前面是false,无论“&&”后面是true还是false,结果都将返“&&”前面的值;
只要“&&”前面是true,无论“&&”后面是true还是false,结果都将返“&&”后面的值;
18、typeof运算符
19、如何准确判断一个变量是数组类型?
instanceof来判断
20、描述new一个对象的过程
1. 创建空对象;
var obj = {};
2. 设置新对象的constructor属性为构造函数的名称,设置新对象的__proto__属性指向构造函数的prototype对象;
obj.__proto__ = ClassA.prototype;
3. 使用新对象调用函数,函数中的this被指向新实例对象:
ClassA.call(obj); //{}.构造函数();
4. 将初始化完毕的新对象地址,保存到等号左边的变量中
注意:若构造函数中返回this或返回值是基本类型(number、string、boolean、null、undefined)的值,则返回新实例对象;若返回值是引用类型的值,则实际返回值为这个引用类型。
var foo = "bar"; function test () { this.foo = "foo"; } new test(); //test中的this指新对象,并未改变全局的foo属性 console.log(this.foo); // "bar" console.log(new test().foo); // "foo";
21、构造函数
构造函数就是初始化一个实例对象,对象的prototype属性是继承一个实例对象。
注意事项:
1、默认函数首字母大写;
2、构造函数并没有显示返回任何东西。new 操作符会自动创建给定的类型并返回他们,当调用构造函数时,new会自动创建this对象,且类型就是构造函数类型;
3、也可以在构造函数中显示调用return.如果返回的值是一个对象,它会代替新创建的对象实例返回。如果返回的值是一个原始类型,它会被忽略,新创建的实例会被返回;
4、因为构造函数也是函数,所以可以直接被调用,但是它的返回值为undefine,此时构造函数里面的this对象等于全局this对象。this.name其实就是创建一个全局的变量name。在严格模式下,当你补通过new 调用Person构造函数会出现错误;
22、函数声明和函数表达式的区别(作用域)
<script> //成功 fn() function fn(){ console.log("函数声明,全局"); } //报错 fn1() var fun1 = function (){ console.log("函数表达式,局部") } </script>
23、说一下对变量提升的理解
顾名思义,就是把下面的东西提到上面。在JS中,就是把定义在后面的东东(变量或函数)提升到前面中定义。
var v='Hello World'; (function(){ alert(v); //undefined var v='I love you'; })()
根据上面变量提升原件以及js的作用域(块级作用域)的分析,得知 上面代码真正变成如下:
var v='Hello World'; (function(){ var v; alert(v); v='I love you'; })()
在我们写js code 的时候,我们有2中写法,一种是函数表达式,另外一种是函数声明方式。我们需要重点注意的是,只有函数声明形式才能被提升。
//成功 function myTest(){ foo(); function foo(){ alert("我来自 foo"); } } myTest(); //失败 function myTest(){ foo(); var foo =function foo(){ alert("我来自 foo"); } } myTest();
24、说明this几种不同的使用场景
1、作为构造函数执行,如果函数创建的目的是使用new来调用,并产生一个对象,那么此函数被称为构造器函数;
var Niu = function (string) { this.name = string; };
2、作为对象属性执行,对象成员方法中的this是对象本身,此时跟其他语言是一致的,但是也有差异,JavaScript中的this到对象的绑定发生在函数调用的时候;
var myObj = { value: 0, increment: function (inc) { this.value += typeof inc === 'number' ? inc : 1; } }; myObj.increment(); //1 myObj.increment(2); //3
3、作为普通函数执行,以普通方式定义的函数中的this:会被自动绑定到全局对象;
var value = 232; function toStr() { console.log("%d", this.value); //232 }
4、对象方法中闭包函数的this
//在以普通方式定义的函数中的this会被自动绑定到全局对象上,大家应该可以看出闭包函数定义也与普通方式无异,因此他也会被绑定到全局对象上。 value = 10; var closureThis = { value: 0, acc: function () { var helper = function () { this.value += 2; console.log("this.value : %d", this.value); } helper(); } }; closureThis.acc(); //12 closureThis.acc(); //14 var closureThat = { value: 0, acc: function () { that = this; var helper = function () { that.value += 2; console.log("that.value : %d", that.value); } helper(); } }; closureThat.acc(); // 2 closureThat.acc(); // 4
5、apply函数的参数this,apply方法允许我们选择一个this值作为第一个参数传递,第二个参数是一个数组,表明可以传递多个参数。
补充:
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title></title> </head> <body> </body> <script> //作为构造函数执行 function Foo(name){ this.name = name; } var f = new Foo('zhangsan'); console.log(f); //作为对象属性执行 var obj = { name:'A', printName:function(){ console.log(this.name); } } obj.printName(); //作为普通函数执行 function fn(){ console.log(this); } fn() //call、apply和bind执行 function fn1(name){ console.log(name); console.log(this); } fn1.call({x:100},'zhangsan',20); fn1.apply({x:200},['lisi',20]); var fn2 = function(name,age){ console.log(name); console.log(this); }.bind({y:200}); fn2('wangwu',20) </script> </html>
25、如何理解作用域
函数的执行依赖于变量作用域,这个作用域是在函数定义时决定的,而不是函数调用时决定的。
function foo(){ var x = 1; return function() { alert(x); } }; var bar = foo(); bar(); // 1 var x = 2 ; bar(); // 1
JavaScript没有块级作用域,只有函数和全局作用域
26、什么是自由变量?
自由变量就是当前作用域没有定义的变量,即“自由变量”
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title></title> </head> <body> </body> <script> var a = 100; function F1(){ var b = 200; function F2(){ var c = 300; //a是自由变量 console.log(a); //b是自由变量 console.log(b); console.log(c); } F2(); } F1(); </script> </html>
27、实际开发中闭包的应用(什么是闭包)
28、this的特点
this要在执行时才能确认值,定义时无法确认。
29、同步和异步的区别是什么?分别举一个同步和异步的例子
同步会阻塞代码执行,而异步不会;alert是同步,setTimeout是异步。
30、一个关于setTimeout的笔试题
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title></title> </head> <body> </body> <script> //输出结果为13524 console.log(1); setTimeout(function(){ console.log(2); },100); console.log(3); setTimeout(function(){ console.log(4); },100); console.log(5) </script> </html> <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title></title> </head> <body> </body> <script> //输出结果13542 console.log(1); setTimeout(function(){ console.log(2); },100); console.log(3); setTimeout(function(){ console.log(4); },99); console.log(5) </script> </html>
31、何时需要异步?
在可能发生等待的情况、等待过程中不能像alert一样阻塞程序运行、因此所有的等待的情况都需要异步
32、前端使用异步的场景
定时任务:setTimeout ,setInverval
网络请求:ajax请求,动态<img>加载
事件绑定:
33、JavaScript中的异步和单线程
其实,单线程和异步确实不能同时成为一个语言的特性。js选择了成为单线程的语言,所以它本身不可能是异步的,但js的宿主环境(比如浏览器,Node)是多线程的,宿主环境通过某种方式(事件驱动,下文会讲)使得js具备了异步的属性。
js是单线程语言,浏览器只分配给js一个主线程,用来执行任务(函数),但一次只能执行一个任务,这些任务形成一个任务队列排队等候执行,但前端的某些任务是非常耗时的,比如网络请求,定时器和事件监听,如果让他们和别的任务一样,都老老实实的排队等待执行的话,执行效率会非常的低,甚至导致页面的假死。所以,浏览器为这些耗时任务开辟了另外的线程,主要包括http请求线程,浏览器定时触发器,浏览器事件触发线程,这些任务是异步的。
34、获取当天的日期(如:2017-06-12)
35、获取随机数,要求是长度一致的字符串格式
36、写一个能遍历对象和数组的通用forEach函数
需要增加一个判断:if(obj.hasOwnProperty(key)){ fn(key,obj[key])}
37、DOM是哪种基本的数据结构?
树;
38、DOM操作的常用API有哪些?
获取DOM节点,以及节点的property和Attribute;获取父节点和子节点;新增节点和删除节点
39、DOM节点的attr和property有何区别?
property只是一个JavaScript对象的属性的修改;Attribute是对html标签属性的修改
40、DOM的本质
浏览器把拿到的HTML代码,结构化一个浏览器能识别并且js可以操作的一个模型而已。
41、编写一个通用的事件监听函数
42、描述事件冒泡流程
43、对于一个无限下拉加载图片的页面,如何给每个图片绑定事件?
44、手动编写一个ajax,不依赖第三方库
45、跨域的几种实现方式
通过jsonp跨域:
在js中,我们直接用XMLHttpRequest请求不同域上的数据时,是不可以的。但是,在页面上引入不同域上的js脚本文件却是可以的,jsonp正是利用这个特性来实现的。
比如,有个a.html页面,它里面的代码需要利用ajax获取一个不同域上的json数据,假设这个json数据地址是http://example.com/data.php,那么a.html中的代码就可以这样:
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title></title> </head> <body> </body> <script> function dosomething(jsondata){ //处理获得的json数据 } </script> <script src="http://example.com/data.php?callback=dosomething"></script> </html>
我们看到获取数据的地址后面还有一个callback参数,按惯例是用这个参数名,但是你用其他的也一样。当然如果获取数据的jsonp地址页面不是你自己能控制的,就得按照提供数据的那一方的规定格式来操作了。
通过修改document.domain来跨子域:
使用window.name来进行跨域:
使用HTML5中新引进的window.postMessage方法来跨域传送数据:
46、什么是跨域?
浏览器有同源策略,不允许ajax访问其他域接口。跨域条件(协议、域名、端口)有一个不同就算是跨域。可以跨域的三个标签(<img src=xxx>、<link href=xxx>、<script src=xxx>)。三个标签的应用场景(<img>用于打点统计,统计网站可能是其他域、<link><script>可以使用CDN,CDN的也是其他域、<script>可以用于JSOP)。;
跨域注意的事项:所有的跨域请求都必须经过信息提供方允许、如果未经过允许即可获取,那是浏览器同源策略出现漏洞。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· AI 智能体引爆开源社区「GitHub 热点速览」
· 三行代码完成国际化适配,妙~啊~
· .NET Core 中如何实现缓存的预热?