前端面试送命题(一)-JS三座大山

前言

本篇文章比较适合3年以上的前端工作者,JS三座大山分别指:原型与原型链,作用域及闭包,异步和单线程。

原型与原型链

说到原型,就不得不提一下构造函数,首先我们看下面一个简单的例子:

复制代码; "复制代码")

function Dog(name,age){   
    this.name = name;
    this.age = age;
}

let dog1 = new Dog("哈士奇",3);
let dog2 = new Dog("泰迪",2);

复制代码; "复制代码")

首先创造空的对象,再让this指向这个对象,通过this.name进行赋值,最终返回this,这其实也是new 一个对象的过程。

其实: let obj = {} 是 let obj = new Object()的语法糖; let arr = [] 是 let arr = new Array()的语法糖; function Dog(){...} 是 let Dog = new Fucntion()的语法糖。

那什么是原型那?在js中,所有对象都是Object的实例,并继承Object.prototype的属性和方法,但是有一些是隐性的。

我们来看一下原型的规则:

1.所有的引用类型(包括数组,对象,函数)都具有对象特性;可自由扩展属性。

var obj = {};
obj.attribute = "三座大山";
var arr = [];
arr.attribute = "三座大山";
function fn1 () {}
fn1.attribute = "三座大山";

2.所有的引用类型(包括数组,对象,函数)都有隐性原型属性(__proto__),值也是一个普通的对象。

console.log(obj.__proto__);

3.所有的函数,都有一个prototype属性,值也是一个普通的对象。

console.log(obj.prototype);

4.所有的引用类型的__proto__属性值都指向构造函数的prototype属性值。

console.log(obj.__proto__ === Object.prototype); // true

5.当试图获取对象属性时,如果对象本身没有这个属性,那就会去他的__proto__(prototype)中去寻找。

复制代码; "复制代码")

function Dog(name){ 
   this.name = name; 
} 
Dog.prototype.callName = function (){ 
   console.log(this.name,"wang wang"); 
}
let dog1 = new Dog("Three Mountain"); 
dog1.printName = function (){ 
   console.log(this.name);
}
dog1.callName();  // Three Mountain wang wang
dog1.printName(); // Three Mountain

复制代码; "复制代码")

原型链:如下图。

我找一个属性,首先会在f.__proto__中去找,因为属性值为一个对象,那么就会去f.__proto__.__proto__去找,同理如果还没找到,就会一直向上去查找,直到结果为null为止。这个串起来的链即为原型链。

作用域及闭包

讲到作用域,你会想到什么?当然是执行上下文。每个函数都有自己的excution context,和variable object。这些环境用于储存上下文中的变量,函数声明,参数等。只有函数才能制造作用域。

PS:for if else 不能创造作用域。

复制代码; "复制代码")

console.log(a) ; // undefined
var a = 1;

//可理解为
var a;
console.log(a); // undefined
a = 1;

复制代码; "复制代码")

执行console.log时,a只是被声明出来,并没有赋值;所以结果当然是undefined。

**this
**

本质上来说,在js里this是一个指向函数执行环境的指针。this永远指向最后调用它的对象,并且在执行时才能获取值,定义是无法确认他的值。

复制代码; "复制代码")

var a = { 
    name : "A", 
    fn : function (){
        console.log (this.name) 
    } 
} 
a.fn() // this === a 
a 调用了fn() 所以此时this为a

a.fn.call ({name : "B"}) // this === {name : "B"} 
使用call(),将this的值指定为{name:"B"}

var fn1 = a.fn 
fn1() // this === window
虽然指定fn1 = a.fn,但是调用是有window调用,所以this 为window

复制代码; "复制代码")

this有多种使用场景,下面我会主要介绍4个使用场景:

1.作为构造函数执行

function  Student(name,age) {
    this.name = name           // this === s
    this.age = age             // this === s
    //return  this
}
var s = new Student("py1988",30)

首先new 字段会创建一个空的对象,然后调用apply()函数,将this指向这个空对象。这样的话,函数内部的this就会被空对象代替。

2.作为普通函数执行

function  fn () {
    console.log (this)       // this === window
}
fn ()

3.作为对象属性执行

复制代码; "复制代码")

var obj = {
    name : "A",
    printName : function () {
        console.log (this.name)  // this === obj
    }
}
obj.printName ()

复制代码; "复制代码")

4.call(),apply(),bind()

三个函数可以修改this的指向,具体请往下看:

复制代码; "复制代码")

var name = "小明" , age = "17"
var obj = { 
    name : "安妮", 
    objAge : this.age, 
    fun : function () { 
            console.log ( this.name + "今年" + this.age ) 
        } 
} 
console.log(obj.objAge) // 17 
obj.fun() // 安妮今年undefined

复制代码; "复制代码")

复制代码; "复制代码")

var name = "小明" , age = "17" 
var obj = { 
    name : "安妮", 
    objAge :this.age, 
    fun : function (like,dislike) { 
              console.log (this.name + "今年" + this.age ,"喜欢吃" + like + "不喜欢吃" + dislike) 
           } 
    } 
var a = { name : "Jay", age : 23 } 
obj.fun.call(a,"苹果","香蕉") // Jay今年23 喜欢吃苹果不喜欢吃香蕉 
obj.fun.apply(a,["苹果","香蕉"]) // Jay今年23 喜欢吃苹果不喜欢吃香蕉 
obj.fun.bind(a,"苹果","香蕉")() // Jay今年23 喜欢吃苹果不喜欢吃香蕉

复制代码; "复制代码")

首先call,apply,bind第一个参数都是this指向的对象,call和apply如果第一个参数指向null或undefined时,那么this会指向windows对象。

call,apply,bind的执行方式如上例所示。call,apply都是改变上下文中的this,并且是立即执行的。bind方法可以让对应的函数想什么时候调用就什么时候调用。

闭包

闭包的概念很抽象,看下面的例子你就会理解什么叫闭包了:

复制代码; "复制代码")

function a(){
  var n = 0;
  this.fun = function () {
    n++; 
    console.log(n);
  };
}
var c = new a();
c.fun();  //1
c.fun();  //2

复制代码; "复制代码")

闭包就是能够读取其他函数内部变量的函数。在js中只有函数内部的子函数才能读取局部变量。所以可以简单的理解为:定义在内部函数的函数。

用途主要有两个:

1)前面提到的,读取函数内部的变量。

2)让变量值始终保持在内存中。

异步和单线程

我们先感受下异步。

console.log("start");
setTimeout(function () {
    console.log("medium");
}, 1000);
console.log("end");

使用异步后,打印的顺序为 start-> end->medium。因为没有阻塞。

为什么会产生异步呢?

首先因为js为单线程,也就是说CPU同一时间只能处理一个事务。得按顺序,一个一个处理。

如上例所示,第一步:执行第一行打印 “start”;第二步:执行setTimeout,将其中的函数分存起来,等待时间结束后执行;第三步:执行最后一行,打印“end”;第四部:处于空闲状态,查看暂存中,是否有可执行的函数;第五步:执行分存函数。

为什么js引擎是单线程?

js的主要用途是与用户互动,以及操作DOM,这决定它只能是单线程。例:一个线程要添加DOM节点,一个线程要删减DOM节点,容易造成分歧。

为了更好使用多CPU,H5提供了web Worker 标准,允许js创建多线程,但是子线程受到主线程控制,而且不得操作DOM。

任务列队

单线程就意味着,所有的任务都要排队,前一个结束,才会执行后面的任务。如果列队是因为计算量大,CPU忙不过来,倒也算了。但是更多的时候,CPU是闲置的,因为IO设备处理得很慢,例如 ajax读取网络数据。js设计者便想到,主线程完全可以不管IO设备,将其挂起,然后执行后面的任务。等后面的任务结束掉,在反过头来处理挂起的任务。

好,我们来梳理一下:

1)所有的同步任务都在主线程上执行,行程一个执行栈。

2)除了主线程之外,还存在一个任务列队,只要一步任务有了运行结果,就在任务列队中植入一个时间。

3)主线程完成所有任务,就会读取列队任务,并将其执行。

4)重复上面三步。

只要主线程空了,就会读取任务列队,这就是js的运行机制,也被称为 event loop(事件循环)。

前端面试送命题(二)-callback,promise,generator,async-await

前言

本篇文章适合前端架构师,或者进阶的前端开发人员;我在面试vmware前端架构师的时候,被问到关于callback,promise,generator,async-await的问题。

首先我们回顾一下javascript异步的发展历程。

ES6 以前:

  回调函数(callback);nodejs express 中常用,ajax中常用。

ES6:

  promise对象; nodejs最早有bluebird promise的雏形,axios中常用。

  generator函数;nodejs koa框架使用率很高。

ES7:

  async/await语法; 当前最常用的异步语法,nodejs koa2 完全使用该语法。

回调函数callback

回调函数实际就是一个参数;将一个函数当做参数传到另一个函数里,当那个函数执行完后,再执行传进去的这个函数;这个过程就叫做回调。

回调字面也好理解,就是先处理本体函数,再处理回调的函数,举个例子,方便大家理解。

复制代码; "复制代码")

function A(callback){
    console.log("我是主体函数");
    callback();
}

function B(){
    console.log("我是回调函数");
}

A(B);
/*输出结果
我是主体函数
我是回调函数
*/

复制代码; "复制代码")

上面的例子很好理解,首先执行主体函数A,打印结果:我是主题函数;然后执行回调函数callback 也就是B,打印结果:我是回调函数。

promise对象

promise 对象用于一个异步操作的最终完成(或最终失败)及其结果的表示。

简单地说就是处理一个异步请求。我们经常会做些断言,如果我赢了你就嫁给我,如果输了我就嫁给你之类的断言。这就是promise的中文含义:断言,一个成功,一个失败。

举个例子,方便大家理解:

promise构造函数的参数是一个函数,我们把它称为处理器函数,处理器函数接收两个函数reslove和reject作为其参数,当异步操作顺利执行则执行reslove函数, 当异步操作中发生异常时,则执行reject函数。通过resolve传入得的值,可以在then方法中获取到,通过reject传入的值可以在chatch方法中获取到。

因为then和catch都返回一个相同的promise对象,所以可以进行链式调用。

复制代码; "复制代码")

function readFileByPromise("a.txt"){
    //显示返回一个promise对象
    return new Promise((resolve,reject)=>{
        fs.readFile(path,"utf8",function(err,data){
            if(err)
                reject(err);
            else
                resolve(data);
        })
    })
}
//书写方式二
readFileByPromise("a.txt").then( data =>{
    //打印文件中的内容
    console.log(data);
}).catch( error =>{
    //抛出异常,
    console.log(error);
})

复制代码; "复制代码")

generator函数

ES6的新特性generator函数(面试的时候挂在这里),中文译为生成器,在以前一个函数中的代码要么被调用,要么不被调用,还不存在能暂停的情况,generator让代码暂停成待执行,定义一个生成器很简单,在函数名前加个*号,使用上也与普通函数有区别。

举个例子,方便大家理解:

function *Calculate(a,b){
  let sum=a+b;
  console.log(sum);
  let sub=a-b;
  console.log(sub);
}

上面便是一个简单的generator声明例子。

generator函数不能直接调用,直接调用generator函数会返回一个对象,只有调用该对象的next()方法才能执行函数里的代码。

let gen=Calculate(2,7);

执行该函数:

gen.next();
/*打印
9
-5
*/

其实单独介绍generator并没有太大的价值,要配合key yield,才能真正发挥generator的价值。yield能将生Generator函数的代码逻辑分割成多个部分,下面改写上面的生成器函数。

复制代码; "复制代码")

function *Calculate(a,b){
  let sum=a+b;
  yield console.log(sum);
  let sub=a-b;
  yield console.log(sub);
}
let gen=Calculate(2,7);
gen.next();
/*输出
9*/

复制代码; "复制代码")

可以看到这段代码执行到第一个yield处就停止了,如果要让里边所有的代码都执行完就得反复调用next()方法

let gen=Calculate(2,7);
gen.next();
gen.next();
/*输出
9
-5*/

在用一个例子,来说明generator函数与回调函数的区别:

回调函数:

复制代码; "复制代码")

fs.readFile("a.txt",(err,data)=>{
    if(!err){
        console.log(data);
        fs.readFile("b.txt",(err,data)=>{
            if(!err)
                console.log(data);
        })
    }
})

复制代码; "复制代码")

这是一个典型的回调嵌套,过多的回调嵌套造成代码的可读性和可维护性大大降低,形成了令人深恶痛绝的回调地狱,试想如果有一天让你按顺序读取10个文件,那就得嵌套10层,再或者需求变更,读取顺序要变了先读b.txt,再度a.txt那改来真的不要太爽。

generator函数:

复制代码; "复制代码")

function readFile(path) {
    fs.readFile(path,"utf8",function(err,data){
          it.next(data);
    })
}

function *main() {
    var result1 = yield readFile("a.txt");
    console.log(result1);

    var result2 = yield readFile("b.txt");
    console.log(result2);

    var result3 = yield readFile("c.txt");
    console.log(result3);
}

var it = main();
it.next(); 

复制代码; "复制代码")

generator函数的强大在于允许你通过一些实现细节来将异步过程隐藏起来,依然使代码保持一个单线程、同步语法的代码风格。这样的语法使得我们能够很自然的方式表达我们程序的步骤/语句流程,而不需要同时去操作一些异步的语法格式。

async-await

async函数返回一个promise对象,如果在async函数中返回一个直接量,async会通过Promise.resolve封装成Promise对象。
我们可以通过调用promise对象的then方法,获取这个直接量。

复制代码; "复制代码")

async function test(){
    return "Hello World";
}

var result=test();
console.log(result);
//打印Promise { 'Hello World' }

复制代码; "复制代码")

那如过async函数不返回值,又会是怎么样呢?

async function test(){
   
}
var result=test();
console.log(result);
//打印Promise { undefined }

await会暂停当前async的执行,await会阻塞代码的执行,直到await后的表达式处理完成,代码才能继续往下执行。
await后的表达式既可以是一个Promise对象,也可以是任何要等待的值。
如果await等到的是一个 Promise 对象,await 就忙起来了,它会阻塞后面的代码,等着 Promise 对象 resolve,然后得到 resolve 的值,作为 await 表达式的运算结果。

上边你看到阻塞一词,不要惊慌,async/await只是一种语法糖,代码执行与多个callback嵌套调用没有区别,本质并不是同步代码,它只是让你思考代码逻辑的时候能够以同步的思维去思考,避开回调地狱,简而言之-async/await是以同步的思维去写异步的代码,所以async/await并不会影响node的并发数,大家可以大胆的应用到项目中去!

如果它等到的不是一个 Promise 对象,那 await 表达式的运算结果就是它等到的东西。

举个例子,方便大家理解:

复制代码; "复制代码")

function A() {
    return "Hello ";
}

async function B(){
    return "World";
}

async function C(){
    //等待一个字符串
    var s1=await A();
    //等待一个promise对象,await的返回值是promise对象resolve的值,也就是"World"
    var s2=await B();
    console.log(s1+s2);
}

C();
//打印"Hello World"

复制代码; "复制代码")

前端面试送命题(三)- 面试题篇

前言

前端入门很容易,简单地用html搭一个页面框架;会用一些css基本样式;懂得用jquery的dom交互,就基本可以挺着胸说自己是个前端工程师了。

但是前端提升异常艰难,算法、数据结构、oop;javascript、dom、css、html5这些仅仅是标配;性能优化、安全性、代码优雅性、seo,这些也是必然需要了解的。前端开发环境很轻便,也很繁琐,各种框架、架构模式的应用也是衡量专业程度的标准。

在这里总结了一些面试题供前端们参考。

内容

JavaScript中如何检测一个变量是字符串类型?

typeof(obj) === "string"   //true
obj.constructor === String  //true

trim()的使用场景

var str = "   hello  word  ";
str = str.trim();
console.log(str); //hello word,trim只会去掉字符中前后的空格,不会去除文字中的空格

this的典型应用

复制代码; "复制代码")

//1 input点击,获取值
<input type="button" id="text" value="点击一下" />
<script type="text/javascript">
    var btn = document.getElementById("text");
    btn.onclick = function() {
        alert(this.value);    //此处的this是按钮元素
    }
</script>

//2 构造函数
function Hello(name, color) {
  this.name = name;
  this.color = color;
}

//3 在html元素事件属性中使用
<input type=”button” onclick=”showInfo(this);” value=”点击一下”/>

复制代码; "复制代码")

instanceof还是typeof?

复制代码; "复制代码")

//相同点:JavaScript 中 typeof 和 instanceof 常用来判断一个变量是否为空,或者是什么类型的。
//细节区别:
//1.typeof 一般只能返回如下几个结果:number,boolean,string,function,object,undefined。
//2.typeof 来获取一个变量是否存在,如 if(typeof a!="undefined"){alert("ok")},而不要去使用 if(a) 因为如果 a 不存在(未声明)则会出错。
//3.对于 Array,Null 等特殊对象使用 typeof 一律返回 object,这正是 typeof 的局限性。
var a = new Array(); 
alert(a instanceof Array);  // true
alert(a instanceof Object)  // true

复制代码; "复制代码")

什么是跨域?

由于浏览器同源策略,凡是发送请求url的协议、域名、端口三者之间任意一与当前页面地址不同即为跨域。存在跨域的情况:
1.网络协议不同,如http协议访问https协议。
2.端口不同,如80端口访问8080端口。
3.域名不同,如qianduanblog.com访问baidu.com。
4.子域名不同,如abc.qianduanblog.com访问def.qianduanblog.com。
5.域名和域名对应ip,如www.bb.com访问8.8.8.8.

JavaScript面向对象?

复制代码; "复制代码")

//面向对象的基本特征:多态,继承,封包(完全参考java)
//prototype 原型链来达到继承的效果
function animal(name){
    this.name = name;
}
animal.prototype.sayName = function(){
    console.log("name is "+this.name);
}
var animal1 = new animal("动物");
animal1.sayName();  //动物
 
function dog(name){
    this.name = name;
}
dog.prototype = new animal()
var dog1= new dog("哈士奇");
dog1.sayName();   //哈士奇

复制代码; "复制代码")

复制代码; "复制代码")

//call()/apply()方法
function teacher(name,age){
    this.name = name;
    this.age = age;
    this.sayhi = function(){
        alert('name:'+name+", age:"+age);
    }
}
function student(){
    var args = arguments;
    teacher.call(this,args[0],args[1]);
    // teacher.apply(this,arguments);
}
var teacher1 = new teacher('xiaoming',23);
teacher1.sayhi();

var student1 = new student('xiaolan',12);
student1.sayhi();

// alert: name:xiaoming, age:23
// alert: name:xiaolan, age:12  

复制代码; "复制代码")

复制代码; "复制代码")

function teacher(name,age){
    this.name = name;
    this.age = age;
}
teacher.prototype.sayName = function(){
    console.log('name:'+this.name);
}
teacher.prototype.sayAge = function(){
    console.log('age:'+this.age);
}

function student(){
    var args = arguments;
    teacher.call(this,args[0],args[1]);
}
student.prototype = new teacher();

var student1 = new student('xiaolin',23);
student1.sayName();
student1.sayAge();
// name:xiaolin
// age:23

复制代码; "复制代码")

复制代码; "复制代码")

function Person(name,age){
    this.name = name;
    this.age = age;
    this.show = function(){
        console.log(this.name+", "+this.age);
    }
}

function Student(name,age){
    this.student = Person; //将Person类的构造函数赋值给this.student
    this.student(name,age); //js中实际上是通过对象冒充来实现继承的
    delete this.student; //移除对Person的引用
}

var s = new Student("小明",17);
s.show();

var p = new Person("小花",18);
p.show();
// 小明, 17
// 小花, 18  

复制代码; "复制代码")

写一个方法,去掉重复的元素

复制代码; "复制代码")

//方法1
var arr = [0,2,3,4,4,0,2];
var obj = {};
var tmp = [];
for(var i = 0 ;i< arr.length;i++){
   if( !obj[arr[i]] ){
      obj[arr[i]] = 1;
      tmp.push(arr[i]);
   }
}
console.log(tmp);

//方法2
var arr = [2,3,4,4,5,2,3,6],
   arr2 = [];
for(var i = 0;i< arr.length;i++){
    if(arr2.indexOf(arr[i]) < 0){
        arr2.push(arr[i]);
    }
}
console.log(arr2);

//方法3
var arr = [2,3,4,4,5,2,3,6];
var arr2 = arr.filter(function(element,index,self){
return self.indexOf(element) === index;
});
console.log(arr2);

复制代码; "复制代码")

Jquery 属性的几种操作方式

复制代码; "复制代码")

//addClass
$(selector).addClass(class):为每个匹配的元素添加指定的类名
//removeClass
$(selector).removeClass(class):从所有匹配的元素中删除全部或者指定的类,删除class中某个值;
//toggleClass
$(selector).toggleClass(class):如果存在(不存在)就删除(添加)一个类
//removeAttr
$(selector).removeAttr(class);删除class这个属性;

复制代码; "复制代码")

$(document).ready与window.onload()?

1.window.onload方法是在网页中所有的元素(包括元素的所有关联文件)完全加载到浏览器后才执行的。
2.$(document).ready() 方法可以在DOM载入就绪时就对其进行操纵,并调用执行绑定的函数。

$.get,$.post与$.ajax

复制代码; "复制代码")

相同点:都是异步请求的方式来获取服务端的数据;
不同点:
1. 请求方式不同:$.get() 方法使用GET方法来进行异步请求的。$.post() 方法使用POST方法来进行异步请求的。
2. 参数传递方式不同:get请求会将参数跟在URL后进行传递,而POST请求则是作为HTTP消息的实体内容发送给Web服务器的,这种传递是对用户不可见的。
3. 数据传输大小不同:get方式传输的数据大小不能超过2KB 而POST要大的多
4. 安全问题: GET 方式请求的数据会被浏览器缓存起来,因此有安全问题。

Ajax:
Ajax 是一种用于创建快速动态网页的技术。Ajax 是一种在无需重新加载整个网页的情况下,能够更新部分网页的技术。
$.get 与 $.post 都是$.ajax的语法糖。
例子
$.ajax({
    url:'http://www.baidu.com',
    type:'POST',
    data:data,
    cache:true,
    headers:{},
    beforeSend:function(){},
    success:function(){},
    error:function(){},
    complete:function(){}
}); 

复制代码; "复制代码")

jquery事件bind

定义和用法:主要用于给选择到的元素上绑定特定事件类型的监听函数;
语法:bind(type,[data],function(eventObject));
特点:
  (1)、适用于页面元素静态绑定。只能给调用它的时候已经存在的元素绑定事件,不能给未来新增的元素绑定事件。
  (2)、当页面加载完的时候,你才可以进行bind(),所以可能产生效率问题。
实例如下:$( "#members li a" ).bind( "click", function( e ) {} );

jquery事件live

复制代码; "复制代码")

定义和用法:主要用于给选择到的元素上绑定特定事件类型的监听函数;
语法:live(type, [data], fn);
特点:
  (1)、live方法并没有将监听器绑定到自己(this)身上,而是绑定到了this.context上了。
  (2)、live正是利用了事件委托机制来完成事件的监听处理,把节点的处理委托给了document,新添加的元素不必再绑定一次监听器。
  (3)、使用live()方法但却只能放在直接选择的元素后面,不能在层级比较深,连缀的DOM遍历方法后面使用,即$(“ul”).live...可以,但$("body").find("ul").live...不行; 
实例如下:$( document ).on( "click", "#members li a", function( e ) {} );

复制代码; "复制代码")

jquery事件delegate

复制代码; "复制代码")

定义和用法:将监听事件绑定在就近的父级元素上
语法:delegate(selector,type,[data],fn)
特点:
  (1)、选择就近的父级元素,因为事件可以更快的冒泡上去,能够在第一时间进行处理。
  (2)、更精确的小范围使用事件代理,性能优于.live()。可以用在动态添加的元素上。
实例如下:
$("#info_table").delegate("td","click",function(){/*显示更多信息*/});
$("table").find("#info").delegate("td","click",function(){/*显示更多信息*/});

复制代码; "复制代码")

jquery事件on

定义和用法:将监听事件绑定到指定元素上。
语法:on(type,[selector],[data],fn)
实例如下:$("#info_table").on("click","td",function(){/*显示更多信息*/});参数的位置写法与delegate不一样。
说明:on方法是当前JQuery推荐使用的事件绑定方法,附加只运行一次就删除函数的方法是one()。
总结:.bind(), .live(), .delegate(),.on()分别对应的相反事件为:.unbind(),.die(), .undelegate(),.off()

什么是盒子模型

在网页中,一个元素占有空间的大小由几个部分构成,其中包括元素的内容(content),元素的内边距(padding),
元素的边框(border),元素的外边距(margin)四个部分。这四个部分占有的空间中,有的部分可以显示相应的内容,而有的部分只用来分隔相邻的区域或区域。
4个部分一起构成了css中元素的盒模型。

三种降低页面加载时间的方法

压缩css、js文件
合并js、css文件,减少http请求
外部js、css文件放在最底下
减少dom操作,尽可能用变量替代不必要的dom操作

三种常见的web攻击

1.XSS(Cross-Site Scripting,跨站脚本攻击):指通过存在安全漏洞的Web网站注册用户的浏览器内运行非法的HTML标签或者JavaScript进行的一种攻击。
2.SQL注入攻击
3.CSRF(Cross-Site Request Forgeries,跨站点请求伪造):指攻击者通过设置好的陷阱,强制对已完成的认证用户进行非预期的个人信息或设定信息等某些状态更新。

针对页面内容的优化

1.减少 HTTP 请求 (Make Fewer HTTP Requests)
2.减少 DOM 元素数量 (Reduce the Number of DOM Elements)
3.使得 Ajax 可缓存 (Make Ajax Cacheable)

针对修饰文件的优化

1.把 CSS 放到代码页上端 (Put Stylesheets at the Top)
2.从页面中剥离 JavaScript 与 CSS (Make JavaScript and CSS External)
3.精简 JavaScript 与 CSS (Minify JavaScript and CSS)
4.避免 CSS 表达式 (Avoid CSS Expressions)

针对脚本的优化

1. 脚本放到 HTML 代码页底部 (Put Scripts at the Bottom)
2. 从页面中剥离 JavaScript 与 CSS (Make JavaScript and CSS External)
3. 精简 JavaScript 与 CSS (Minify JavaScript and CSS)
4. 移除重复脚本 (Remove Duplicate Scripts)

针对图片类文件的优化

1.优化图片
2 不要在 HTML 中使用缩放图片
3 使用恰当的图片格式
4 使用 CSS Sprites 技巧对图片优化

图像格式的区别

复制代码; "复制代码")

矢量图:图标字体,如 font-awesome;svg 
普通图:gif,jpg(jpeg),png
区别:
  1、gif:是是一种无损,8位图片格式。具有支持动画,索引透明,压缩等特性。适用于做色彩简单(色调少)的图片,如logo,各种小图标icons等。
  2、JPEG格式是一种大小与质量相平衡的压缩图片格式。适用于允许轻微失真的色彩丰富的照片,不适合做色彩简单(色调少)的图片,如logo,各种小图标icons等。
  3、png:PNG可以细分为三种格式:PNG8,PNG24,PNG32。后面的数字代表这种PNG格式最多可以索引和存储的颜色值。
关于透明:PNG8支持索引透明和alpha透明;PNG24不支持透明;而PNG32在24位的PNG基础上增加了8位(256阶)的alpha通道透明;

优缺点:
  1、能在保证最不失真的情况下尽可能压缩图像文件的大小。
  2、对于需要高保真的较复杂的图像,PNG虽然能无损压缩,但图片文件较大,不适合应用在Web页面上。

复制代码; "复制代码")

浏览器渲染页面流程

1.解析HTML文件,创建DOM树。
自上而下,遇到任何样式(link、style)与脚本(script)都会阻塞(外部样式不阻塞后续外部脚本的加载)。
2.解析CSS。优先级:浏览器默认设置<用户设置<外部样式<内联样式<HTML中的style样式;
3.将CSS与DOM合并,构建渲染树(Render Tree)
4.布局和绘制,重绘(repaint)和重排(reflow)

JS的进阶技巧

前言

你真的了解JS吗,看完全篇,你可能对人生产生疑问。

typeof

typeof运算符,把类型信息当做字符串返回。

复制代码; "复制代码")

//正则表达式 是个什么 ?

typeof /s/     // object

//null

typeof null   // object

复制代码; "复制代码")

正则表达式并不是一个‘function’,而是一个object。在大多数语言中,null 代表的是一个空指针(0x00),但是在js中,null为一个object。

instanceof

instanceof运算符,用来测试一个对象在其原型链中是否存在一个构造函数:prototype

//语法 object instanceof constructor 

function Person(){};
var p =new Person();
p instanceof Person;  //true

[] instanceof window.frames[0].Array  // false

因为 Array.prototype !== window.frames[0].Array.prototype ,因此,我们必须使用Array.isArray(obj)或者Object.prototype.toString.call(obj) === "[object Array]" 来判断obj是否为数组。

Object.prototype.toString

根据上面提到的,可以使用该方法获取对象类型的字符串。

复制代码; "复制代码")

//call的使用可以看博主前面的文章。(使用apply亦可)

var toString = Object.prototype.toString;

toString.call(new Date); // [object Date]
toString.call(new String); // [object String]
toString.call(Math); // [object Math]
toString.call(/s/); // [object RegExp]
toString.call([]); // [object Array]
toString.call(undefined); // [object Undefined]
toString.call(null); // [object Null]

复制代码; "复制代码")

作用域安全与构造函数

构造函数:使用new调用的函数,当使用new调用时,构造函数内的this会指向新创建的对象实例。

复制代码; "复制代码")

function Dog(name, age){
    this.name = name;
    this.age = age;
}

let dog = new Dog("柴犬", 5);

dog.name // 柴犬

复制代码; "复制代码")

如果我们没有使用new又会如何?

let dog = Dog("柴犬", 5);

dog.name // undefined
window.name // 柴犬

这是因为在没有使用new关键字时,this在当前情况被解析成window,所以属性就被分配到window上了。

复制代码; "复制代码")

function Dog(name, age){
    if(this instanceof Dog){
        this.name = name;
        this.age = age;   
    }else{
        return new Dog(name, age);
    }
}

let dog1 = new Person("柴犬", 5);
dog1.name // 柴犬

let dog2 = Dog("柯基犬", 20);
dog2 .name // 柯基犬

复制代码; "复制代码")

使用上面的方法,就可以再不使用new的情况下,正常构造函数。

惰性载入函数

一个函数如下:

复制代码; "复制代码")

function foo(){
    if(a != b){
        console.log('111') //返回结果1
    }else{
        console.log('222') //返回结果2
    }
}

复制代码; "复制代码")

a和b是不变的,那么无论执行多少次,结果都是不变的,但是每一次都要执行if判断语句,这样就造成了资源浪费

而惰性载入函数,便可以解决这个问题。

复制代码; "复制代码")

function foo(){
    if(a != b){
        foo = function(){
            console.log('111')
        }
    }else{
        foo = function(){
            console.log('222')
        }
    }
    return foo();
}

复制代码; "复制代码")

复制代码; "复制代码")

var foo = (function foo(){
    if(a != b){
        return function(){
            console.log('111')
        }
    }else{
        return function(){
            console.log('222')
        }
    }
})();

复制代码; "复制代码")

如上函数所示:第一次执行后便会对foo进行赋值,覆盖之前的函数,所以再次执行,便不会在执行if判断语句。

fun.bind(thisarg[,arg1[,arg2[,....]]])绑定函数

thisarg:当绑定函数被调用时,该参数会作为原函数运行时的this指向。当使用new时,该参数无效。

arg:当绑定时,这些参数将传递给被绑定的方法。

例子:

复制代码; "复制代码")

let person = {
   name: 'addone',
   click: function(e){
       console.log(this.name)
   }
}

let btn = document.getElementById('btn');
EventUtil.addHandle(btn, 'click', person.click);

复制代码; "复制代码")

这里创建了一个person对象,然后将person.click方法分配给DOM,但是当你按按钮时,会打印undefied,原因是this指向了DOM而不是person。

解决方法,当时是使用绑定函数了:

 EventUtil.addHandle(btn, 'click', person.click.bind(person));

函数柯里化

柯里化是把接受多个参数的函数转变成接受单一参数的函数。

复制代码; "复制代码")

//简单例子,方便你明白柯里化
function add(num1, num2){
    return num1 + num2;
}
function curryAdd(num2){
    return add(1, num2);
}
add(2, 3) // 5
curryAdd(2) // 3

复制代码; "复制代码")

下面是柯里化函数的通用方法:

复制代码; "复制代码")

function curry(fn){
    var args = Array.prototype.slice.call(arguments, 1);
    return function(){
        let innerArgs = Array.prototype.slice.call(arguments);
        let finalArgs = args.concat(innerArgs);
        return fn.apply(null, finalArgs);
    }
}

复制代码; "复制代码")

Array.prototype.slice.call(arguments, 1)来获取第一个参数后的所有参数。在函数中,同样调用 Array.prototype.slice.call(arguments)让innerArgs存放所有的参数, 然后用contact将内部外部参数组合起来,用apply传递函数。

复制代码; "复制代码")

function add(num1, num2){
    return num1 + num2;
}
let curryAdd1 = curry(add, 1);
curryAdd1(2); // 3

let curryAdd2 = curry(add, 1, 2);
curryAdd2(); // 3

复制代码; "复制代码")

不可扩展对象

默认情况下对象都是可扩展的,无论是扩展属性或是方法。

let dog = { name: '柴犬' };
dog.age = 5;

如第二行,我们为dog扩展了age属性。

使用Object.preventExtensions()可以阻止扩展行为。

let dog = { name: '柴犬' };
Object.preventExtensions(dog);
dog.age = 20;

dog.age // undefined

还可以使用 Object.isExtensible()来判断对象是否支持扩展。

let dog = { name: 'addone' };
Object.isExtensible(dog); // true

Object.preventExtensions(dog);
Object.isExtensible(dog); // false。

密封的对象

密封后的对象不可扩展,且不能删除属性和方法。

使用Object.seal()来进行密封。

复制代码; "复制代码")

let dog = { name: '柴犬' };
Object.seal(dog);

dog.age = 20;
delete dog.name;

dog.age // undefined
dog.name // 柴犬

复制代码; "复制代码")

当然也有Object.isSealed()来判断是否密封

复制代码; "复制代码")

let dog = { name: '柴犬' };
Object.isExtensible(dog); // true
Object.isSealed(dog); // false

Object.seal(dog);
Object.isExtensible(dog); // false
Object.isSealed(dog); // true

复制代码; "复制代码")

冻结对象

冻结对象为防篡改级别最高的,密封,且不能修改。

使用Object.freeze()来进行冻结。

复制代码; "复制代码")

let dog= { name: '柴犬' };
Object.freeze(dog);

dog.age = 20;
delete dog.name;
dog.name = '吉娃娃'

dog.age // undefined
dog.name // 柴犬

复制代码; "复制代码")

当然也有Object.isFrozen()来判断是否冻结

复制代码; "复制代码")

let dog = { name: '柴犬' };
Object.isExtensible(dog); // true
Object.isSealed(dog); // false
Object.isFrozen(dog); // false

Object.freeze(dog);
Object.isExtensible(dog); // false
Object.isSealed(dog); // true
Object.isFrozen(dog); // true

复制代码; "复制代码")

数组分块

浏览器对长时间运行的脚本进行了制约,如果运行时间超过特定时间或者特定长度,便不会继续执行。

如果发现某个循环占用了大量的时间,那么就要面对下面两个问题:

1.该循环是否必须同步完成?

2.数据是否必须按顺序完成?

如果是否,那么我们可以使用一种叫做数组分块的技术。基本思路是为要处理的项目创建一个列队,然后使用定时取出一个要处理的项目进行处理,以此类推。

复制代码; "复制代码")

function chunk(array, process, context){
    setTimeout(function(){
        // 取出下一个项目进行处理
        let item = array.shift();
        process.call(item);
      
        if(array.length > 0){
            setTimeout(arguments.callee, 100);
        }
    }, 100)
}

复制代码; "复制代码")

这里设置三个参数,要处理的数组,处理的函数,运行该函数的环境。

节流函数

节流函数目的是为了防止某些操作执行的太快,比如onresize,touch等事件。这种高频率的操作可能会使浏览器崩溃,为了避免这种情况,可以采用节流的方式。

function throttle(method, context){
    clearTimeout(method.tId);
    method.tId = setTimeout(function(){
        method.call(context);
    }, 100)
}

这里接收两个参数,要执行的函数,和执行的环境。执行时先clear之前的定时器,然后将当前定时器赋值给方法的tId,之后调用call来确定函数的执行环境。

复制代码; "复制代码")

function resizeDiv(){
    let div = document.getElementById('div');
    div.style.height = div.offsetWidth + "px";
}

window.onresize = function(){
    throttle(resizeDiv);
}

复制代码; "复制代码")

标签: javascript, js, js进阶

2018年JavaScript现状报告

前言

JavaScript(后面统称JS)在过去五年得到飞速地增长,早期JS实现类似微博的“点赞”这样的功能都需要刷新一次页面。

后来开发者通过JS来制作SPA(单页面应用程序),在浏览器加载一次,后续视图都是通过JS动态生成的。webpack技术出现,解决了SPA页面加载过慢的问题,提升了性能与用户体验(但我觉得是因为4G和1M+宽带的普及)。

尽管JS最初是一门弱类型浏览器语言,但现在,在互联网各个领域都大展身手;毫无疑问JS已经成为一门一流的编程语言。

最近我看到斯坦福大学做的一份JS现状调查报告,调查覆盖了2万多名开发者(包括中国);这份报告向我们展示了许多信息,从流行趋势到薪资情况;话不多说,让我们看一下这份报告的细节。

前端框架部分

最受欢迎的前端框架React

React已经连续三年蝉联最受欢迎前端框架,十分受中小企业欢迎(react是要license的,很多大公司已经弃用这个框架,2018年2月份改为MIT)。

如图所示,紫色部分越多,就说明该框架就越受欢迎。

Angularjs现状

React的火热并不意味着我们可以忽略掉Angular,虽然它没有React的活力,但是却有强大的基因。

首先,它是由google团队运维的,并且同时也得到了他们得支持。Angular拥有庞大得用户群,例如openstack(Angular1)。

上图可以看出,Angular1要比Angular2更受欢迎。

实际上Angular更聚焦于企业市场,Angular2与TypeScript的结合就可以看出端倪。

不能忽视的Vuejs

国人维护开源的前端框架,短时间内就成为React的最大威胁;

实际Vuejs击败React的可能性并不大,但是Vuejs可以给我们带来更好的用户体验(毕竟有完整的中文doc)。

一些库可以让你获得更高的薪资

通过下面图表,你可以获悉,熟知哪些库可以给你带来更高的工资。

事实证明,与高工资关联的通常是一种特殊的技术,例如Reason,ClojureScript(这两个都没听说过)

这两张图表不太适用于国内市场,更高级的开发人员更倾向于尝试更多样化的库,而初级开发人员比较专注于一两种主流技术。

GraphQL一种用于API查询的语言

GraphQL一种用于API查询的语言,它对你的API数据提供了一套易于理解的完整描述,使得客户端能够准时地获得它需要的数据,而且没有任何冗余。

虽然目前使用者较少,但是我相信GraphQL是最令人感兴趣的技术之一。

JavaScript !== 前端

JS因浏览器前端而知名,但是不等于它只能做前端的相关项目;经过这么多年的迭代,Nodejs已经是流行的后端选择。

而在物联网设备中,JS也越来越趋向于主流,以后你家的电视能够运行JS代码也不要感到奇怪。

今年大受欢迎的VS code文本编辑器,也是有JS编写的,并且使用Electron做载体,在桌面运行。

世界各地JSer

当我们谈到JS,我们经常把它看作一个单一的,统一的生态系统,实际,在世界各地都有各自的主流趋势。

Vuejs在中国特别受欢迎,Angular在印度比较受欢迎,这也很合理,Angular本身就适合企业级的大项目,印度又是以技术外包而闻名。

强类型JS崛起

TypeScript,GraphQL,Reason都有共同的特点,他们都依赖于类型。

虽然JS开发人员长期以来享受着随心所欲地编写代码,这种自由是一把双刃剑:不可靠,容易出错。

近些年,这种情况得到了改善,TypeScript的出现,并且被大量地使用,这并不是巧合,而是一种进化。

总结

这项报告调查再一次显示了JS生态系统的丰富程度。

似乎经过了多年的争论和忽视 JS之后,开发者们终于找到了第三种选择:改进它。

JS基本上是能满足你任何想要实现的需求,它为语言变体和适应性敞开大门,而且这种趋势几乎没有停止的迹象。

这也许就是为什么大多数开发者都认为,尽管存在缺陷, JS语言仍然朝着正确的方向发展:

Last modification:December 27, 2020
如果觉得我的文章对你有用,请随意赞赏